From 4b7d744d0f9463128a0e7f51c8fbdc699b07ef43 Mon Sep 17 00:00:00 2001 From: Virginia Cook Date: Wed, 6 Dec 2023 14:54:02 -0500 Subject: [PATCH] wip on allowing confirm from presenting view controller before processing payment --- .../Source/PaymentSheet/PaymentSheet.swift | 23 +++- .../PaymentSheetConfiguration.swift | 2 + .../PaymentSheetViewController.swift | 125 +++++++++++------- 3 files changed, 99 insertions(+), 51 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheet.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheet.swift index f2ae000d12e..438c4823978 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheet.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheet.swift @@ -155,7 +155,8 @@ public class PaymentSheet { savedPaymentMethods: savedPaymentMethods, configuration: self.configuration, isApplePayEnabled: isApplePayEnabled, - isLinkEnabled: isLinkEnabled, + isLinkEnabled: isLinkEnabled, + isConfirmed: self.configuration.delegate == nil, delegate: self ) @@ -201,6 +202,15 @@ public class PaymentSheet { } } } + + public func confirmPayment() { + let psvc = self.findPaymentSheetViewController() + psvc?.confirmPayment() + } + + public func presentError(_ error: Error) { + // VBC TODO + } /// Deletes all persisted authentication state associated with a customer. /// @@ -324,6 +334,11 @@ extension PaymentSheet: PaymentSheetViewControllerDelegate { intent: paymentSheetViewController.intent ) } + + func paymentSheetViewControllerDidTapBuy(_ paymentSheetViewController: PaymentSheetViewController) { + self.configuration.delegate?.paymentSheetDidTapBuy(self) + } + } extension PaymentSheet: LoadingViewControllerDelegate { @@ -387,3 +402,9 @@ private extension PaymentSheet { } } + +// MARK: - PaymentSheetDelegate + +public protocol PaymentSheetDelegate: AnyObject { + func paymentSheetDidTapBuy(_ paymentSheet: PaymentSheet) +} diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift index 36017a416a1..e2304adf579 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift @@ -192,6 +192,8 @@ extension PaymentSheet { /// - Note: If you omit payment methods from this list, they’ll be automatically ordered by Stripe after the ones you provide. Invalid payment methods are ignored. @_spi(ExternalPaymentMethodsPrivateBeta) public var paymentMethodOrder: [String]? + + public weak var delegate: PaymentSheetDelegate? } /// Configuration related to the Stripe Customer diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift index 605c0aa502b..90610338447 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift @@ -29,6 +29,9 @@ protocol PaymentSheetViewControllerDelegate: AnyObject { func paymentSheetViewControllerDidSelectPayWithLink( _ paymentSheetViewController: PaymentSheetViewController ) + func paymentSheetViewControllerDidTapBuy( + _ paymentSheetViewController: PaymentSheetViewController + ) } /// For internal SDK use only @@ -44,6 +47,10 @@ class PaymentSheetViewController: UIViewController { let isWalletEnabled = true let shouldShowWalletHeader = true + + var isConfirmed: Bool + + var paymentOption: PaymentOption? // MARK: - Writable Properties weak var delegate: PaymentSheetViewControllerDelegate? @@ -168,6 +175,7 @@ class PaymentSheetViewController: UIViewController { configuration: PaymentSheet.Configuration, isApplePayEnabled: Bool, isLinkEnabled: Bool, + isConfirmed: Bool, delegate: PaymentSheetViewControllerDelegate ) { self.intent = intent @@ -175,6 +183,7 @@ class PaymentSheetViewController: UIViewController { self.configuration = configuration self.isApplePayEnabled = isApplePayEnabled self.isLinkEnabled = isLinkEnabled + self.isConfirmed = isConfirmed self.delegate = delegate if savedPaymentMethods.isEmpty { @@ -430,6 +439,19 @@ class PaymentSheetViewController: UIViewController { pay(with: selectedPaymentOption) } } + + func confirmPayment() { + guard let paymentOption = paymentOption else { + // VBC TODO: error handling if somehow we got to this point and there's no payment option + return + } + isConfirmed = true + pay(with: paymentOption) + } + + func presentError() { + // VBC TODO: error handling from external sources + } func pay(with paymentOption: PaymentOption) { view.endEditing(true) @@ -437,61 +459,64 @@ class PaymentSheetViewController: UIViewController { // Clear any errors error = nil updateUI() - - // Confirm the payment with the payment option - let startTime = NSDate.timeIntervalSinceReferenceDate - self.delegate?.paymentSheetViewControllerShouldConfirm(self, with: paymentOption) { result, deferredIntentConfirmationType in - let elapsedTime = NSDate.timeIntervalSinceReferenceDate - startTime - DispatchQueue.main.asyncAfter( - deadline: .now() + max(PaymentSheetUI.minimumFlightTime - elapsedTime, 0) - ) { - STPAnalyticsClient.sharedClient.logPaymentSheetPayment( - isCustom: false, - paymentMethod: paymentOption.analyticsValue, - result: result, - linkEnabled: self.intent.supportsLink, - activeLinkSession: LinkAccountContext.shared.account?.sessionState == .verified, - linkSessionType: self.intent.linkPopupWebviewOption, - currency: self.intent.currency, - intentConfig: self.intent.intentConfig, - deferredIntentConfirmationType: deferredIntentConfirmationType, - paymentMethodTypeAnalyticsValue: paymentOption.paymentMethodTypeAnalyticsValue, - error: result.error - ) - - self.isPaymentInFlight = false - switch result { - case .canceled: - // Do nothing, keep customer on payment sheet - self.updateUI() - case .failed(let error): - #if !STP_BUILD_FOR_VISION - UINotificationFeedbackGenerator().notificationOccurred(.error) - #endif - // Update state - self.error = error - // Handle error - if PaymentSheetError.isUnrecoverable(error: error) { - self.delegate?.paymentSheetViewControllerDidFinish(self, result: result) - } - self.updateUI() - UIAccessibility.post(notification: .layoutChanged, argument: self.errorLabel) - case .completed: - // We're done! - let delay: TimeInterval = + if isConfirmed { + // Confirm the payment with the payment option + let startTime = NSDate.timeIntervalSinceReferenceDate + self.delegate?.paymentSheetViewControllerShouldConfirm(self, with: paymentOption) { result, deferredIntentConfirmationType in + let elapsedTime = NSDate.timeIntervalSinceReferenceDate - startTime + DispatchQueue.main.asyncAfter( + deadline: .now() + max(PaymentSheetUI.minimumFlightTime - elapsedTime, 0) + ) { + STPAnalyticsClient.sharedClient.logPaymentSheetPayment( + isCustom: false, + paymentMethod: paymentOption.analyticsValue, + result: result, + linkEnabled: self.intent.supportsLink, + activeLinkSession: LinkAccountContext.shared.account?.sessionState == .verified, + linkSessionType: self.intent.linkPopupWebviewOption, + currency: self.intent.currency, + intentConfig: self.intent.intentConfig, + deferredIntentConfirmationType: deferredIntentConfirmationType, + paymentMethodTypeAnalyticsValue: paymentOption.paymentMethodTypeAnalyticsValue, + error: result.error + ) + + self.isPaymentInFlight = false + switch result { + case .canceled: + // Do nothing, keep customer on payment sheet + self.updateUI() + case .failed(let error): +#if !STP_BUILD_FOR_VISION + UINotificationFeedbackGenerator().notificationOccurred(.error) +#endif + // Update state + self.error = error + // Handle error + if PaymentSheetError.isUnrecoverable(error: error) { + self.delegate?.paymentSheetViewControllerDidFinish(self, result: result) + } + self.updateUI() + UIAccessibility.post(notification: .layoutChanged, argument: self.errorLabel) + case .completed: + // We're done! + let delay: TimeInterval = self.presentedViewController?.isBeingDismissed == true ? 1 : 0 - // Hack: PaymentHandler calls the completion block while SafariVC is still being dismissed - "wait" until it's finished before updating UI - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - #if !STP_BUILD_FOR_VISION - UINotificationFeedbackGenerator().notificationOccurred(.success) - #endif - self.buyButton.update(state: .succeeded, animated: true) { - // Wait a bit before closing the sheet - self.delegate?.paymentSheetViewControllerDidFinish(self, result: .completed) + // Hack: PaymentHandler calls the completion block while SafariVC is still being dismissed - "wait" until it's finished before updating UI + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { +#if !STP_BUILD_FOR_VISION + UINotificationFeedbackGenerator().notificationOccurred(.success) +#endif + self.buyButton.update(state: .succeeded, animated: true) { + // Wait a bit before closing the sheet + self.delegate?.paymentSheetViewControllerDidFinish(self, result: .completed) + } } } } } + } else { + // VBC TODO: call delegate } } }