Skip to content

Commit

Permalink
[Fixed] PaymentSheet 3DS2 cancel crash (#840)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
yuki-stripe authored Mar 14, 2022
1 parent 2050bda commit 33c5a46
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 24 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.

Expand Down
11 changes: 9 additions & 2 deletions Stripe/BottomSheetViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -300,6 +307,6 @@ extension BottomSheetViewController: BottomSheet3DS2ViewControllerDelegate {
func bottomSheet3DS2ViewControllerDidCancel(
_ bottomSheet3DS2ViewController: BottomSheet3DS2ViewController
) {
STPPaymentHandler.shared().cancel3DS2ChallengeFlow()
didCancelNative3DS2()
}
}
2 changes: 1 addition & 1 deletion Stripe/PaymentSheet+API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
{
Expand Down
29 changes: 22 additions & 7 deletions Stripe/PaymentSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
13 changes: 10 additions & 3 deletions Stripe/PaymentSheetFlowController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
24 changes: 20 additions & 4 deletions Stripe/STPPaymentHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -578,6 +586,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate {
}
}

@available(iOSApplicationExtension, unavailable)
@available(macCatalystApplicationExtension, unavailable)
func _handleNextAction(
forPayment paymentIntent: STPPaymentIntent,
with authenticationContext: STPAuthenticationContext,
Expand Down Expand Up @@ -612,6 +622,8 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate {
}
}

@available(iOSApplicationExtension, unavailable)
@available(macCatalystApplicationExtension, unavailable)
func _handleNextAction(
for setupIntent: STPSetupIntent,
with authenticationContext: STPAuthenticationContext,
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -1278,13 +1292,17 @@ 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)
}

/// 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 {
Expand Down Expand Up @@ -1670,8 +1688,6 @@ public class STPPaymentHandler: NSObject, SFSafariViewControllerDelegate {
}
}

@available(iOSApplicationExtension, unavailable)
@available(macCatalystApplicationExtension, unavailable)
extension STPPaymentHandler {
// MARK: - STPChallengeStatusReceiver
/// :nodoc:
Expand Down
6 changes: 0 additions & 6 deletions Stripe/STPPaymentHandlerActionParams.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -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
Expand Down Expand Up @@ -102,8 +98,6 @@ internal class STPPaymentHandlerPaymentIntentActionParams: NSObject, STPPaymentH
}
}

@available(iOSApplicationExtension, unavailable)
@available(macCatalystApplicationExtension, unavailable)
internal class STPPaymentHandlerSetupIntentActionParams: NSObject, STPPaymentHandlerActionParams {
private var serviceInitialized = false

Expand Down

0 comments on commit 33c5a46

Please sign in to comment.