From 0d3994973483424dc361e5fcc34dfe1f0f041609 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 10 Mar 2025 09:01:54 -0700 Subject: [PATCH 1/5] auto set default for first --- .../PaymentSheetFormFactory+Card.swift | 17 +++++---- .../PaymentSheetFormFactory.swift | 35 ++++++++++++------- .../USBankAccountPaymentMethodElement.swift | 4 +-- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift index 2166b11e30a..cae085a0d22 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift @@ -13,20 +13,23 @@ import StripePayments import UIKit extension PaymentSheetFormFactory { - func makeCard(cardBrandChoiceEligible: Bool = false, showSetAsDefaultCheckbox: Bool) -> PaymentMethodElement { + func makeCard(cardBrandChoiceEligible: Bool = false) -> PaymentMethodElement { let showLinkInlineSignup = showLinkInlineCardSignup - var defaultCheckbox: PaymentMethodElementWrapper? - if showSetAsDefaultCheckbox { - defaultCheckbox = makeDefaultCheckbox() - } + let defaultCheckbox: Element? = { + guard setAsDefaultPMEnabled else { + return nil + } + let defaultCheckbox = makeDefaultCheckbox() + return shouldDisplayDefaultCheckbox ? defaultCheckbox : SectionElement.HiddenElement(defaultCheckbox) + }() let saveCheckbox = makeSaveCheckbox( label: String.Localized.save_payment_details_for_future_$merchant_payments( merchantDisplayName: configuration.merchantDisplayName ) ) { selected in - defaultCheckbox?.element.checkboxButton.isHidden = !selected + defaultCheckbox?.view.isHidden = !selected } - defaultCheckbox?.element.checkboxButton.isHidden = !saveCheckbox.element.isSelected + defaultCheckbox?.view.isHidden = !saveCheckbox.element.isSelected // Make section titled "Contact Information" w/ phone and email if merchant requires it. let optionalPhoneAndEmailInformationSection: SectionElement? = { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift index 0aee7ab704a..6da86614a96 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift @@ -36,7 +36,8 @@ class PaymentSheetFormFactory { let countryCode: String? let cardBrandChoiceEligible: Bool let savePaymentMethodConsentBehavior: SavePaymentMethodConsentBehavior - let showSetAsDefaultCheckbox: Bool + let setAsDefaultPMEnabled: Bool + let isFirstSavedPaymentMethod: Bool let analyticsHelper: PaymentSheetAnalyticsHelper? let paymentMethodIncentive: PaymentMethodIncentive? @@ -55,6 +56,10 @@ class PaymentSheetFormFactory { } } + var shouldDisplayDefaultCheckbox: Bool { + return setAsDefaultPMEnabled && !isFirstSavedPaymentMethod + } + var theme: ElementsAppearance { return configuration.appearance.asElementsTheme } @@ -108,7 +113,8 @@ class PaymentSheetFormFactory { isSettingUp: intent.isSettingUp, countryCode: elementsSession.countryCode(overrideCountry: configuration.overrideCountry), savePaymentMethodConsentBehavior: elementsSession.savePaymentMethodConsentBehavior, - showSetAsDefaultCheckbox: elementsSession.paymentMethodSetAsDefaultForPaymentSheet, + setAsDefaultPMEnabled: elementsSession.paymentMethodSetAsDefaultForPaymentSheet, + isFirstSavedPaymentMethod: elementsSession.customer?.paymentMethods.isEmpty ?? true, analyticsHelper: analyticsHelper, paymentMethodIncentive: elementsSession.incentive) } @@ -126,7 +132,8 @@ class PaymentSheetFormFactory { isSettingUp: Bool, countryCode: String?, savePaymentMethodConsentBehavior: SavePaymentMethodConsentBehavior, - showSetAsDefaultCheckbox: Bool = false, + setAsDefaultPMEnabled: Bool = false, + isFirstSavedPaymentMethod: Bool = false, analyticsHelper: PaymentSheetAnalyticsHelper?, paymentMethodIncentive: PaymentMethodIncentive? ) { @@ -147,7 +154,8 @@ class PaymentSheetFormFactory { self.countryCode = countryCode self.cardBrandChoiceEligible = cardBrandChoiceEligible self.savePaymentMethodConsentBehavior = savePaymentMethodConsentBehavior - self.showSetAsDefaultCheckbox = showSetAsDefaultCheckbox + self.setAsDefaultPMEnabled = setAsDefaultPMEnabled + self.isFirstSavedPaymentMethod = isFirstSavedPaymentMethod self.analyticsHelper = analyticsHelper self.paymentMethodIncentive = paymentMethodIncentive } @@ -164,9 +172,9 @@ class PaymentSheetFormFactory { // We have two ways to create the form for a payment method // 1. Custom, one-off forms if paymentMethod == .card { - return makeCard(cardBrandChoiceEligible: cardBrandChoiceEligible, showSetAsDefaultCheckbox: showSetAsDefaultCheckbox) + return makeCard(cardBrandChoiceEligible: cardBrandChoiceEligible) } else if paymentMethod == .USBankAccount { - return makeUSBankAccount(merchantName: configuration.merchantDisplayName, showSetAsDefaultCheckbox: showSetAsDefaultCheckbox) + return makeUSBankAccount(merchantName: configuration.merchantDisplayName) } else if paymentMethod == .UPI { return makeUPI() } else if paymentMethod == .cashApp && isSettingUp { @@ -374,7 +382,7 @@ extension PaymentSheetFormFactory { let element = CheckboxElement( theme: configuration.appearance.asElementsTheme, label: String.Localized.set_as_default_payment_method, - isSelectedByDefault: false, + isSelectedByDefault: isFirstSavedPaymentMethod, didToggle: didToggle ) return PaymentMethodElementWrapper(element) { checkbox, params in @@ -556,12 +564,15 @@ extension PaymentSheetFormFactory { ) } - func makeUSBankAccount(merchantName: String, showSetAsDefaultCheckbox: Bool) -> PaymentMethodElement { + func makeUSBankAccount(merchantName: String) -> PaymentMethodElement { let isSaving = BoolReference() - var defaultCheckbox: PaymentMethodElementWrapper? - if showSetAsDefaultCheckbox { - defaultCheckbox = makeDefaultCheckbox() - } + let defaultCheckbox: Element? = { + guard setAsDefaultPMEnabled else { + return nil + } + let defaultCheckbox = makeDefaultCheckbox() + return shouldDisplayDefaultCheckbox ? defaultCheckbox : SectionElement.HiddenElement(defaultCheckbox) + }() let saveCheckbox = makeSaveCheckbox( label: String( format: .Localized.save_this_account_for_future_payments, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift index 732c1fe33b2..8531fbb9bfb 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift @@ -38,7 +38,7 @@ final class USBankAccountPaymentMethodElement: ContainerElement { private let bankInfoSectionElement: SectionElement private let bankInfoView: BankAccountInfoView private let saveCheckboxElement: PaymentMethodElementWrapper? - private let defaultCheckboxElement: PaymentMethodElement? + private let defaultCheckboxElement: Element? private var savingAccount: BoolReference private let theme: ElementsAppearance @@ -89,7 +89,7 @@ final class USBankAccountPaymentMethodElement: ContainerElement { phoneElement: PaymentMethodElementWrapper?, addressElement: PaymentMethodElementWrapper?, saveCheckboxElement: PaymentMethodElementWrapper?, - defaultCheckboxElement: PaymentMethodElement?, + defaultCheckboxElement: Element?, savingAccount: BoolReference, merchantName: String, initialLinkedBank: FinancialConnectionsLinkedBank?, From 7d9da951cca5bfd744529c6a9ce324fcce376f1c Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 10 Mar 2025 09:02:03 -0700 Subject: [PATCH 2/5] lint --- .../USBankAccount/USBankAccountPaymentMethodElement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift index 8531fbb9bfb..53cd6712aea 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/USBankAccountPaymentMethodElement.swift @@ -132,7 +132,7 @@ final class USBankAccountPaymentMethodElement: ContainerElement { addressElement, bankInfoSectionElement, saveCheckboxElement, - defaultCheckboxElement + defaultCheckboxElement, ] let autoSectioningElements = allElements.compactMap { $0 } self.formElement = FormElement(autoSectioningElements: autoSectioningElements, theme: theme) From 1903bbbd7a78d5121edf2058a470fe044b2ef7ae Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 10 Mar 2025 09:46:00 -0700 Subject: [PATCH 3/5] fix tests --- .../PaymentSheetUITest.swift | 9 ++---- .../PaymentSheetFormFactory.swift | 2 +- ...entMethodViewControllerSnapshotTests.swift | 2 +- .../STPFixtures+PaymentSheet.swift | 32 +++++++++++-------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index f55730b25cd..b4af202655c 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -2816,10 +2816,8 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { var saveThisCardToggle = app.switches["Save payment details to Example, Inc. for future purchases"] saveThisCardToggle.tap() XCTAssertTrue(saveThisCardToggle.isSelected) - // toggle set this card as default - var setDefaultToggle = app.switches["Set as default payment method"] - setDefaultToggle.tap() - XCTAssertTrue(setDefaultToggle.isSelected) + // this card should be automatically set as the default, as there are no other saved pms + XCTAssertFalse(app.switches["Set as default payment method"].waitForExistence(timeout: 3)) // Complete payment app.buttons["Pay $50.99"].waitForExistenceAndTap() @@ -2854,8 +2852,7 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { saveThisCardToggle.tap() XCTAssertTrue(saveThisCardToggle.isSelected) // do not set this card as default - setDefaultToggle = app.switches["Set as default payment method"] - XCTAssertFalse(setDefaultToggle.isSelected) + XCTAssertFalse(app.switches["Set as default payment method"].isSelected) // Complete payment app.buttons["Pay $50.99"].waitForExistenceAndTap() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift index 6da86614a96..91ca90b76ce 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift @@ -133,7 +133,7 @@ class PaymentSheetFormFactory { countryCode: String?, savePaymentMethodConsentBehavior: SavePaymentMethodConsentBehavior, setAsDefaultPMEnabled: Bool = false, - isFirstSavedPaymentMethod: Bool = false, + isFirstSavedPaymentMethod: Bool = true, analyticsHelper: PaymentSheetAnalyticsHelper?, paymentMethodIncentive: PaymentMethodIncentive? ) { diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/AddPaymentMethodViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/AddPaymentMethodViewControllerSnapshotTests.swift index 9cca68d7f96..c0db3004f38 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/AddPaymentMethodViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/AddPaymentMethodViewControllerSnapshotTests.swift @@ -66,7 +66,7 @@ final class AddPaymentMethodViewControllerSnapshotTests: STPSnapshotTestCase { let sut = AddPaymentMethodViewController( intent: intent, // ...and a "Set as default" checkbox... - elementsSession: ._testValue(intent: intent, allowsSetAsDefaultPM: true), + elementsSession: ._testValue(intent: intent, paymentMethods: [STPPaymentMethod._testCardJSON], allowsSetAsDefaultPM: true), configuration: config, previousCustomerInput: previousCustomerInput, paymentMethodTypes: [.stripe(.payPal), .stripe(.card), .stripe(.cashApp)], diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift index f94f40b2492..789d9f45640 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift @@ -180,6 +180,8 @@ extension STPElementsSession { intent: Intent, linkMode: LinkMode? = nil, linkFundingSources: Set = [], + defaultPaymentMethod: String? = nil, + paymentMethods: [[AnyHashable: Any]]? = nil, allowsSetAsDefaultPM: Bool = false ) -> STPElementsSession { let paymentMethodTypes: [String] = { @@ -211,7 +213,9 @@ extension STPElementsSession { paymentMethodTypes: paymentMethodTypes, customerSessionData: customerSessionData, linkMode: linkMode, - linkFundingSources: linkFundingSources + linkFundingSources: linkFundingSources, + defaultPaymentMethod: defaultPaymentMethod, + paymentMethods: paymentMethods ) } } @@ -245,19 +249,21 @@ extension Intent { } extension STPPaymentMethod { + static let _testCardJSON = [ + "id": "pm_123card", + "type": "card", + "card": [ + "last4": "4242", + "brand": "visa", + "fingerprint": "B8XXs2y2JsVBtB9f", + "networks": ["available": ["visa"]], + "exp_month": "01", + "exp_year": "2040", + ], + ] as [AnyHashable: Any] + static func _testCard() -> STPPaymentMethod { - return STPPaymentMethod.decodedObject(fromAPIResponse: [ - "id": "pm_123card", - "type": "card", - "card": [ - "last4": "4242", - "brand": "visa", - "fingerprint": "B8XXs2y2JsVBtB9f", - "networks": ["available": ["visa"]], - "exp_month": "01", - "exp_year": "2040", - ], - ])! + return STPPaymentMethod.decodedObject(fromAPIResponse: _testCardJSON)! } static func _testCardAmex() -> STPPaymentMethod { From 9be7ed41a9998b840599a726ec6bf3adace54301 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 10 Mar 2025 09:54:27 -0700 Subject: [PATCH 4/5] rename bool --- .../PaymentSheetFormFactory+Card.swift | 2 +- .../PaymentSheetFormFactory/PaymentSheetFormFactory.swift | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift index cae085a0d22..955be1f9860 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory+Card.swift @@ -16,7 +16,7 @@ extension PaymentSheetFormFactory { func makeCard(cardBrandChoiceEligible: Bool = false) -> PaymentMethodElement { let showLinkInlineSignup = showLinkInlineCardSignup let defaultCheckbox: Element? = { - guard setAsDefaultPMEnabled else { + guard allowsSetAsDefaultPM else { return nil } let defaultCheckbox = makeDefaultCheckbox() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift index 91ca90b76ce..b3e5144115f 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift @@ -36,7 +36,7 @@ class PaymentSheetFormFactory { let countryCode: String? let cardBrandChoiceEligible: Bool let savePaymentMethodConsentBehavior: SavePaymentMethodConsentBehavior - let setAsDefaultPMEnabled: Bool + let allowsSetAsDefaultPM: Bool let isFirstSavedPaymentMethod: Bool let analyticsHelper: PaymentSheetAnalyticsHelper? let paymentMethodIncentive: PaymentMethodIncentive? @@ -57,7 +57,7 @@ class PaymentSheetFormFactory { } var shouldDisplayDefaultCheckbox: Bool { - return setAsDefaultPMEnabled && !isFirstSavedPaymentMethod + return allowsSetAsDefaultPM && !isFirstSavedPaymentMethod } var theme: ElementsAppearance { @@ -154,7 +154,7 @@ class PaymentSheetFormFactory { self.countryCode = countryCode self.cardBrandChoiceEligible = cardBrandChoiceEligible self.savePaymentMethodConsentBehavior = savePaymentMethodConsentBehavior - self.setAsDefaultPMEnabled = setAsDefaultPMEnabled + self.allowsSetAsDefaultPM = setAsDefaultPMEnabled self.isFirstSavedPaymentMethod = isFirstSavedPaymentMethod self.analyticsHelper = analyticsHelper self.paymentMethodIncentive = paymentMethodIncentive @@ -567,7 +567,7 @@ extension PaymentSheetFormFactory { func makeUSBankAccount(merchantName: String) -> PaymentMethodElement { let isSaving = BoolReference() let defaultCheckbox: Element? = { - guard setAsDefaultPMEnabled else { + guard allowsSetAsDefaultPM else { return nil } let defaultCheckbox = makeDefaultCheckbox() From 5312961a3e656f344d5cdefa4edb8234b946d3b3 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 10 Mar 2025 10:20:51 -0700 Subject: [PATCH 5/5] rename --- .../PaymentSheetFormFactory/PaymentSheetFormFactory.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift index b3e5144115f..0eb809c7f18 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetFormFactory/PaymentSheetFormFactory.swift @@ -113,7 +113,7 @@ class PaymentSheetFormFactory { isSettingUp: intent.isSettingUp, countryCode: elementsSession.countryCode(overrideCountry: configuration.overrideCountry), savePaymentMethodConsentBehavior: elementsSession.savePaymentMethodConsentBehavior, - setAsDefaultPMEnabled: elementsSession.paymentMethodSetAsDefaultForPaymentSheet, + allowsSetAsDefaultPM: elementsSession.paymentMethodSetAsDefaultForPaymentSheet, isFirstSavedPaymentMethod: elementsSession.customer?.paymentMethods.isEmpty ?? true, analyticsHelper: analyticsHelper, paymentMethodIncentive: elementsSession.incentive) @@ -132,7 +132,7 @@ class PaymentSheetFormFactory { isSettingUp: Bool, countryCode: String?, savePaymentMethodConsentBehavior: SavePaymentMethodConsentBehavior, - setAsDefaultPMEnabled: Bool = false, + allowsSetAsDefaultPM: Bool = false, isFirstSavedPaymentMethod: Bool = true, analyticsHelper: PaymentSheetAnalyticsHelper?, paymentMethodIncentive: PaymentMethodIncentive? @@ -154,7 +154,7 @@ class PaymentSheetFormFactory { self.countryCode = countryCode self.cardBrandChoiceEligible = cardBrandChoiceEligible self.savePaymentMethodConsentBehavior = savePaymentMethodConsentBehavior - self.allowsSetAsDefaultPM = setAsDefaultPMEnabled + self.allowsSetAsDefaultPM = allowsSetAsDefaultPM self.isFirstSavedPaymentMethod = isFirstSavedPaymentMethod self.analyticsHelper = analyticsHelper self.paymentMethodIncentive = paymentMethodIncentive