BTPaymentFlowDriver+ThreeDSecure.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. #import "BTPaymentFlowDriver+ThreeDSecure_Internal.h"
  2. #import "BTThreeDSecureResult_Internal.h"
  3. #import "BTThreeDSecureRequest_Internal.h"
  4. #import "BTThreeDSecurePostalAddress_Internal.h"
  5. #import "BTThreeDSecureAdditionalInformation_Internal.h"
  6. #if __has_include(<Braintree/BraintreeThreeDSecure.h>) // CocoaPods
  7. #import <Braintree/BTPaymentFlowDriver+ThreeDSecure.h>
  8. #import <Braintree/BTThreeDSecureRequest.h>
  9. #import <Braintree/BraintreeCore.h>
  10. #import <Braintree/BTPaymentFlowDriver_Internal.h>
  11. #import <Braintree/BTAPIClient_Internal.h>
  12. #import <Braintree/Braintree-Version.h>
  13. #elif SWIFT_PACKAGE // SPM
  14. #import <BraintreeThreeDSecure/BTPaymentFlowDriver+ThreeDSecure.h>
  15. #import <BraintreeThreeDSecure/BTThreeDSecureRequest.h>
  16. #import <BraintreeCore/BraintreeCore.h>
  17. #import "../BraintreePaymentFlow/BTPaymentFlowDriver_Internal.h"
  18. #import "../BraintreeCore/BTAPIClient_Internal.h"
  19. #import "../BraintreeCore/Braintree-Version.h"
  20. #else // Carthage
  21. #import <BraintreeThreeDSecure/BTPaymentFlowDriver+ThreeDSecure.h>
  22. #import <BraintreeThreeDSecure/BTThreeDSecureRequest.h>
  23. #import <BraintreeCore/BraintreeCore.h>
  24. #import <BraintreePaymentFlow/BTPaymentFlowDriver_Internal.h>
  25. #import <BraintreeCore/BTAPIClient_Internal.h>
  26. #import <BraintreeCore/Braintree-Version.h>
  27. #endif
  28. @implementation BTPaymentFlowDriver (ThreeDSecure)
  29. NSString * const BTThreeDSecureFlowErrorDomain = @"com.braintreepayments.BTThreeDSecureFlowErrorDomain";
  30. NSString * const BTThreeDSecureFlowInfoKey = @"com.braintreepayments.BTThreeDSecureFlowInfoKey";
  31. NSString * const BTThreeDSecureFlowValidationErrorsKey = @"com.braintreepayments.BTThreeDSecureFlowValidationErrorsKey";
  32. #pragma mark - ThreeDSecure Lookup
  33. - (void)performThreeDSecureLookup:(BTThreeDSecureRequest *)request
  34. completion:(void (^)(BTThreeDSecureResult * _Nullable threeDSecureResult, NSError * _Nullable error))completionBlock {
  35. [self.apiClient fetchOrReturnRemoteConfiguration:^(__unused BTConfiguration *configuration, NSError *error) {
  36. if (error) {
  37. completionBlock(nil, error);
  38. return;
  39. }
  40. NSMutableDictionary *customer = [[NSMutableDictionary alloc] init];
  41. NSMutableDictionary *requestParameters = [@{ @"amount": request.amount,
  42. @"customer": customer,
  43. @"requestedThreeDSecureVersion": request.versionRequestedAsString } mutableCopy];
  44. if (request.dfReferenceID) {
  45. requestParameters[@"dfReferenceId"] = request.dfReferenceID;
  46. }
  47. if (request.accountTypeAsString) {
  48. requestParameters[@"accountType"] = request.accountTypeAsString;
  49. }
  50. if (request.challengeRequested) {
  51. requestParameters[@"challengeRequested"] = @(request.challengeRequested);
  52. }
  53. if (request.exemptionRequested) {
  54. requestParameters[@"exemptionRequested"] = @(request.exemptionRequested);
  55. }
  56. if (request.requestedExemptionTypeAsString) {
  57. requestParameters[@"requestedExemptionType"] = request.requestedExemptionTypeAsString;
  58. }
  59. if (request.dataOnlyRequested) {
  60. requestParameters[@"dataOnlyRequested"] = @(request.dataOnlyRequested);
  61. }
  62. if (request.cardAddChallenge == BTThreeDSecureCardAddChallengeRequested) {
  63. requestParameters[@"cardAdd"] = @(YES);
  64. } else if (request.cardAddChallenge == BTThreeDSecureCardAddChallengeNotRequested) {
  65. requestParameters[@"cardAdd"] = @(NO);
  66. }
  67. NSMutableDictionary *additionalInformation = [NSMutableDictionary dictionary];
  68. if (request.billingAddress) {
  69. [additionalInformation addEntriesFromDictionary:[request.billingAddress asParametersWithPrefix:@"billing"]];
  70. }
  71. if (request.mobilePhoneNumber) {
  72. additionalInformation[@"mobilePhoneNumber"] = request.mobilePhoneNumber;
  73. }
  74. if (request.email) {
  75. additionalInformation[@"email"] = request.email;
  76. }
  77. if (request.shippingMethodAsString) {
  78. additionalInformation[@"shippingMethod"] = request.shippingMethodAsString;
  79. }
  80. if (request.additionalInformation) {
  81. [additionalInformation addEntriesFromDictionary:[request.additionalInformation asParameters]];
  82. }
  83. if (additionalInformation.count) {
  84. requestParameters[@"additionalInfo"] = additionalInformation;
  85. }
  86. NSString *urlSafeNonce = [request.nonce stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
  87. [self.apiClient POST:[NSString stringWithFormat:@"v1/payment_methods/%@/three_d_secure/lookup", urlSafeNonce]
  88. parameters:requestParameters
  89. completion:^(BTJSON *body, __unused NSHTTPURLResponse *response, NSError *error) {
  90. if (error) {
  91. if (error.code == NETWORK_CONNECTION_LOST_CODE) {
  92. [self.apiClient sendAnalyticsEvent:@"ios.three-d-secure.lookup.network-connection.failure"];
  93. }
  94. // Provide more context for card validation error when status code 422
  95. if ([error.domain isEqualToString:BTHTTPErrorDomain] &&
  96. error.code == BTHTTPErrorCodeClientError &&
  97. ((NSHTTPURLResponse *)error.userInfo[BTHTTPURLResponseKey]).statusCode == 422) {
  98. NSMutableDictionary *userInfo = [error.userInfo mutableCopy];
  99. BTJSON *errorBody = error.userInfo[BTHTTPJSONResponseBodyKey];
  100. if ([errorBody[@"error"][@"message"] isString]) {
  101. userInfo[NSLocalizedDescriptionKey] = [errorBody[@"error"][@"message"] asString];
  102. }
  103. if ([errorBody[@"threeDSecureInfo"] isObject]) {
  104. userInfo[BTThreeDSecureFlowInfoKey] = [errorBody[@"threeDSecureInfo"] asDictionary];
  105. }
  106. if ([errorBody[@"error"] isObject]) {
  107. userInfo[BTThreeDSecureFlowValidationErrorsKey] = [errorBody[@"error"] asDictionary];
  108. }
  109. error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  110. code:BTThreeDSecureFlowErrorTypeFailedLookup
  111. userInfo:userInfo];
  112. }
  113. completionBlock(nil, error);
  114. return;
  115. }
  116. completionBlock([[BTThreeDSecureResult alloc] initWithJSON:body], nil);
  117. }];
  118. }];
  119. }
  120. - (void)prepareLookup:(BTPaymentFlowRequest<BTPaymentFlowRequestDelegate> *)request completion:(void (^)(NSString * _Nullable, NSError * _Nullable))completionBlock {
  121. BTThreeDSecureRequest *threeDSecureRequest = (BTThreeDSecureRequest *)request;
  122. NSError *integrationError;
  123. if (self.apiClient.clientToken == nil) {
  124. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  125. code:BTThreeDSecureFlowErrorTypeConfiguration
  126. userInfo:@{NSLocalizedDescriptionKey: @"A client token must be used for ThreeDSecure integrations."}];
  127. } else if (threeDSecureRequest.nonce == nil) {
  128. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  129. code:BTThreeDSecureFlowErrorTypeConfiguration
  130. userInfo:@{NSLocalizedDescriptionKey: @"BTThreeDSecureRequest nonce can not be nil."}];
  131. }
  132. if (integrationError != nil) {
  133. completionBlock(nil, integrationError);
  134. return;
  135. }
  136. [threeDSecureRequest prepareLookup:self.apiClient completion:^(NSError * _Nullable error) {
  137. if (error != nil) {
  138. completionBlock(nil, error);
  139. } else {
  140. NSMutableDictionary *requestParameters = [@{} mutableCopy];
  141. if (threeDSecureRequest.dfReferenceID) {
  142. requestParameters[@"dfReferenceId"] = threeDSecureRequest.dfReferenceID;
  143. }
  144. requestParameters[@"nonce"] = threeDSecureRequest.nonce;
  145. requestParameters[@"authorizationFingerprint"] = self.apiClient.clientToken.authorizationFingerprint;
  146. requestParameters[@"braintreeLibraryVersion"] = [NSString stringWithFormat:@"iOS-%@", BRAINTREE_VERSION];
  147. NSMutableDictionary *clientMetadata = [@{} mutableCopy];
  148. clientMetadata[@"sdkVersion"] = [NSString stringWithFormat:@"iOS/%@", BRAINTREE_VERSION];
  149. clientMetadata[@"requestedThreeDSecureVersion"] = @"2";
  150. requestParameters[@"clientMetadata"] = clientMetadata;
  151. NSError *jsonError;
  152. NSData *jsonData = [NSJSONSerialization dataWithJSONObject:requestParameters options:0 error:&jsonError];
  153. if (!jsonData) {
  154. completionBlock(nil, jsonError);
  155. } else {
  156. NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
  157. completionBlock(jsonString, nil);
  158. }
  159. }
  160. }];
  161. }
  162. - (void)initializeChallengeWithLookupResponse:(NSString *)lookupResponse
  163. request:(BTPaymentFlowRequest<BTPaymentFlowRequestDelegate> *)request
  164. completion:(void (^)(BTPaymentFlowResult * _Nullable, NSError * _Nullable))completionBlock {
  165. [self setupPaymentFlow:request completion:completionBlock];
  166. BTJSON *jsonResponse = [[BTJSON alloc] initWithData:[lookupResponse dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:NO]];
  167. BTThreeDSecureResult *lookupResult = [[BTThreeDSecureResult alloc] initWithJSON:jsonResponse];
  168. BTThreeDSecureRequest *threeDSecureRequest = (BTThreeDSecureRequest *)request;
  169. threeDSecureRequest.paymentFlowDriverDelegate = self;
  170. [self.apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration * _Nullable configuration, NSError * _Nullable configurationError) {
  171. if (configurationError) {
  172. [threeDSecureRequest.paymentFlowDriverDelegate onPaymentComplete:nil error:configurationError];
  173. return;
  174. }
  175. [threeDSecureRequest processLookupResult:lookupResult configuration:configuration];
  176. }];
  177. }
  178. @end