BTThreeDSecureRequest.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. #import "BTPaymentFlowDriver+ThreeDSecure_Internal.h"
  2. #import "BTThreeDSecureRequest_Internal.h"
  3. #import "BTThreeDSecurePostalAddress_Internal.h"
  4. #import "BTThreeDSecureAdditionalInformation_Internal.h"
  5. #import "BTThreeDSecureV2Provider.h"
  6. #import "BTThreeDSecureV1BrowserSwitchHelper.h"
  7. #import "BTThreeDSecureResult_Internal.h"
  8. #import <SafariServices/SafariServices.h>
  9. #if __has_include(<Braintree/BraintreeThreeDSecure.h>) // CocoaPods
  10. #import <Braintree/BTThreeDSecureRequest.h>
  11. #import <Braintree/BTThreeDSecureResult.h>
  12. #import <Braintree/BTThreeDSecureLookup.h>
  13. #import <Braintree/BTConfiguration+ThreeDSecure.h>
  14. #import <Braintree/BraintreeCard.h>
  15. #import <Braintree/BTLogger_Internal.h>
  16. #import <Braintree/BTAPIClient_Internal.h>
  17. #import <Braintree/Braintree-Version.h>
  18. #import <Braintree/BTPaymentFlowDriver_Internal.h>
  19. #elif SWIFT_PACKAGE // SPM
  20. #import <BraintreeThreeDSecure/BTThreeDSecureRequest.h>
  21. #import <BraintreeThreeDSecure/BTThreeDSecureResult.h>
  22. #import <BraintreeThreeDSecure/BTThreeDSecureLookup.h>
  23. #import <BraintreeThreeDSecure/BTConfiguration+ThreeDSecure.h>
  24. #import <BraintreeCard/BraintreeCard.h>
  25. #import "../BraintreeCore/BTLogger_Internal.h"
  26. #import "../BraintreeCore/BTAPIClient_Internal.h"
  27. #import "../BraintreeCore/Braintree-Version.h"
  28. #import "../BraintreePaymentFlow/BTPaymentFlowDriver_Internal.h"
  29. #else // Carthage
  30. #import <BraintreeThreeDSecure/BTThreeDSecureRequest.h>
  31. #import <BraintreeThreeDSecure/BTThreeDSecureResult.h>
  32. #import <BraintreeThreeDSecure/BTThreeDSecureLookup.h>
  33. #import <BraintreeThreeDSecure/BTConfiguration+ThreeDSecure.h>
  34. #import <BraintreeCard/BraintreeCard.h>
  35. #import <BraintreeCore/BTLogger_Internal.h>
  36. #import <BraintreeCore/BTAPIClient_Internal.h>
  37. #import <BraintreeCore/Braintree-Version.h>
  38. #import <BraintreePaymentFlow/BTPaymentFlowDriver_Internal.h>
  39. #endif
  40. @interface BTThreeDSecureRequest () <BTThreeDSecureRequestDelegate>
  41. @property (nonatomic, strong) BTThreeDSecureV2Provider *threeDSecureV2Provider;
  42. @end
  43. @implementation BTThreeDSecureRequest
  44. - (instancetype)init {
  45. self = [super init];
  46. if (self) {
  47. _versionRequested = BTThreeDSecureVersion2;
  48. }
  49. return self;
  50. }
  51. - (NSString *)accountTypeAsString {
  52. switch (self.accountType) {
  53. case BTThreeDSecureAccountTypeCredit:
  54. return @"credit";
  55. case BTThreeDSecureAccountTypeDebit:
  56. return @"debit";
  57. default:
  58. return nil;
  59. }
  60. }
  61. - (NSString *)shippingMethodAsString {
  62. switch (self.shippingMethod) {
  63. case BTThreeDSecureShippingMethodSameDay:
  64. return @"01";
  65. case BTThreeDSecureShippingMethodExpedited:
  66. return @"02";
  67. case BTThreeDSecureShippingMethodPriority:
  68. return @"03";
  69. case BTThreeDSecureShippingMethodGround:
  70. return @"04";
  71. case BTThreeDSecureShippingMethodElectronicDelivery:
  72. return @"05";
  73. case BTThreeDSecureShippingMethodShipToStore:
  74. return @"06";
  75. default:
  76. return nil;
  77. }
  78. }
  79. - (NSString *)versionRequestedAsString {
  80. switch (self.versionRequested) {
  81. case BTThreeDSecureVersion1:
  82. return @"1";
  83. default:
  84. return @"2";
  85. }
  86. }
  87. - (NSString *)requestedExemptionTypeAsString {
  88. switch (self.requestedExemptionType) {
  89. case BTThreeDSecureRequestedExemptionTypeLowValue:
  90. return @"low_value";
  91. case BTThreeDSecureRequestedExemptionTypeSecureCorporate:
  92. return @"secure_corporate";
  93. case BTThreeDSecureRequestedExemptionTypeTrustedBeneficiary:
  94. return @"trusted_beneficiary";
  95. case BTThreeDSecureRequestedExemptionTypeTransactionRiskAnalysis:
  96. return @"transaction_risk_analysis";
  97. default:
  98. return nil;
  99. }
  100. }
  101. - (void)handleRequest:(BTPaymentFlowRequest *)request
  102. client:(BTAPIClient *)apiClient
  103. paymentDriverDelegate:(id<BTPaymentFlowDriverDelegate>)delegate {
  104. self.paymentFlowDriverDelegate = delegate;
  105. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.initialized"];
  106. [apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration * _Nullable configuration, NSError * _Nullable configurationError) {
  107. if (configurationError) {
  108. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:configurationError];
  109. return;
  110. }
  111. NSError *integrationError;
  112. if (self.versionRequested == BTThreeDSecureVersion2 && !configuration.cardinalAuthenticationJWT) {
  113. [[BTLogger sharedLogger] critical:@"BTThreeDSecureRequest versionRequested is 2, but merchant account is not setup properly."];
  114. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  115. code:BTThreeDSecureFlowErrorTypeConfiguration
  116. userInfo:@{NSLocalizedDescriptionKey: @"BTThreeDSecureRequest versionRequested is 2, but merchant account is not setup properly."}];
  117. }
  118. if (!self.amount || [self.amount isEqualToNumber:NSDecimalNumber.notANumber]) {
  119. [[BTLogger sharedLogger] critical:@"BTThreeDSecureRequest amount can not be nil or NaN."];
  120. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  121. code:BTThreeDSecureFlowErrorTypeConfiguration
  122. userInfo:@{NSLocalizedDescriptionKey: @"BTThreeDSecureRequest amount can not be nil or NaN."}];
  123. }
  124. if (self.versionRequested == BTThreeDSecureVersion2 && self.threeDSecureRequestDelegate == nil) {
  125. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  126. code:BTThreeDSecureFlowErrorTypeConfiguration
  127. userInfo:@{NSLocalizedDescriptionKey: @"Configuration Error: threeDSecureRequestDelegate can not be nil when versionRequested is 2."}];
  128. }
  129. if (integrationError != nil) {
  130. [delegate onPaymentComplete:nil error:integrationError];
  131. return;
  132. }
  133. if (configuration.cardinalAuthenticationJWT && self.versionRequested == BTThreeDSecureVersion2) {
  134. [self prepareLookup:apiClient completion:^(NSError * _Nullable error) {
  135. if (error != nil) {
  136. [delegate onPaymentComplete:nil error:error];
  137. } else {
  138. [self startRequest:request configuration:configuration];
  139. }
  140. }];
  141. } else {
  142. [self startRequest:request configuration:configuration];
  143. }
  144. }];
  145. }
  146. - (void)prepareLookup:(BTAPIClient *)apiClient completion:(void (^)(NSError * _Nullable))completionBlock {
  147. [apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration * _Nullable configuration, NSError * _Nullable configurationError) {
  148. if (configurationError) {
  149. completionBlock(configurationError);
  150. return;
  151. }
  152. if (configuration.cardinalAuthenticationJWT) {
  153. self.threeDSecureV2Provider = [BTThreeDSecureV2Provider initializeProviderWithConfiguration:configuration
  154. apiClient:apiClient
  155. request:self
  156. completion:^(NSDictionary *lookupParameters) {
  157. if (lookupParameters[@"dfReferenceId"]) {
  158. self.dfReferenceID = lookupParameters[@"dfReferenceId"];
  159. }
  160. completionBlock(nil);
  161. }];
  162. } else {
  163. NSError *error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  164. code:BTThreeDSecureFlowErrorTypeConfiguration
  165. userInfo:@{NSLocalizedDescriptionKey: @"Merchant is not configured for 3SD 2."}];
  166. completionBlock(error);
  167. }
  168. }];
  169. }
  170. - (void)startRequest:(BTPaymentFlowRequest *)request configuration:(BTConfiguration *)configuration {
  171. BTThreeDSecureRequest *threeDSecureRequest = (BTThreeDSecureRequest *)request;
  172. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  173. BTPaymentFlowDriver *paymentFlowDriver = [[BTPaymentFlowDriver alloc] initWithAPIClient:apiClient];
  174. if (threeDSecureRequest.versionRequested == BTThreeDSecureVersion1 && threeDSecureRequest.threeDSecureRequestDelegate == nil) {
  175. threeDSecureRequest.threeDSecureRequestDelegate = self;
  176. }
  177. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.started"];
  178. [paymentFlowDriver performThreeDSecureLookup:threeDSecureRequest
  179. completion:^(BTThreeDSecureResult *lookupResult, NSError *error) {
  180. dispatch_async(dispatch_get_main_queue(), ^{
  181. if (error) {
  182. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  183. [self.paymentFlowDriverDelegate onPaymentWithURL:nil error:error];
  184. return;
  185. }
  186. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.3ds-version.%@", lookupResult.lookup.threeDSecureVersion]];
  187. [self.threeDSecureRequestDelegate onLookupComplete:threeDSecureRequest lookupResult:lookupResult next:^{
  188. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.challenge-presented.%@",
  189. [self stringForBool:lookupResult.lookup.requiresUserAuthentication]]];
  190. [self processLookupResult:lookupResult configuration:configuration];
  191. }];
  192. });
  193. }];
  194. }
  195. - (void)processLookupResult:(BTThreeDSecureResult *)lookupResult configuration:(BTConfiguration *)configuration {
  196. if (!lookupResult.lookup.requiresUserAuthentication) {
  197. [self.paymentFlowDriverDelegate onPaymentComplete:lookupResult error:nil];
  198. return;
  199. }
  200. if (lookupResult.lookup.isThreeDSecureVersion2) {
  201. [self performV2Authentication:lookupResult];
  202. } else {
  203. NSURL *browserSwitchURL = [BTThreeDSecureV1BrowserSwitchHelper urlWithScheme:self.paymentFlowDriverDelegate.returnURLScheme
  204. assetsURL:[configuration.json[@"assetsUrl"] asString]
  205. threeDSecureRequest:self
  206. threeDSecureLookup:lookupResult.lookup];
  207. [self.paymentFlowDriverDelegate onPaymentWithURL:browserSwitchURL error:nil];
  208. }
  209. }
  210. - (void)performV2Authentication:(BTThreeDSecureResult *)lookupResult {
  211. typeof(self) __weak weakSelf = self;
  212. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  213. [self.threeDSecureV2Provider processLookupResult:lookupResult
  214. success:^(BTThreeDSecureResult *result) {
  215. [weakSelf logThreeDSecureCompletedAnalyticsForResult:result withAPIClient:apiClient];
  216. [weakSelf.paymentFlowDriverDelegate onPaymentComplete:result error:nil];
  217. } failure:^(NSError *error) {
  218. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  219. [weakSelf.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  220. }];
  221. }
  222. - (void)handleOpenURL:(NSURL *)url {
  223. NSString *jsonAuthResponse = [BTURLUtils queryParametersForURL:url][@"auth_response"];
  224. if (!jsonAuthResponse || jsonAuthResponse.length == 0) {
  225. [self.paymentFlowDriverDelegate.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.missing-auth-response"]];
  226. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  227. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  228. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response missing from URL."}]];
  229. return;
  230. }
  231. NSError *jsonError;
  232. NSData *jsonData = [NSJSONSerialization JSONObjectWithData:[jsonAuthResponse dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&jsonError];
  233. if (!jsonData) {
  234. [self.paymentFlowDriverDelegate.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.invalid-auth-response"]];
  235. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  236. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  237. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response JSON parsing error."}]];
  238. return;
  239. }
  240. BTJSON *authBody = [[BTJSON alloc] initWithValue:jsonData];
  241. if (!authBody.isObject) {
  242. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  243. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  244. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response is not a valid BTJSON object."}]];
  245. return;
  246. }
  247. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  248. BTThreeDSecureResult *result = [[BTThreeDSecureResult alloc] initWithJSON:authBody];
  249. if (result.errorMessage || !result.tokenizedCard) {
  250. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  251. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
  252. if (result.errorMessage) {
  253. userInfo[NSLocalizedDescriptionKey] = result.errorMessage;
  254. }
  255. NSError *error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  256. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  257. userInfo:userInfo];
  258. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  259. return;
  260. }
  261. [self logThreeDSecureCompletedAnalyticsForResult:result withAPIClient:apiClient];
  262. [self.paymentFlowDriverDelegate onPaymentComplete:result error:nil];
  263. }
  264. - (void)logThreeDSecureCompletedAnalyticsForResult:(BTThreeDSecureResult *)result withAPIClient:(BTAPIClient *)apiClient {
  265. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.liability-shift-possible.%@",
  266. [self stringForBool:result.tokenizedCard.threeDSecureInfo.liabilityShiftPossible]]];
  267. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.liability-shifted.%@",
  268. [self stringForBool:result.tokenizedCard.threeDSecureInfo.liabilityShifted]]];
  269. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.completed"];
  270. }
  271. - (BOOL)canHandleAppSwitchReturnURL:(NSURL *)url {
  272. return [url.host isEqualToString:@"x-callback-url"] && [url.path hasPrefix:@"/braintree/threedsecure"];
  273. }
  274. - (NSString *)paymentFlowName {
  275. return @"three-d-secure";
  276. }
  277. - (NSString *)stringForBool:(BOOL)boolean {
  278. if (boolean) {
  279. return @"true";
  280. }
  281. else {
  282. return @"false";
  283. }
  284. }
  285. - (void)onLookupComplete:(__unused BTThreeDSecureRequest *)request
  286. lookupResult:(__unused BTThreeDSecureResult *)result
  287. next:(void (^)(void))next {
  288. next();
  289. }
  290. @end