Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show checkbox for sepa based payment methods to set allow_redisplay #4503

Merged
merged 16 commits into from
Feb 11, 2025
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,8 @@ class PaymentSheetStandardLPMUITwoTests: PaymentSheetStandardLPMUICase {
settings.customerMode = .new
settings.applePayEnabled = .off // disable Apple Pay
settings.mode = .setup
settings.customerKeyType = .legacy // TODO: Change to customerSessions when adding save checkbox for SEPA settings.allowsDelayedPMs = .on
settings.customerKeyType = .customerSession
settings.allowsDelayedPMs = .on
loadPlayground(app, settings)

let paymentMethodButton = app.buttons["Payment method"]
Expand All @@ -562,17 +563,7 @@ class PaymentSheetStandardLPMUITwoTests: PaymentSheetStandardLPMUICase {
// Save SEPA
app.buttons["+ Add"].waitForExistenceAndTap()
tapPaymentMethod("SEPA Debit")

app.textFields["Full name"].tap()
app.typeText("John Doe" + XCUIKeyboardKey.return.rawValue)
app.typeText("[email protected]" + XCUIKeyboardKey.return.rawValue)
app.typeText("AT611904300234573201" + XCUIKeyboardKey.return.rawValue)
app.textFields["Address line 1"].tap()
app.typeText("510 Townsend St" + XCUIKeyboardKey.return.rawValue)
app.typeText("Floor 3" + XCUIKeyboardKey.return.rawValue)
app.typeText("San Francisco" + XCUIKeyboardKey.return.rawValue)
app.textFields["ZIP"].tap()
app.typeText("94102" + XCUIKeyboardKey.return.rawValue)
try! fillSepaData(app, iban: "AT611904300234573201", tapCheckboxWithText: "Save this account for future Example, Inc. payments")
app.buttons["Continue"].tap()
app.buttons["Confirm"].waitForExistenceAndTap()
XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10.0))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase {
settings.mode = .setup
settings.customerMode = .new
settings.currency = .eur
settings.customerKeyType = .legacy // TODO: Change to customerSessions when adding save checkbox for SEPA
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also see a TODO like this in PaymentSheetStandardLPMUIOneTests.testSEPADebitPaymentMethod_PaymentSheet. Should we update that one too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YES! thank you!!

settings.customerKeyType = .customerSession
settings.uiStyle = .flowController
settings.layout = .vertical
loadPlayground(app, settings)
Expand Down Expand Up @@ -72,7 +72,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase {
XCTAssertEqual(app.textFields["Card number"].value as? String, "1, Your card number is invalid.")
app.textFields["Card number"].clearText()
// Finish the card payment
try! fillCardData(app, cardNumber: "4242424242424242")
try! fillCardData(app, cardNumber: "4242424242424242", tapCheckboxWithText: "Save payment details to Example, Inc. for future purchases")
continueButton.tap()
XCTAssertEqual(paymentMethodButton.label, "•••• 4242, card, 12345, US")
app.buttons["Confirm"].tap()
Expand All @@ -89,10 +89,10 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase {

// Add a SEPA Debit PM
app.buttons["SEPA Debit"].tap()
try! fillSepaData(app)
try! fillSepaData(app, tapCheckboxWithText: "Save this account for future Example, Inc. payments")
continueButton.tap()
XCTAssertEqual(paymentMethodButton.label, "SEPA Debit, sepa_debit, 123 Main, San Francisco, CA, 94016, US")
app.buttons["Confirm"].tap()
XCTAssertEqual(paymentMethodButton.label, "SEPA Debit, sepa_debit, John Doe, [email protected], 123 Main, San Francisco, CA, 94016, US")
app.buttons["Confirm"].waitForExistenceAndTap(timeout: 3.0)
XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10))
XCTAssertEqual(
analyticsLog.map({ $0[string: "event"]! }).filter({ $0.starts(with: "mc") }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ extension XCTestCase {
app.tapCoordinate(at: .init(x: 150, y: 150))
}
func fillSepaData(_ app: XCUIApplication,
iban: String = "DE89370400440532013000",
tapCheckboxWithText checkboxText: String? = nil,
container: XCUIElement? = nil) throws {
let context = container ?? app
let nameField = context.textFields["Full name"]
Expand All @@ -261,7 +263,7 @@ extension XCTestCase {

let ibanField = context.textFields["IBAN"]
ibanField.forceTapWhenHittableInTestCase(self)
app.typeText("DE89370400440532013000")
app.typeText(iban)

let addressLine1 = context.textFields["Address line 1"]
addressLine1.forceTapWhenHittableInTestCase(self)
Expand All @@ -279,6 +281,13 @@ extension XCTestCase {

app.typeText("94016")
context.buttons["Done"].tap()

if let checkboxText {
let saveThisAccountToggle = app.switches[checkboxText]
XCTAssertFalse(saveThisAccountToggle.isSelected)
saveThisAccountToggle.tap()
XCTAssertTrue(saveThisAccountToggle.isSelected)
}
}

func skipLinkSignup(_ app: XCUIApplication) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,13 @@ extension String.Localized {
)
}

static var save_this_account_for_future_payments: String {
STPLocalizedString(
"Save this account for future %@ payments",
"Prompt next to checkbox to save bank account."
)
}

static var by_providing_your_card_information_text: String {
STPLocalizedString(
"By providing your card information, you allow %@ to charge your card for future payments in accordance with their terms.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ extension PaymentSheet {
return "link_card_brand"
}
}

/// Returns the Stripe API identifier used for incentives for this payment method type, which can be different from `identifier`.
var incentiveIdentifier: String? {
switch self {
Expand Down Expand Up @@ -457,7 +457,7 @@ extension PaymentSheet {
}
// This payment method and its requirements are hardcoded on the client
switch paymentMethodType {
case .card, .USBankAccount:
case .card, .USBankAccount, .iDEAL, .sofort, .bancontact, .SEPADebit:
return true
default:
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ class PaymentSheetFormFactory {
return makeiDEAL(spec: spec)
} else if paymentMethod == .sofort {
return makeSofort(spec: spec)
} else if paymentMethod == .SEPADebit {
return makeSepaDebit()
}

// 2. Element-based forms defined in JSON
Expand Down Expand Up @@ -458,7 +460,25 @@ extension PaymentSheetFormFactory {
}
}()
let mandate: Element? = isSettingUp ? makeSepaMandate() : nil // Note: We show a SEPA mandate b/c sofort saves bank details as a SEPA Direct Debit Payment Method
let elements: [Element?] = [contactSection, addressSection, mandate]
let checkboxElement: Element? = makeSepaBasedPMCheckbox()
let elements: [Element?] = [contactSection, addressSection, checkboxElement, mandate]
return FormElement(
autoSectioningElements: elements.compactMap { $0 },
theme: theme
)
}

func makeSepaDebit() -> PaymentMethodElement {
let contactSection: Element? = makeContactInformationSection(
nameRequiredByPaymentMethod: true,
emailRequiredByPaymentMethod: true,
phoneRequiredByPaymentMethod: false
)
let iban: Element = makeIban()
let addressSection: Element? = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: true)
let checkboxElement: Element? = makeSepaBasedPMCheckbox()
let mandate: Element? = makeSepaMandate()
let elements: [Element?] = [contactSection, iban, addressSection, checkboxElement, mandate]
return FormElement(
autoSectioningElements: elements.compactMap { $0 },
theme: theme
Expand All @@ -472,8 +492,9 @@ extension PaymentSheetFormFactory {
phoneRequiredByPaymentMethod: false
)
let addressSection: Element? = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false)
let checkboxElement: Element? = makeSepaBasedPMCheckbox()
let mandate: Element? = isSettingUp ? makeSepaMandate() : nil // Note: We show a SEPA mandate b/c iDEAL saves bank details as a SEPA Direct Debit Payment Method
let elements: [Element?] = [contactSection, addressSection, mandate]
let elements: [Element?] = [contactSection, addressSection, checkboxElement, mandate]
return FormElement(
autoSectioningElements: elements.compactMap { $0 },
theme: theme
Expand Down Expand Up @@ -519,7 +540,8 @@ extension PaymentSheetFormFactory {

let addressSection: Element? = makeBillingAddressSectionIfNecessary(requiredByPaymentMethod: false)
let mandate: Element? = isSettingUp ? makeSepaMandate() : nil // Note: We show a SEPA mandate b/c iDEAL saves bank details as a SEPA Direct Debit Payment Method
let elements: [Element?] = [contactSection, bankDropdown, addressSection, mandate]
let checkboxElement = makeSepaBasedPMCheckbox()
let elements: [Element?] = [contactSection, bankDropdown, addressSection, checkboxElement, mandate]
return FormElement(
autoSectioningElements: elements.compactMap { $0 },
theme: theme
Expand All @@ -534,10 +556,7 @@ extension PaymentSheetFormFactory {
}
let saveCheckbox = makeSaveCheckbox(
label: String(
format: STPLocalizedString(
"Save this account for future %@ payments",
"Prompt next to checkbox to save bank account."
),
format: .Localized.save_this_account_for_future_payments,
merchantName
)
) { value in
Expand Down Expand Up @@ -603,6 +622,23 @@ extension PaymentSheetFormFactory {
return FormElement(elements: [contactInfoSection, billingDetails], theme: theme)
}

// Only show checkbox for PI+SFU & Setup Intent
func makeSepaBasedPMCheckbox() -> Element? {
let isSaving = BoolReference()
let saveCheckbox = makeSaveCheckbox(
label: String(
format: .Localized.save_this_account_for_future_payments,
configuration.merchantDisplayName
)
) { value in
isSaving.value = value
}
isSaving.value = shouldDisplaySaveCheckbox && isSettingUp
? configuration.savePaymentMethodOptInBehavior.isSelectedByDefault : isSettingUp

return shouldDisplaySaveCheckbox && isSettingUp ? saveCheckbox : nil
}

func makeCountry(countryCodes: [String]?, apiPath: String? = nil) -> PaymentMethodElement {
let locale = Locale.current
let resolvedCountryCodes = countryCodes ?? addressSpecProvider.countries
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ final class PaymentSheet_LPM_ConfirmFlowTests: STPNetworkStubbingTestCase {
form.getTextFieldElement("City").setText("asdf")
form.getTextFieldElement("ZIP").setText("12345")
XCTAssertNotNil(form.getMandateElement())
XCTAssertEqual(form.getAllUnwrappedSubElements().count, 17)
XCTAssertEqual(form.getAllUnwrappedSubElements().count, 16)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,33 @@ class PaymentSheetSnapshotTests: STPSnapshotTestCase {
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_iDeal_setupIntent_customerSession() {
stubSessions(
fileMock: .elementsSessions_customerSessionsMobilePaymentElement_setupIntent_200,
responseCallback: { data in
return self.updatePaymentMethodDetail(
data: data,
variables: [
"<paymentMethods>": "\"ideal\"",
"<currency>": "\"eur\"",
]
)
}
)
let intentConfig = PaymentSheet.IntentConfiguration(mode: .setup(currency: "eur", setupFutureUsage: .offSession),
paymentMethodTypes: ["ideal"],
confirmHandler: confirmHandler(_:_:_:),
requireCVCRecollection: false)
preparePaymentSheet(
currency: "eur",
override_payment_methods_types: ["ideal"],
automaticPaymentMethods: false,
useLink: false,
intentConfig: intentConfig
)
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}
func testPaymentSheet_LPM_bancontact_only() {
stubSessions(
fileMock: .elementsSessionsPaymentMethod_200,
Expand Down Expand Up @@ -786,6 +812,85 @@ class PaymentSheetSnapshotTests: STPSnapshotTestCase {
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_sofort_setupIntent_customerSession() {
stubSessions(
fileMock: .elementsSessions_customerSessionsMobilePaymentElement_setupIntent_200,
responseCallback: { data in
return self.updatePaymentMethodDetail(
data: data,
variables: [
"<paymentMethods>": "\"sofort\"",
"<currency>": "\"eur\"",
]
)
}
)
let intentConfig = PaymentSheet.IntentConfiguration(mode: .setup(currency: "eur", setupFutureUsage: .offSession),
paymentMethodTypes: ["sofort"],
confirmHandler: confirmHandler(_:_:_:),
requireCVCRecollection: false)
preparePaymentSheet(
currency: "eur",
override_payment_methods_types: ["sofort"],
automaticPaymentMethods: false,
useLink: false,
intentConfig: intentConfig
)
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_sepaDebit_paymentIntent_customerSession() {
stubSessions(
fileMock: .elementsSessions_customerSessionsMobilePaymentElement_200,
responseCallback: { data in
return self.updatePaymentMethodDetail(
data: data,
variables: [
"<paymentMethods>": "\"sepa_debit\"",
"<currency>": "\"eur\"",
]
)
}
)
preparePaymentSheet(
currency: "eur",
override_payment_methods_types: ["sepa_debit"],
automaticPaymentMethods: false,
useLink: false
)
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_sepaDebit_setupIntent_customerSession() {
stubSessions(
fileMock: .elementsSessions_customerSessionsMobilePaymentElement_setupIntent_200,
responseCallback: { data in
return self.updatePaymentMethodDetail(
data: data,
variables: [
"<paymentMethods>": "\"sepa_debit\"",
"<currency>": "\"eur\"",
]
)
}
)
let intentConfig = PaymentSheet.IntentConfiguration(mode: .setup(currency: "eur", setupFutureUsage: .offSession),
paymentMethodTypes: ["sepa_debit"],
confirmHandler: confirmHandler(_:_:_:),
requireCVCRecollection: false)
preparePaymentSheet(
currency: "eur",
override_payment_methods_types: ["sepa_debit"],
automaticPaymentMethods: false,
useLink: false,
intentConfig: intentConfig
)
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_sepaDebit_only() {
stubSessions(
fileMock: .elementsSessionsPaymentMethod_200,
Expand Down Expand Up @@ -941,7 +1046,7 @@ class PaymentSheetSnapshotTests: STPSnapshotTestCase {
presentPaymentSheet(darkMode: false)
verify(paymentSheet.bottomSheetViewController.view!)
}

func testPaymentSheet_LPM_InstantDebits_only_promotion() {
UserDefaults.standard.setValue(true, forKey: "FINANCIAL_CONNECTIONS_INSTANT_DEBITS_INCENTIVES")
stubSessions(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public class ClassForBundle {}
case saved_payment_methods_withSepa_200 = "MockFiles/saved_payment_methods_withSepa_200"

case elementsSessionsPaymentMethod_200 = "MockFiles/elements_sessions_paymentMethod_200"
case elementsSessions_customerSessionsMobilePaymentElement_200 = "MockFiles/elements_sessions_customerSessionMobilePaymentElement_200"
case elementsSessions_customerSessionsMobilePaymentElement_setupIntent_200 = "MockFiles/elements_sessions_customerSessionMobilePaymentElement_setupIntent_200"
case elementsSessions_customerSessionsCustomerSheet_200 = "MockFiles/elements_sessions_customerSessionCustomerSheet_200"
case elementsSessions_customerSessionsCustomerSheetWithSavedPM_200 = "MockFiles/elements_sessions_customerSessionCustomerSheetWithSavedPM_200"

Expand Down
Loading
Loading