Restrict manual confirmation usage to FlowController (#2633)
* Restrict manual confirmation usage to FlowController * Add comment
This commit is contained in:
parent
53f4a18511
commit
b0a9cad965
|
@ -14,7 +14,9 @@ final class PaymentSheetDeferredValidatorTests: XCTestCase {
|
|||
func testMismatchedIntentAndIntentConfiguration() throws {
|
||||
let pi = STPFixtures.makePaymentIntent()
|
||||
let intentConfig_si = PaymentSheet.IntentConfiguration(mode: .setup(currency: "USD"), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi, intentConfiguration: intentConfig_si)) { error in
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig_si,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. You returned a PaymentIntent client secret but used a PaymentSheet.IntentConfiguration in setup mode.")
|
||||
}
|
||||
let si = STPFixtures.makeSetupIntent()
|
||||
|
@ -27,7 +29,9 @@ final class PaymentSheetDeferredValidatorTests: XCTestCase {
|
|||
func testPaymentIntentMismatchedCurrency() throws {
|
||||
let pi = STPFixtures.makePaymentIntent(amount: 100, currency: "GBP")
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .payment(amount: 100, currency: "USD"), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi, intentConfiguration: intentConfig)) { error in
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. Your PaymentIntent currency (GBP) does not match the PaymentSheet.IntentConfiguration currency (USD).")
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +39,9 @@ final class PaymentSheetDeferredValidatorTests: XCTestCase {
|
|||
func testPaymentIntentMismatchedSetupFutureUsage() throws {
|
||||
let pi = STPFixtures.makePaymentIntent(amount: 100, currency: "USD", setupFutureUsage: .offSession)
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .payment(amount: 100, currency: "USD"), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi, intentConfiguration: intentConfig)) { error in
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. Your PaymentIntent setupFutureUsage (offSession) does not match the PaymentSheet.IntentConfiguration setupFutureUsage (nil).")
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +49,9 @@ final class PaymentSheetDeferredValidatorTests: XCTestCase {
|
|||
func testPaymentIntentMismatchedAmount() throws {
|
||||
let pi = STPFixtures.makePaymentIntent(amount: 1000, currency: "USD")
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .payment(amount: 100, currency: "USD"), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi, intentConfiguration: intentConfig)) { error in
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. Your PaymentIntent amount (1000) does not match the PaymentSheet.IntentConfiguration amount (100).")
|
||||
}
|
||||
}
|
||||
|
@ -51,11 +59,23 @@ final class PaymentSheetDeferredValidatorTests: XCTestCase {
|
|||
func testPaymentIntentMismatchedCaptureMethod() throws {
|
||||
let pi = STPFixtures.makePaymentIntent(amount: 100, currency: "USD", captureMethod: "manual")
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .payment(amount: 100, currency: "USD", captureMethod: .automatic), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi, intentConfiguration: intentConfig)) { error in
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. Your PaymentIntent captureMethod (manual) does not match the PaymentSheet.IntentConfiguration amount (automatic).")
|
||||
}
|
||||
}
|
||||
|
||||
func testPaymentIntentNotFlowControllerManualConfirmationMethod() throws {
|
||||
let pi = STPFixtures.makePaymentIntent(amount: 1000, currency: "USD", confirmationMethod: "manual")
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .payment(amount: 1000, currency: "USD"), confirmHandler: confirmHandler)
|
||||
XCTAssertThrowsError(try PaymentSheetDeferredValidator.validate(paymentIntent: pi,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: false)) { error in
|
||||
XCTAssertEqual("\(error)", "An error occured in PaymentSheet. Your PaymentIntent confirmationMethod (manual) can only be used with PaymentSheet.FlowController.")
|
||||
}
|
||||
}
|
||||
|
||||
func testSetupIntentMismatchedUsage() throws {
|
||||
let si = STPFixtures.makeSetupIntent(usage: "on_session")
|
||||
let intentConfig = PaymentSheet.IntentConfiguration(mode: .setup(currency: "USD", setupFutureUsage: .offSession), confirmHandler: confirmHandler)
|
||||
|
|
|
@ -717,6 +717,7 @@ extension STPFixtures {
|
|||
setupFutureUsage: STPPaymentIntentSetupFutureUsage? = nil,
|
||||
paymentMethodOptions: STPPaymentMethodOptions? = nil,
|
||||
captureMethod: String = "automatic",
|
||||
confirmationMethod: String = "automatic",
|
||||
shippingProvided: Bool = false
|
||||
) -> STPPaymentIntent {
|
||||
var json = STPTestUtils.jsonNamed(STPTestJSONPaymentIntent)!
|
||||
|
@ -726,6 +727,7 @@ extension STPFixtures {
|
|||
json["amount"] = amount
|
||||
json["currency"] = currency
|
||||
json["capture_method"] = captureMethod
|
||||
json["confirmation_method"] = confirmationMethod
|
||||
if let paymentMethodTypes = paymentMethodTypes {
|
||||
json["payment_method_types"] = paymentMethodTypes.map {
|
||||
STPPaymentMethod.string(from: $0)
|
||||
|
|
|
@ -163,7 +163,8 @@ import UIKit
|
|||
authenticationContext: AuthenticationContext(presentingViewController: presentingViewController, appearance: .default),
|
||||
intent: intent,
|
||||
paymentOption: paymentOption,
|
||||
paymentHandler: STPPaymentHandler(apiClient: configuration.apiClient)
|
||||
paymentHandler: STPPaymentHandler(apiClient: configuration.apiClient),
|
||||
isFlowController: false
|
||||
) { result in
|
||||
switch result {
|
||||
case .completed:
|
||||
|
|
|
@ -80,6 +80,7 @@ extension PayWithLinkController: PayWithLinkViewControllerDelegate {
|
|||
intent: intent,
|
||||
paymentOption: paymentOption,
|
||||
paymentHandler: paymentHandler,
|
||||
isFlowController: false,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ extension PaymentSheet {
|
|||
intent: Intent,
|
||||
paymentOption: PaymentOption,
|
||||
paymentHandler: STPPaymentHandler,
|
||||
isFlowController: Bool = false,
|
||||
paymentMethodID: String? = nil,
|
||||
completion: @escaping (PaymentSheetResult) -> Void
|
||||
) {
|
||||
|
@ -129,6 +130,7 @@ extension PaymentSheet {
|
|||
intentConfig: intentConfig,
|
||||
authenticationContext: authenticationContext,
|
||||
paymentHandler: paymentHandler,
|
||||
isFlowController: isFlowController,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
@ -170,6 +172,7 @@ extension PaymentSheet {
|
|||
intentConfig: intentConfig,
|
||||
authenticationContext: authenticationContext,
|
||||
paymentHandler: paymentHandler,
|
||||
isFlowController: isFlowController,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
@ -206,6 +209,7 @@ extension PaymentSheet {
|
|||
intentConfig: intentConfig,
|
||||
authenticationContext: authenticationContext,
|
||||
paymentHandler: paymentHandler,
|
||||
isFlowController: isFlowController,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ extension PaymentSheet {
|
|||
intentConfig: PaymentSheet.IntentConfiguration,
|
||||
authenticationContext: STPAuthenticationContext,
|
||||
paymentHandler: STPPaymentHandler,
|
||||
isFlowController: Bool,
|
||||
completion: @escaping (PaymentSheetResult) -> Void
|
||||
) {
|
||||
// Hack: Add deferred to analytics product usage as a hack to get it into the payment_user_agent string in the request to create a PaymentMethod
|
||||
|
@ -51,7 +52,9 @@ extension PaymentSheet {
|
|||
switch intentConfig.mode {
|
||||
case .payment:
|
||||
let paymentIntent = try await configuration.apiClient.retrievePaymentIntent(clientSecret: clientSecret, expand: ["payment_method"])
|
||||
try PaymentSheetDeferredValidator.validate(paymentIntent: paymentIntent, intentConfiguration: intentConfig)
|
||||
try PaymentSheetDeferredValidator.validate(paymentIntent: paymentIntent,
|
||||
intentConfiguration: intentConfig,
|
||||
isFlowController: isFlowController)
|
||||
// Check if it needs confirmation
|
||||
if [STPPaymentIntentStatus.requiresPaymentMethod, STPPaymentIntentStatus.requiresConfirmation].contains(paymentIntent.status) {
|
||||
// 4a. Client-side confirmation
|
||||
|
|
|
@ -284,7 +284,8 @@ extension PaymentSheet: PaymentSheetViewControllerDelegate {
|
|||
authenticationContext: self.bottomSheetViewController,
|
||||
intent: paymentSheetViewController.intent,
|
||||
paymentOption: paymentOption,
|
||||
paymentHandler: self.paymentHandler)
|
||||
paymentHandler: self.paymentHandler,
|
||||
isFlowController: false)
|
||||
{ result in
|
||||
if case let .failed(error) = result {
|
||||
self.mostRecentError = error
|
||||
|
@ -370,7 +371,8 @@ extension PaymentSheet: PayWithLinkViewControllerDelegate {
|
|||
authenticationContext: self.bottomSheetViewController,
|
||||
intent: intent,
|
||||
paymentOption: paymentOption,
|
||||
paymentHandler: self.paymentHandler)
|
||||
paymentHandler: self.paymentHandler,
|
||||
isFlowController: false)
|
||||
{ result in
|
||||
if case let .failed(error) = result {
|
||||
self.mostRecentError = error
|
||||
|
|
|
@ -9,7 +9,9 @@ import Foundation
|
|||
import StripePayments
|
||||
|
||||
struct PaymentSheetDeferredValidator {
|
||||
static func validate(paymentIntent: STPPaymentIntent, intentConfiguration: PaymentSheet.IntentConfiguration) throws {
|
||||
static func validate(paymentIntent: STPPaymentIntent,
|
||||
intentConfiguration: PaymentSheet.IntentConfiguration,
|
||||
isFlowController: Bool) throws {
|
||||
guard case let .payment(amount, currency, setupFutureUsage, captureMethod) = intentConfiguration.mode else {
|
||||
throw PaymentSheetError.unknown(debugDescription: "You returned a PaymentIntent client secret but used a PaymentSheet.IntentConfiguration in setup mode.")
|
||||
}
|
||||
|
@ -25,9 +27,18 @@ struct PaymentSheetDeferredValidator {
|
|||
guard paymentIntent.captureMethod == captureMethod else {
|
||||
throw PaymentSheetError.unknown(debugDescription: "Your PaymentIntent captureMethod (\(paymentIntent.captureMethod)) does not match the PaymentSheet.IntentConfiguration amount (\(captureMethod)).")
|
||||
}
|
||||
|
||||
/*
|
||||
Manual confirmation is only available using FlowController because merchants own the final step of confirmation.
|
||||
Showing a successful payment in the complete flow may be misleading when merchants still need to do a final confirmation which could fail e.g., bad network
|
||||
*/
|
||||
if !isFlowController && paymentIntent.confirmationMethod == .manual {
|
||||
throw PaymentSheetError.unknown(debugDescription: "Your PaymentIntent confirmationMethod (\(paymentIntent.confirmationMethod)) can only be used with PaymentSheet.FlowController.")
|
||||
}
|
||||
}
|
||||
|
||||
static func validate(setupIntent: STPSetupIntent, intentConfiguration: PaymentSheet.IntentConfiguration) throws {
|
||||
static func validate(setupIntent: STPSetupIntent,
|
||||
intentConfiguration: PaymentSheet.IntentConfiguration) throws {
|
||||
guard case let .setup(_, setupFutureUsage) = intentConfiguration.mode else {
|
||||
throw PaymentSheetError.unknown(debugDescription: "You returned a SetupIntent client secret but used a PaymentSheet.IntentConfiguration in payment mode.")
|
||||
}
|
||||
|
|
|
@ -310,7 +310,8 @@ extension PaymentSheet {
|
|||
authenticationContext: authenticationContext,
|
||||
intent: intent,
|
||||
paymentOption: paymentOption,
|
||||
paymentHandler: paymentHandler
|
||||
paymentHandler: paymentHandler,
|
||||
isFlowController: true
|
||||
) { [intent, configuration] result in
|
||||
STPAnalyticsClient.sharedClient.logPaymentSheetPayment(
|
||||
isCustom: true,
|
||||
|
|
Loading…
Reference in New Issue