From 33c5a4666c8ded268caf6881d0950a5d091e60a5 Mon Sep 17 00:00:00 2001 From: Yuki Date: Mon, 14 Mar 2022 11:40:40 -0700 Subject: [PATCH] [Fixed] PaymentSheet 3DS2 cancel crash (#840) * Make @available(iOSApplicationExtension, unavailable) more specific in STPPaymentHandler * Make STPPaymentHandler an instance var of PaymentSheet * Fix cancel not being called on same STPPaymentHandler instance as confirm * Update CHANGELOG * wip test * Revert "wip test" This reverts commit e6cc7a13bb940d31910d269796d29c84812727d9. --- CHANGELOG.md | 5 +++- Stripe/BottomSheetViewController.swift | 11 ++++++-- Stripe/PaymentSheet+API.swift | 2 +- Stripe/PaymentSheet.swift | 29 ++++++++++++++++------ Stripe/PaymentSheetFlowController.swift | 13 +++++++--- Stripe/STPPaymentHandler.swift | 24 +++++++++++++++--- Stripe/STPPaymentHandlerActionParams.swift | 6 ----- 7 files changed, 66 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 531c484b504..474f641d263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ ## x.x.x 2022-x-x -* [Fixed] - Fixed potential crash when using PaymentSheet custom flow with SwiftUI + +### PaymentSheet +* [Fixed] Fixed potential crash when using PaymentSheet custom flow with SwiftUI. +* [Fixed] Fixed being unable to cancel native 3DS2 in PaymentSheet. * [Fixed] The payment method icons will now use the correct colors when PaymentSheet is configured with `alwaysLight` or `alwaysDark`. * [Fixed] A race condition when setting the `primaryButtonColor` on `PaymentSheet.Configuration`. diff --git a/Stripe/BottomSheetViewController.swift b/Stripe/BottomSheetViewController.swift index 89d98d47bbd..6c356688ad7 100644 --- a/Stripe/BottomSheetViewController.swift +++ b/Stripe/BottomSheetViewController.swift @@ -89,11 +89,18 @@ class BottomSheetViewController: UIViewController, PanModalPresentable { } var linkPaymentDetails: (PaymentSheetLinkAccount, ConsumerPaymentDetails)? = nil + let didCancelNative3DS2: () -> () - required init(contentViewController: BottomSheetContentViewController, appearance: PaymentSheet.Appearance, isTestMode: Bool) { + required init( + contentViewController: BottomSheetContentViewController, + appearance: PaymentSheet.Appearance, + isTestMode: Bool, + didCancelNative3DS2: @escaping () -> () + ) { self.contentViewController = contentViewController self.appearance = appearance self.isTestMode = isTestMode + self.didCancelNative3DS2 = didCancelNative3DS2 super.init(nibName: nil, bundle: nil) @@ -300,6 +307,6 @@ extension BottomSheetViewController: BottomSheet3DS2ViewControllerDelegate { func bottomSheet3DS2ViewControllerDidCancel( _ bottomSheet3DS2ViewController: BottomSheet3DS2ViewController ) { - STPPaymentHandler.shared().cancel3DS2ChallengeFlow() + didCancelNative3DS2() } } diff --git a/Stripe/PaymentSheet+API.swift b/Stripe/PaymentSheet+API.swift index 3f5528eed43..680e9e1221f 100644 --- a/Stripe/PaymentSheet+API.swift +++ b/Stripe/PaymentSheet+API.swift @@ -20,9 +20,9 @@ extension PaymentSheet { authenticationContext: STPAuthenticationContext, intent: Intent, paymentOption: PaymentOption, + paymentHandler: STPPaymentHandler, completion: @escaping (PaymentSheetResult) -> Void ) { - let paymentHandler = STPPaymentHandler(apiClient: configuration.apiClient) // Translates a STPPaymentHandler result to a PaymentResult let paymentHandlerCompletion: (STPPaymentHandlerActionStatus, NSObject?, NSError?) -> Void = { diff --git a/Stripe/PaymentSheet.swift b/Stripe/PaymentSheet.swift index 68fbe19b749..2de85be4031 100644 --- a/Stripe/PaymentSheet.swift +++ b/Stripe/PaymentSheet.swift @@ -197,14 +197,27 @@ public class PaymentSheet { /// A user-supplied completion block. Nil until `present` is called. var completion: ((PaymentSheetResult) -> ())? + /// The STPPaymentHandler instance + lazy var paymentHandler: STPPaymentHandler = { STPPaymentHandler(apiClient: configuration.apiClient) }() + /// The parent view controller to present lazy var bottomSheetViewController: BottomSheetViewController = { - let loadingViewController = LoadingViewController(delegate: self, - appearance: configuration.appearance, - isTestMode:configuration.apiClient.isTestmode) + let isTestMode = configuration.apiClient.isTestmode + let loadingViewController = LoadingViewController( + delegate: self, + appearance: configuration.appearance, + isTestMode: isTestMode + ) - let vc = BottomSheetViewController(contentViewController: loadingViewController, - appearance: configuration.appearance, isTestMode: configuration.apiClient.isTestmode) + let vc = BottomSheetViewController( + contentViewController: loadingViewController, + appearance: configuration.appearance, + isTestMode: isTestMode, + didCancelNative3DS2: { [weak self] in + self?.paymentHandler.cancel3DS2ChallengeFlow() + } + ) + if #available(iOS 13.0, *) { configuration.style.configure(vc) } @@ -228,7 +241,8 @@ extension PaymentSheet: PaymentSheetViewControllerDelegate { configuration: self.configuration, authenticationContext: self.bottomSheetViewController, intent: paymentSheetViewController.intent, - paymentOption: paymentOption) + paymentOption: paymentOption, + paymentHandler: self.paymentHandler) { result in if case let .failed(error) = result { self.mostRecentError = error @@ -326,7 +340,8 @@ extension PaymentSheet: PayWithLinkViewControllerDelegate { configuration: self.configuration, authenticationContext: self.bottomSheetViewController, intent: intent, - paymentOption: paymentOption) + paymentOption: paymentOption, + paymentHandler: self.paymentHandler) { result in if case let .failed(error) = result { self.mostRecentError = error diff --git a/Stripe/PaymentSheetFlowController.swift b/Stripe/PaymentSheetFlowController.swift index 92e6e1bc002..85ffb451ef3 100644 --- a/Stripe/PaymentSheetFlowController.swift +++ b/Stripe/PaymentSheetFlowController.swift @@ -85,6 +85,7 @@ extension PaymentSheet { private var intent: Intent private let savedPaymentMethods: [STPPaymentMethod] + lazy var paymentHandler: STPPaymentHandler = { STPPaymentHandler(apiClient: configuration.apiClient) }() private var linkAccount: PaymentSheetLinkAccount? { didSet { paymentOptionsViewController.linkAccount = linkAccount @@ -220,8 +221,13 @@ extension PaymentSheet { let presentPaymentOptionsVC = { [self] (linkAccount: PaymentSheetLinkAccount?, justVerifiedLinkOTP: Bool) in // Set the PaymentSheetViewController as the content of our bottom sheet - let bottomSheetVC = BottomSheetViewController(contentViewController: paymentOptionsViewController, appearance: configuration.appearance, isTestMode: configuration.apiClient.isTestmode) - + let bottomSheetVC = BottomSheetViewController( + contentViewController: paymentOptionsViewController, + appearance: configuration.appearance, + isTestMode: configuration.apiClient.isTestmode, + didCancelNative3DS2: { [weak self] in + self?.paymentHandler.cancel3DS2ChallengeFlow() + }) // Workaround to silence a warning in the Catalyst target #if targetEnvironment(macCatalyst) self.configuration.style.configure(bottomSheetVC) @@ -329,7 +335,8 @@ extension PaymentSheet { configuration: configuration, authenticationContext: authenticationContext, intent: intent, - paymentOption: paymentOption + paymentOption: paymentOption, + paymentHandler: paymentHandler ) { result in STPAnalyticsClient.sharedClient.logPaymentSheetPayment( isCustom: true, diff --git a/Stripe/STPPaymentHandler.swift b/Stripe/STPPaymentHandler.swift index 047556629c5..ecaa6b9e3ec 100644 --- a/Stripe/STPPaymentHandler.swift +++ b/Stripe/STPPaymentHandler.swift @@ -87,8 +87,6 @@ public typealias STPPaymentHandlerActionSetupIntentCompletionBlock = ( /// `STPPaymentHandler` is a utility class that confirms PaymentIntents/SetupIntents and handles any authentication required, such as 3DS1/3DS2 for Strong Customer Authentication. /// It can present authentication UI on top of your app or redirect users out of your app (to e.g. their banking app). /// - seealso: https://stripe.com/docs/mobile/ios/authentication -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// The error domain for errors in `STPPaymentHandler`. @@ -167,6 +165,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// - paymentParams: The params used to confirm the PaymentIntent. Note that this method overrides the value of `paymentParams.useStripeSDK` to `@YES`. /// - authenticationContext: The authentication context used to authenticate the payment. /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the PaymentIntent status is not necessarily STPPaymentIntentStatusSucceeded (e.g. some bank payment methods take days before the PaymentIntent succeeds). + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) @objc(confirmPayment:withAuthenticationContext:completion:) public func confirmPayment( _ paymentParams: STPPaymentIntentParams, @@ -250,6 +250,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { *, deprecated, message: "Use confirmPayment(_:with:completion:) instead", renamed: "confirmPayment(_:with:completion:)" ) + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) public func confirmPayment( withParams: STPPaymentIntentParams, authenticationContext: STPAuthenticationContext, @@ -266,6 +268,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during PaymentIntent confirmation. /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the PaymentIntent status will always be either STPPaymentIntentStatusSucceeded, or STPPaymentIntentStatusRequiresConfirmation, or STPPaymentIntentStatusRequiresCapture if you are using manual capture. In the latter two cases, confirm or capture the PaymentIntent on your backend to complete the payment. @objc(handleNextActionForPayment:withAuthenticationContext:returnURL:completion:) + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) public func handleNextAction( forPayment paymentIntentClientSecret: String, with authenticationContext: STPAuthenticationContext, @@ -356,6 +360,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// - setupIntentConfirmParams: The params used to confirm the SetupIntent. Note that this method overrides the value of `setupIntentConfirmParams.useStripeSDK` to `@YES`. /// - authenticationContext: The authentication context used to authenticate the SetupIntent. /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the SetupIntent status will always be STPSetupIntentStatusSucceeded. + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) @objc(confirmSetupIntent:withAuthenticationContext:completion:) public func confirmSetupIntent( _ setupIntentConfirmParams: STPSetupIntentConfirmParams, @@ -450,6 +456,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// - returnURL: An optional URL to redirect your customer back to after they authenticate or cancel in a webview. This should match the returnURL you specified during SetupIntent confirmation. /// - completion: The completion block. If the status returned is `STPPaymentHandlerActionStatusSucceeded`, the SetupIntent status will always be STPSetupIntentStatusSucceeded. @objc(handleNextActionForSetupIntent:withAuthenticationContext:returnURL:completion:) + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) public func handleNextAction( forSetupIntent setupIntentClientSecret: String, with authenticationContext: STPAuthenticationContext, @@ -578,6 +586,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { } } + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) func _handleNextAction( forPayment paymentIntent: STPPaymentIntent, with authenticationContext: STPAuthenticationContext, @@ -612,6 +622,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { } } + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) func _handleNextAction( for setupIntent: STPSetupIntent, with authenticationContext: STPAuthenticationContext, @@ -797,6 +809,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { return false } + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) func _handleAuthenticationForCurrentAction() { guard let currentAction = currentAction, let authenticationAction = currentAction.nextAction() @@ -1278,6 +1292,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { _retrieveAndCheckIntentForCurrentAction() } + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable) func _handleRedirect(to url: URL, withReturn returnURL: URL?) { _handleRedirect(to: url, fallbackURL: url, return: returnURL) } @@ -1285,6 +1301,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { /// This method: /// 1. Redirects to an app using url /// 2. Open fallbackURL in a webview if 1) fails + @available(iOSApplicationExtension, unavailable) + @available(macCatalystApplicationExtension, unavailable)/// func _handleRedirect(to nativeURL: URL?, fallbackURL: URL?, return returnURL: URL?) { var url = nativeURL guard let currentAction = currentAction else { @@ -1670,8 +1688,6 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate { } } -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) extension STPPaymentHandler { // MARK: - STPChallengeStatusReceiver /// :nodoc: diff --git a/Stripe/STPPaymentHandlerActionParams.swift b/Stripe/STPPaymentHandlerActionParams.swift index cc42610a613..9f0cf93e86f 100644 --- a/Stripe/STPPaymentHandlerActionParams.swift +++ b/Stripe/STPPaymentHandlerActionParams.swift @@ -13,8 +13,6 @@ import Foundation #endif @_spi(STP) import StripeCore -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) internal protocol STPPaymentHandlerActionParams: NSObject { var threeDS2Service: STDSThreeDS2Service? { get } var threeDS2Transaction: STDSTransaction? { get set } @@ -28,8 +26,6 @@ internal protocol STPPaymentHandlerActionParams: NSObject { func complete(with status: STPPaymentHandlerActionStatus, error: NSError?) } -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) internal class STPPaymentHandlerPaymentIntentActionParams: NSObject, STPPaymentHandlerActionParams { private var serviceInitialized = false @@ -102,8 +98,6 @@ internal class STPPaymentHandlerPaymentIntentActionParams: NSObject, STPPaymentH } } -@available(iOSApplicationExtension, unavailable) -@available(macCatalystApplicationExtension, unavailable) internal class STPPaymentHandlerSetupIntentActionParams: NSObject, STPPaymentHandlerActionParams { private var serviceInitialized = false