BTPaymentFlowDriver.m 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. #if __has_include("BTLogger_Internal.h")
  2. #import "BTLogger_Internal.h"
  3. #else
  4. #import <BraintreeCore/BTLogger_Internal.h>
  5. #endif
  6. #if __has_include("BTAPIClient_Internal.h")
  7. #import "BTAPIClient_Internal.h"
  8. #else
  9. #import <BraintreeCore/BTAPIClient_Internal.h>
  10. #endif
  11. #import "BTPaymentFlowDriver_Internal.h"
  12. #import "Braintree-Version.h"
  13. #import <SafariServices/SafariServices.h>
  14. @interface BTPaymentFlowDriver () <SFSafariViewControllerDelegate>
  15. @property (nonatomic, copy) void (^paymentFlowCompletionBlock)(BTPaymentFlowResult *, NSError *);
  16. @property (nonatomic, strong, nullable) SFSafariViewController *safariViewController NS_AVAILABLE_IOS(9_0);
  17. @property (nonatomic, strong, nullable) id<BTPaymentFlowRequestDelegate> paymentFlowRequestDelegate;
  18. @end
  19. NSString * const BTPaymentFlowDriverErrorDomain = @"com.braintreepayments.BTPaymentFlowDriverErrorDomain";
  20. @implementation BTPaymentFlowDriver
  21. static BTPaymentFlowDriver *paymentFlowDriver;
  22. + (void)load {
  23. if (self == [BTPaymentFlowDriver class]) {
  24. [[BTAppSwitch sharedInstance] registerAppSwitchHandler:self];
  25. }
  26. }
  27. - (instancetype)initWithAPIClient:(BTAPIClient *)apiClient {
  28. if (self = [super init]) {
  29. _apiClient = apiClient;
  30. }
  31. return self;
  32. }
  33. - (instancetype)init {
  34. return nil;
  35. }
  36. #pragma mark - Accessors
  37. - (id)application {
  38. if (!_application) {
  39. _application = [UIApplication sharedApplication];
  40. }
  41. return _application;
  42. }
  43. - (NSBundle *)bundle {
  44. if (!_bundle) {
  45. _bundle = [NSBundle mainBundle];
  46. }
  47. return _bundle;
  48. }
  49. - (NSString *)returnURLScheme {
  50. if (!_returnURLScheme) {
  51. _returnURLScheme = [BTAppSwitch sharedInstance].returnURLScheme;
  52. }
  53. return _returnURLScheme;
  54. }
  55. - (void)startPaymentFlow:(BTPaymentFlowRequest<BTPaymentFlowRequestDelegate> *)request completion:(void (^)(BTPaymentFlowResult * _Nullable, NSError * _Nullable))completionBlock {
  56. [self setupPaymentFlow:request completion:completionBlock];
  57. [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.%@.start-payment.selected", [self.paymentFlowRequestDelegate paymentFlowName]]];
  58. [self.paymentFlowRequestDelegate handleRequest:request client:self.apiClient paymentDriverDelegate:self];
  59. }
  60. - (void)setupPaymentFlow:(BTPaymentFlowRequest<BTPaymentFlowRequestDelegate> *)request completion:(void (^)(BTPaymentFlowResult * _Nullable, NSError * _Nullable))completionBlock {
  61. paymentFlowDriver = self;
  62. self.paymentFlowCompletionBlock = completionBlock;
  63. self.paymentFlowRequestDelegate = request;
  64. }
  65. - (void)performSwitchRequest:(NSURL *)appSwitchURL {
  66. [self informDelegateAppContextWillSwitch];
  67. if (@available(iOS 9.0, *)) {
  68. [self informDelegatePresentingViewControllerRequestPresent:appSwitchURL];
  69. } else {
  70. UIApplication *application = [UIApplication sharedApplication];
  71. #pragma clang diagnostic push
  72. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  73. [application openURL:appSwitchURL];
  74. #pragma clang diagnostic pop
  75. }
  76. }
  77. - (void)informDelegatePresentingViewControllerRequestPresent:(NSURL *)appSwitchURL {
  78. if ([self.viewControllerPresentingDelegate respondsToSelector:@selector(paymentDriver:requestsPresentationOfViewController:)]) {
  79. if (@available(iOS 9.0, *)) {
  80. self.safariViewController = [[SFSafariViewController alloc] initWithURL:appSwitchURL];
  81. self.safariViewController.delegate = self;
  82. [self.viewControllerPresentingDelegate paymentDriver:self requestsPresentationOfViewController:self.safariViewController];
  83. }
  84. } else {
  85. [[BTLogger sharedLogger] critical:@"Unable to display View Controller to continue payment flow. BTPaymentFlowDriver needs a viewControllerPresentingDelegate<BTViewControllerPresentingDelegate> to be set."];
  86. }
  87. }
  88. - (void)informDelegatePresentingViewControllerNeedsDismissal {
  89. if (self.viewControllerPresentingDelegate != nil && [self.viewControllerPresentingDelegate respondsToSelector:@selector(paymentDriver:requestsDismissalOfViewController:)]) {
  90. if (@available(iOS 9.0, *)) {
  91. [self.viewControllerPresentingDelegate paymentDriver:self requestsDismissalOfViewController:self.safariViewController];
  92. self.safariViewController = nil;
  93. }
  94. } else {
  95. [[BTLogger sharedLogger] critical:@"Unable to dismiss View Controller to end payment flow. BTPaymentFlowDriver needs a viewControllerPresentingDelegate<BTViewControllerPresentingDelegate> to be set."];
  96. }
  97. }
  98. #pragma mark - App switch
  99. + (void)handleAppSwitchReturnURL:(NSURL *)url {
  100. [paymentFlowDriver handleOpenURL:url];
  101. }
  102. + (BOOL)canHandleAppSwitchReturnURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
  103. return [paymentFlowDriver.paymentFlowRequestDelegate canHandleAppSwitchReturnURL:url sourceApplication:sourceApplication];
  104. }
  105. - (void)handleOpenURL:(NSURL *)url {
  106. [self informDelegateAppContextDidReturn];
  107. [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.%@.webswitch.succeeded", [self.paymentFlowRequestDelegate paymentFlowName]]];
  108. if (@available(iOS 9.0, *)) {
  109. if (self.safariViewController) {
  110. [self informDelegatePresentingViewControllerNeedsDismissal];
  111. }
  112. }
  113. [self.paymentFlowRequestDelegate handleOpenURL:url];
  114. }
  115. - (void)safariViewControllerDidFinish:(__unused SFSafariViewController *)controller API_AVAILABLE(ios(9.0)) {
  116. [self onPaymentCancel];
  117. }
  118. #pragma mark - BTPaymentFlowDriverDelegate protocol
  119. - (void)onPaymentWithURL:(NSURL *)url error:(NSError *)error {
  120. if (error) {
  121. [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.%@.start-payment.failed", [self.paymentFlowRequestDelegate paymentFlowName]]];
  122. [self onPaymentComplete:nil error:error];
  123. return;
  124. }
  125. [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.%@.webswitch.initiate.succeeded", [self.paymentFlowRequestDelegate paymentFlowName]]];
  126. [self performSwitchRequest:url];
  127. }
  128. - (void)onPaymentCancel {
  129. [self.apiClient sendAnalyticsEvent:[NSString stringWithFormat:@"ios.%@.webswitch.canceled", [self.paymentFlowRequestDelegate paymentFlowName]]];
  130. NSError *error = [NSError errorWithDomain:BTPaymentFlowDriverErrorDomain
  131. code:BTPaymentFlowDriverErrorTypeCanceled
  132. userInfo:@{NSLocalizedDescriptionKey: @"Payment flow was canceled by the user."}];
  133. self.paymentFlowCompletionBlock(nil, error);
  134. paymentFlowDriver = nil;
  135. }
  136. - (void)onPaymentComplete:(BTPaymentFlowResult *)result error:(NSError *)error {
  137. self.paymentFlowCompletionBlock(result, error);
  138. paymentFlowDriver = nil;
  139. }
  140. - (void)informDelegateAppContextWillSwitch {
  141. NSNotification *notification = [[NSNotification alloc] initWithName:BTAppContextWillSwitchNotification object:self userInfo:nil];
  142. [[NSNotificationCenter defaultCenter] postNotification:notification];
  143. if ([self.appSwitchDelegate respondsToSelector:@selector(appContextWillSwitch:)]) {
  144. [self.appSwitchDelegate appContextWillSwitch:self];
  145. }
  146. }
  147. - (void)informDelegateAppContextDidReturn {
  148. NSNotification *notification = [[NSNotification alloc] initWithName:BTAppContextDidReturnNotification object:self userInfo:nil];
  149. [[NSNotificationCenter defaultCenter] postNotification:notification];
  150. if ([self.appSwitchDelegate respondsToSelector:@selector(appContextDidReturn:)]) {
  151. [self.appSwitchDelegate appContextDidReturn:self];
  152. }
  153. }
  154. @end