BTThreeDSecureRequest.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. #import "BTThreeDSecureRequest.h"
  2. #if __has_include("BTLogger_Internal.h")
  3. #import "BTLogger_Internal.h"
  4. #else
  5. #import <BraintreeCore/BTLogger_Internal.h>
  6. #endif
  7. #if __has_include("BTAPIClient_Internal.h")
  8. #import "BTAPIClient_Internal.h"
  9. #else
  10. #import <BraintreeCore/BTAPIClient_Internal.h>
  11. #endif
  12. #import "BTPaymentFlowDriver_Internal.h"
  13. #import "BTThreeDSecureRequest.h"
  14. #import "Braintree-Version.h"
  15. #import <SafariServices/SafariServices.h>
  16. #import "BTThreeDSecureResult.h"
  17. #import "BTThreeDSecureLookup.h"
  18. #import "BTPaymentFlowDriver+ThreeDSecure_Internal.h"
  19. #import "BTThreeDSecureRequest_Internal.h"
  20. #import "BTThreeDSecurePostalAddress_Internal.h"
  21. #import "BTThreeDSecureAdditionalInformation_Internal.h"
  22. #import "BTURLUtils.h"
  23. #import "BTConfiguration+ThreeDSecure.h"
  24. #import "BTThreeDSecureV2Provider.h"
  25. #import "BTThreeDSecureV1BrowserSwitchHelper.h"
  26. @interface BTThreeDSecureRequest () <BTThreeDSecureRequestDelegate>
  27. @property (nonatomic, strong) BTThreeDSecureV2Provider *threeDSecureV2Provider;
  28. @end
  29. @implementation BTThreeDSecureRequest
  30. - (instancetype)init {
  31. self = [super init];
  32. if (self) {
  33. _versionRequested = BTThreeDSecureVersion1;
  34. }
  35. return self;
  36. }
  37. - (void)handleRequest:(BTPaymentFlowRequest *)request
  38. client:(BTAPIClient *)apiClient
  39. paymentDriverDelegate:(id<BTPaymentFlowDriverDelegate>)delegate {
  40. self.paymentFlowDriverDelegate = delegate;
  41. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.initialized"];
  42. [apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration * _Nullable configuration, NSError * _Nullable configurationError) {
  43. if (configurationError) {
  44. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:configurationError];
  45. return;
  46. }
  47. NSError *integrationError;
  48. if (self.versionRequested == BTThreeDSecureVersion2 && !configuration.cardinalAuthenticationJWT) {
  49. [[BTLogger sharedLogger] critical:@"BTThreeDSecureRequest versionRequested is 2, but merchant account is not setup properly."];
  50. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  51. code:BTThreeDSecureFlowErrorTypeConfiguration
  52. userInfo:@{NSLocalizedDescriptionKey: @"BTThreeDSecureRequest versionRequested is 2, but merchant account is not setup properly."}];
  53. }
  54. if (!self.amount || [self.amount isEqualToNumber:NSDecimalNumber.notANumber]) {
  55. [[BTLogger sharedLogger] critical:@"BTThreeDSecureRequest amount can not be nil or NaN."];
  56. integrationError = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  57. code:BTThreeDSecureFlowErrorTypeConfiguration
  58. userInfo:@{NSLocalizedDescriptionKey: @"BTThreeDSecureRequest amount can not be nil or NaN."}];
  59. }
  60. if (integrationError != nil) {
  61. [delegate onPaymentComplete:nil error:integrationError];
  62. return;
  63. }
  64. if (configuration.cardinalAuthenticationJWT && self.versionRequested == BTThreeDSecureVersion2) {
  65. [self prepareLookup:apiClient completion:^(NSError * _Nullable error) {
  66. if (error != nil) {
  67. [delegate onPaymentComplete:nil error:error];
  68. } else {
  69. [self startRequest:request configuration:configuration];
  70. }
  71. }];
  72. } else {
  73. [self startRequest:request configuration:configuration];
  74. }
  75. }];
  76. }
  77. - (void)prepareLookup:(BTAPIClient *)apiClient completion:(void (^)(NSError * _Nullable))completionBlock {
  78. [apiClient fetchOrReturnRemoteConfiguration:^(BTConfiguration * _Nullable configuration, NSError * _Nullable configurationError) {
  79. if (configurationError) {
  80. completionBlock(configurationError);
  81. return;
  82. }
  83. if (configuration.cardinalAuthenticationJWT) {
  84. self.threeDSecureV2Provider = [BTThreeDSecureV2Provider initializeProviderWithConfiguration:configuration
  85. apiClient:apiClient
  86. request:self
  87. completion:^(NSDictionary *lookupParameters) {
  88. if (lookupParameters[@"dfReferenceId"]) {
  89. self.dfReferenceId = lookupParameters[@"dfReferenceId"];
  90. }
  91. completionBlock(nil);
  92. }];
  93. } else {
  94. NSError *error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  95. code:BTThreeDSecureFlowErrorTypeConfiguration
  96. userInfo:@{NSLocalizedDescriptionKey: @"Merchant is not configured for 3SD 2."}];
  97. completionBlock(error);
  98. }
  99. }];
  100. }
  101. - (void)startRequest:(BTPaymentFlowRequest *)request configuration:(BTConfiguration *)configuration {
  102. BTThreeDSecureRequest *threeDSecureRequest = (BTThreeDSecureRequest *)request;
  103. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  104. BTPaymentFlowDriver *paymentFlowDriver = [[BTPaymentFlowDriver alloc] initWithAPIClient:apiClient];
  105. if (threeDSecureRequest.versionRequested == BTThreeDSecureVersion2) {
  106. if (threeDSecureRequest.threeDSecureRequestDelegate == nil) {
  107. NSError *error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  108. code:BTThreeDSecureFlowErrorTypeConfiguration
  109. userInfo:@{NSLocalizedDescriptionKey: @"Configuration Error: threeDSecureRequestDelegate can not be nil when versionRequested is 2."}];
  110. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  111. return;
  112. }
  113. }
  114. if (threeDSecureRequest.versionRequested == BTThreeDSecureVersion1 && threeDSecureRequest.threeDSecureRequestDelegate == nil) {
  115. threeDSecureRequest.threeDSecureRequestDelegate = self;
  116. }
  117. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.started"];
  118. [paymentFlowDriver performThreeDSecureLookup:threeDSecureRequest
  119. completion:^(BTThreeDSecureLookup *lookupResult, NSError *error) {
  120. dispatch_async(dispatch_get_main_queue(), ^{
  121. if (error) {
  122. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  123. [self.paymentFlowDriverDelegate onPaymentWithURL:nil error:error];
  124. return;
  125. }
  126. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.3ds-version.%@", lookupResult.threeDSecureVersion]];
  127. [self.threeDSecureRequestDelegate onLookupComplete:threeDSecureRequest result:lookupResult next:^{
  128. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.challenge-presented.%@", [self stringForBool:lookupResult.requiresUserAuthentication]]];
  129. [self processLookupResult:lookupResult configuration:configuration];
  130. }];
  131. });
  132. }];
  133. }
  134. - (void)processLookupResult:(BTThreeDSecureLookup *)lookupResult configuration:(BTConfiguration *)configuration {
  135. if (!lookupResult.requiresUserAuthentication) {
  136. [self.paymentFlowDriverDelegate onPaymentComplete:lookupResult.threeDSecureResult error:nil];
  137. return;
  138. }
  139. if (lookupResult.isThreeDSecureVersion2) {
  140. [self performV2Authentication:lookupResult];
  141. } else {
  142. NSURL *browserSwitchURL = [BTThreeDSecureV1BrowserSwitchHelper urlWithScheme:self.paymentFlowDriverDelegate.returnURLScheme
  143. assetsURL:[configuration.json[@"assetsUrl"] asString]
  144. threeDSecureRequest:self
  145. threeDSecureLookup:lookupResult];
  146. [self.paymentFlowDriverDelegate onPaymentWithURL:browserSwitchURL error:nil];
  147. }
  148. }
  149. - (void)performV2Authentication:(BTThreeDSecureLookup *)lookupResult {
  150. typeof(self) __weak weakSelf = self;
  151. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  152. [self.threeDSecureV2Provider processLookupResult:lookupResult
  153. success:^(BTThreeDSecureResult *result) {
  154. [weakSelf logThreeDSecureCompletedAnalyticsForResult:result withAPIClient:apiClient];
  155. [weakSelf.paymentFlowDriverDelegate onPaymentComplete:result error:nil];
  156. } failure:^(NSError *error) {
  157. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  158. [weakSelf.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  159. }];
  160. }
  161. - (void)handleOpenURL:(NSURL *)url {
  162. NSString *jsonAuthResponse = [BTURLUtils queryParametersForURL:url][@"auth_response"];
  163. if (!jsonAuthResponse || jsonAuthResponse.length == 0) {
  164. [self.paymentFlowDriverDelegate.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.missing-auth-response"]];
  165. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  166. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  167. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response missing from URL."}]];
  168. return;
  169. }
  170. NSError *jsonError;
  171. NSData *jsonData = [NSJSONSerialization JSONObjectWithData:[jsonAuthResponse dataUsingEncoding:NSUTF8StringEncoding] options:0 error:&jsonError];
  172. if (!jsonData) {
  173. [self.paymentFlowDriverDelegate.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.invalid-auth-response"]];
  174. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  175. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  176. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response JSON parsing error."}]];
  177. return;
  178. }
  179. BTJSON *authBody = [[BTJSON alloc] initWithValue:jsonData];
  180. if (!authBody.isObject) {
  181. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:[NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  182. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  183. userInfo:@{NSLocalizedDescriptionKey: @"Auth Response is not a valid BTJSON object."}]];
  184. return;
  185. }
  186. BTAPIClient *apiClient = [self.paymentFlowDriverDelegate apiClient];
  187. BTThreeDSecureResult *result = [[BTThreeDSecureResult alloc] initWithJSON:authBody];
  188. #pragma clang diagnostic push
  189. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  190. // NEXT_MAJOR_VERSION we've deprecated `errorMessage` and `success` for public use, but we will continue to use them internally here. Rename to `isSuccess` for parity across SDKs.
  191. if ((self.versionRequested == BTThreeDSecureVersion1 && !result.success) || !result.tokenizedCard) {
  192. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.failed"];
  193. NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:1];
  194. if (result.errorMessage) {
  195. userInfo[NSLocalizedDescriptionKey] = result.errorMessage;
  196. }
  197. #pragma clang diagnostic pop
  198. NSError *error = [NSError errorWithDomain:BTThreeDSecureFlowErrorDomain
  199. code:BTThreeDSecureFlowErrorTypeFailedAuthentication
  200. userInfo:userInfo];
  201. [self.paymentFlowDriverDelegate onPaymentComplete:nil error:error];
  202. return;
  203. }
  204. [self logThreeDSecureCompletedAnalyticsForResult:result withAPIClient:apiClient];
  205. [self.paymentFlowDriverDelegate onPaymentComplete:result error:nil];
  206. }
  207. - (void)logThreeDSecureCompletedAnalyticsForResult:(BTThreeDSecureResult *)result withAPIClient:(BTAPIClient *)apiClient {
  208. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.liability-shift-possible.%@", [self stringForBool:result.tokenizedCard.threeDSecureInfo.liabilityShiftPossible]]];
  209. [apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.three-d-secure.verification-flow.liability-shifted.%@", [self stringForBool:result.tokenizedCard.threeDSecureInfo.liabilityShifted]]];
  210. [apiClient sendAnalyticsEvent:@"ios.three-d-secure.verification-flow.completed"];
  211. }
  212. - (BOOL)canHandleAppSwitchReturnURL:(NSURL *)url sourceApplication:(__unused NSString *)sourceApplication {
  213. return [url.host isEqualToString:@"x-callback-url"] && [url.path hasPrefix:@"/braintree/threedsecure"];
  214. }
  215. - (NSString *)paymentFlowName {
  216. return @"three-d-secure";
  217. }
  218. - (NSString *)stringForBool:(BOOL)boolean {
  219. if (boolean) {
  220. return @"true";
  221. }
  222. else {
  223. return @"false";
  224. }
  225. }
  226. - (void)onLookupComplete:(__unused BTThreeDSecureRequest *)request result:(__unused BTThreeDSecureLookup *)result next:(void (^)(void))next {
  227. next();
  228. }
  229. @end