From 1403094744c9996fe7562e32034cb184cc69d396 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Wed, 27 Nov 2024 13:28:18 -0800 Subject: [PATCH 01/26] reading default_payment_method from back end? --- .../CustomerSheetTestPlayground.swift | 3 ++ ...ustomerSheetTestPlaygroundController.swift | 3 +- .../CustomerSheetTestPlaygroundSettings.swift | 10 ++++++- .../PaymentSheetTestPlayground.swift | 3 ++ .../PaymentSheetTestPlaygroundSettings.swift | 10 ++++++- .../PlaygroundController.swift | 4 ++- .../ElementsCustomer.swift | 4 ++- .../STPElementsSession.swift | 1 + .../CustomerPaymentOption.swift | 2 +- ...ymentMethodsCollectionViewController.swift | 4 ++- ...merSavedPaymentMethodsViewController.swift | 6 ++-- .../CustomerSheet/CustomerSheet.swift | 16 ++++++++++- .../CustomerSheetConfiguration.swift | 5 ++++ .../EmbeddedPaymentElement+Internal.swift | 10 +++++++ .../EmbeddedPaymentElementConfiguration.swift | 5 ++++ .../PaymentElementConfiguration.swift | 1 + .../PaymentSheetConfiguration.swift | 5 ++++ .../PaymentSheet/PaymentSheetLoader.swift | 20 +++++++++++-- .../SavedPaymentMethodCollectionView.swift | 4 ++- .../SavedPaymentOptionsViewController.swift | 28 +++++++++++++++---- ...entSheetFlowControllerViewController.swift | 4 ++- .../PaymentSheetVerticalViewController.swift | 9 ++++++ .../PaymentSheetViewController.swift | 4 ++- 23 files changed, 140 insertions(+), 21 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift index 22ea9084dbd..b4e79b4bb17 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift @@ -52,6 +52,9 @@ struct CustomerSheetTestPlayground: View { } SettingPickerView(setting: $playgroundController.settings.paymentMethodRemove) SettingPickerView(setting: $playgroundController.settings.paymentMethodAllowRedisplayFilters) + if playgroundController.settings.alternateUpdatePaymentMethodNavigation == .on { + SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) + } } } } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift index 4a4f638f5ce..ddd3b292d7f 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift @@ -4,7 +4,7 @@ // import Combine -@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) import StripePaymentSheet +@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet import SwiftUI class CustomerSheetTestPlaygroundController: ObservableObject { @@ -148,6 +148,7 @@ class CustomerSheetTestPlaygroundController: ObservableObject { configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on + configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift index 7a06f7174b4..2954c0b455a 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift @@ -148,6 +148,12 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { case off } + enum AllowsSetAsDefaultPM: String, PickerEnum { + static let enumName: String = "allowsSetAsDefaultPM" + case on + case off + } + var customerMode: CustomerMode var customerId: String? var customerKeyType: CustomerKeyType @@ -169,6 +175,7 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { var paymentMethodAllowRedisplayFilters: PaymentMethodAllowRedisplayFilters var cardBrandAcceptance: CardBrandAcceptance var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation + var allowsSetAsDefaultPM: AllowsSetAsDefaultPM static func defaultValues() -> CustomerSheetTestPlaygroundSettings { return CustomerSheetTestPlaygroundSettings(customerMode: .new, @@ -190,7 +197,8 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { paymentMethodRemove: .enabled, paymentMethodAllowRedisplayFilters: .always, cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off) + alternateUpdatePaymentMethodNavigation: .off, + allowsSetAsDefaultPM: .off) } var base64Data: String { diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift index aa3f3c3982c..a9c12497a7e 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift @@ -119,6 +119,9 @@ struct PaymentSheetTestPlayground: View { if playgroundController.settings.paymentMethodRedisplay == .enabled { SettingPickerView(setting: $playgroundController.settings.paymentMethodAllowRedisplayFilters) } + if playgroundController.settings.alternateUpdatePaymentMethodNavigation == .on { + SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) + } } } } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift index 006818d00e3..32395d7e8db 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift @@ -444,6 +444,12 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { case off } + enum AllowsSetAsDefaultPM: String, PickerEnum { + static let enumName: String = "allowsSetAsDefaultPM" + case on + case off + } + var uiStyle: UIStyle var layout: Layout var mode: Mode @@ -490,6 +496,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { var embeddedViewDisplaysMandateText: DisplaysMandateTextEnabled var cardBrandAcceptance: CardBrandAcceptance var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation + var allowsSetAsDefaultPM: AllowsSetAsDefaultPM static func defaultValues() -> PaymentSheetTestPlaygroundSettings { return PaymentSheetTestPlaygroundSettings( @@ -535,7 +542,8 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { formSheetAction: .confirm, embeddedViewDisplaysMandateText: .on, cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off) + alternateUpdatePaymentMethodNavigation: .off, + allowsSetAsDefaultPM: .off) } static let nsUserDefaultsKey = "PaymentSheetTestPlaygroundSettings" diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift index f192f61b022..72e7e56e4fb 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift @@ -14,7 +14,7 @@ import Contacts import PassKit @_spi(STP) import StripeCore @_spi(STP) import StripePayments -@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) import StripePaymentSheet +@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet import SwiftUI import UIKit @@ -185,6 +185,7 @@ class PlaygroundController: ObservableObject { configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on + configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } @@ -273,6 +274,7 @@ class PlaygroundController: ObservableObject { configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on + configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift index 5ff41532bed..eb15b24ea95 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift @@ -36,7 +36,9 @@ struct ElementsCustomer: Equatable, Hashable { } // Optional - let defaultPaymentMethod = response["default_payment_method"] as? String +// let defaultPaymentMethod = response["default_payment_method"] as? String + let defaultPaymentMethod: String? = "pm_1QPqgILu5o3P18ZpQNyB8VrP" + print("getting backend default payment method \(defaultPaymentMethod ?? "nil")") return ElementsCustomer(paymentMethods: paymentMethods, defaultPaymentMethod: defaultPaymentMethod, customerSession: customerSession) } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift index 8d98ef38aaa..ad3a3c0ba86 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift @@ -224,6 +224,7 @@ extension STPElementsSession { var isLinkCardBrand: Bool { linkSettings?.linkMode == .linkCardBrand } + } extension STPElementsSession { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift index 63905b0c4be..5dea071e1c3 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift @@ -60,7 +60,7 @@ public enum CustomerPaymentOption: Equatable { guard let value = UserDefaults.standard.customerToLastSelectedPaymentMethod?[key] else { return nil } - + return CustomerPaymentOption(value: value) } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index a1554fbcac6..e5515627b86 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -83,6 +83,7 @@ class CustomerSavedPaymentMethodsCollectionViewController: UIViewController { let paymentMethodRemove: Bool let isTestMode: Bool let alternateUpdatePaymentMethodNavigation: Bool + let allowsSetAsDefaultPM: Bool } /// Whether or not you can edit save payment methods by removing or updating them. @@ -388,7 +389,8 @@ extension CustomerSavedPaymentMethodsCollectionViewController: UICollectionViewD cell.setViewModel(viewModel.toSavedPaymentOptionsViewControllerSelection(), cbcEligible: cbcEligible, allowsPaymentMethodRemoval: configuration.paymentMethodRemove, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift index 6a070a3d844..c8f839a59f0 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift @@ -104,7 +104,8 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), appearance: configuration.appearance, cbcEligible: cbcEligible, @@ -656,7 +657,8 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), appearance: configuration.appearance, cbcEligible: cbcEligible, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift index 4ece2ba2fc2..2ad965674b0 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift @@ -310,7 +310,21 @@ extension CustomerSheet { switch customerSheetDataSource.dataSource { case .customerSession(let customerSessionAdapter): let (elementsSession, customerSessionClientSecret) = try await customerSessionAdapter.elementsSessionWithCustomerSessionClientSecret() - let selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) + + var selectedPaymentOption: CustomerPaymentOption? + + // read from back end + if configuration.allowsSetAsDefaultPM, + let customer = elementsSession.customer { + let defaultPaymentMethod = customer.paymentMethods.filter { + $0.stripeId == customer.defaultPaymentMethod + }.first + guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) + } switch selectedPaymentOption { case .applePay: diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift index 03508b9be46..ba1b7c15457 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift @@ -86,6 +86,11 @@ extension CustomerSheet { /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false + /// This is an experimental feature that may be removed at any time. + /// If true, users can set a payment method as default. + /// If false (default), users cannot set default payment methods. + @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false + public init () { } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index adac81d1293..ab1244e9e3f 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -37,6 +37,16 @@ extension EmbeddedPaymentElement { isFlatCheckmarkStyle: configuration.appearance.embeddedPaymentElement.row.style == .flatWithCheckmark ) let initialSelection: EmbeddedPaymentMethodsView.Selection? = { + // read from back end + if configuration.allowsSetAsDefaultPM, + let customer = loadResult.elementsSession.customer { + let defaultPaymentMethod = customer.paymentMethods.filter { + $0.stripeId == customer.defaultPaymentMethod + }.first + guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + return .saved(paymentMethod: defaultPaymentMethod) + } + // Select the previous payment option switch previousPaymentOption { case .applePay: diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift index b224a784d1d..e43d2da2b98 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift @@ -136,6 +136,11 @@ extension EmbeddedPaymentElement { /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false + /// This is an experimental feature that may be removed at any time. + /// If true, users can set a payment method as default. + /// If false (default), users cannot set default payment methods. + @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false + /// The view can display payment methods like “Card” that, when tapped, open a form sheet where customers enter their payment method details. The sheet has a button at the bottom. `FormSheetAction` enumerates the actions the button can perform. public enum FormSheetAction { /// The button says “Pay” or “Setup”. When tapped, we confirm the payment or setup in the form sheet. diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift index 09334d57a50..0842e182394 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift @@ -38,6 +38,7 @@ protocol PaymentElementConfiguration: PaymentMethodRequirementProvider { var analyticPayload: [String: Any] { get } var disableWalletPaymentMethodFiltering: Bool { get set } var alternateUpdatePaymentMethodNavigation: Bool { get set } + var allowsSetAsDefaultPM: Bool { get set } var linkPaymentMethodsOnly: Bool { get set } var forceNativeLinkEnabled: Bool { get set } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift index f85122850fb..ac655d97d92 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift @@ -215,6 +215,11 @@ extension PaymentSheet { /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false + + /// This is an experimental feature that may be removed at any time. + /// If true, users can set a payment method as default. + /// If false (default), users cannot set default payment methods. + @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false } /// Defines the layout orientations available for displaying payment methods in PaymentSheet. diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift index b1802dca622..0724bbdc7f3 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift @@ -116,7 +116,9 @@ final class PaymentSheetLoader { savedPaymentMethods: filteredSavedPaymentMethods, customerID: configuration.customer?.id, showApplePay: integrationShape.canDefaultToLinkOrApplePay ? isApplePayEnabled : false, - showLink: integrationShape.canDefaultToLinkOrApplePay ? isLinkEnabled : false + showLink: integrationShape.canDefaultToLinkOrApplePay ? isLinkEnabled : false, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM, + elementsSession: elementsSession ) let paymentMethodTypes = PaymentSheet.PaymentMethodType.filteredPaymentMethodTypes(from: intent, elementsSession: elementsSession, configuration: configuration, logAvailability: true) @@ -316,9 +318,21 @@ final class PaymentSheetLoader { // Move default PM to front if let customerID = configuration.customer?.id { - let defaultPaymentMethod = CustomerPaymentOption.defaultPaymentMethod(for: customerID) + var defaultPaymentMethodOption: CustomerPaymentOption? + // read from back end + if configuration.allowsSetAsDefaultPM, + let customer = elementsSession.customer { + let defaultPaymentMethod = customer.paymentMethods.filter { + $0.stripeId == customer.defaultPaymentMethod + }.first + guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + defaultPaymentMethodOption = .stripeId(defaultPaymentMethod.stripeId) + } + else { + defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) + } if let defaultPMIndex = savedPaymentMethods.firstIndex(where: { - $0.stripeId == defaultPaymentMethod?.value + $0.stripeId == defaultPaymentMethodOption?.value }) { let defaultPM = savedPaymentMethods.remove(at: defaultPMIndex) savedPaymentMethods.insert(defaultPM, at: 0) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 84039502938..927955e3e4d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -113,6 +113,7 @@ extension SavedPaymentMethodCollectionView { var cbcEligible: Bool = false var allowsPaymentMethodRemoval: Bool = true var alternateUpdatePaymentMethodNavigation: Bool = false + var allowsSetAsDefaultPM: Bool = false /// Indicates whether the cell should be editable or just removable. /// If the card is a co-branded card and the merchant is eligible for card brand choice, then @@ -218,7 +219,7 @@ extension SavedPaymentMethodCollectionView { // MARK: - Internal Methods - func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool) { + func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool, allowsSetAsDefaultPM: Bool) { paymentMethodLogo.isHidden = false plus.isHidden = true shadowRoundedRectangle.isHidden = false @@ -226,6 +227,7 @@ extension SavedPaymentMethodCollectionView { self.cbcEligible = cbcEligible self.allowsPaymentMethodRemoval = allowsPaymentMethodRemoval self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation + self.allowsSetAsDefaultPM = allowsSetAsDefaultPM update() } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 25b7fba3a56..d0caf28e85a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -104,6 +104,7 @@ class SavedPaymentOptionsViewController: UIViewController { let allowsRemovalOfLastSavedPaymentMethod: Bool let allowsRemovalOfPaymentMethods: Bool let alternateUpdatePaymentMethodNavigation: Bool + let allowsSetAsDefaultPM: Bool } // MARK: - Internal Properties @@ -217,6 +218,7 @@ class SavedPaymentOptionsViewController: UIViewController { } weak var delegate: SavedPaymentOptionsViewControllerDelegate? var appearance = PaymentSheet.Appearance.default + var elementsSession: STPElementsSession // MARK: - Private Properties private var selectedViewModelIndex: Int? @@ -312,6 +314,7 @@ class SavedPaymentOptionsViewController: UIViewController { paymentSheetConfiguration: PaymentSheet.Configuration, intent: Intent, appearance: PaymentSheet.Appearance, + elementsSession: STPElementsSession, cbcEligible: Bool = false, analyticsHelper: PaymentSheetAnalyticsHelper, delegate: SavedPaymentOptionsViewControllerDelegate? = nil @@ -321,6 +324,7 @@ class SavedPaymentOptionsViewController: UIViewController { self.paymentSheetConfiguration = paymentSheetConfiguration self.intent = intent self.appearance = appearance + self.elementsSession = elementsSession self.cbcEligible = cbcEligible self.delegate = delegate self.analyticsHelper = analyticsHelper @@ -364,7 +368,9 @@ class SavedPaymentOptionsViewController: UIViewController { savedPaymentMethods: savedPaymentMethods, customerID: configuration.customerID, showApplePay: configuration.showApplePay, - showLink: configuration.showLink + showLink: configuration.showLink, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM, + elementsSession: elementsSession ) collectionView.reloadData() @@ -438,9 +444,21 @@ class SavedPaymentOptionsViewController: UIViewController { /// Creates the list of viewmodels to display in the "saved payment methods" carousel e.g. `["+ Add", "Apple Pay", "Link", "Visa 4242"]` /// - Returns defaultSelectedIndex: The index of the view model that is the default e.g. in the above list, if "Visa 4242" is the default, the index is 3. - static func makeViewModels(savedPaymentMethods: [STPPaymentMethod], customerID: String?, showApplePay: Bool, showLink: Bool) -> (defaultSelectedIndex: Int, viewModels: [Selection]) { + static func makeViewModels(savedPaymentMethods: [STPPaymentMethod], customerID: String?, showApplePay: Bool, showLink: Bool, allowsSetAsDefaultPM: Bool, elementsSession: STPElementsSession) -> (defaultSelectedIndex: Int, viewModels: [Selection]) { // Get the default - let defaultPaymentMethod = CustomerPaymentOption.defaultPaymentMethod(for: customerID) + var defaultPaymentMethodOption: CustomerPaymentOption? + // read from back end + if allowsSetAsDefaultPM, + let customer = elementsSession.customer { + let defaultPaymentMethod = customer.paymentMethods.filter { + $0.stripeId == customer.defaultPaymentMethod + }.first + guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) + } // Transform saved PaymentMethods into view models let savedPMViewModels = savedPaymentMethods.compactMap { paymentMethod in @@ -461,7 +479,7 @@ class SavedPaymentOptionsViewController: UIViewController { let firstPaymentMethodIsLink = !showApplePay && showLink let defaultIndex = firstPaymentMethodIsLink ? 2 : 1 - let defaultSelectedIndex = viewModels.firstIndex(where: { $0 == defaultPaymentMethod }) ?? defaultIndex + let defaultSelectedIndex = viewModels.firstIndex(where: { $0 == defaultPaymentMethodOption }) ?? defaultIndex return (defaultSelectedIndex, viewModels) } } @@ -493,7 +511,7 @@ extension SavedPaymentOptionsViewController: UICollectionViewDataSource, UIColle stpAssertionFailure() return UICollectionViewCell() } - cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation) + cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation, allowsSetAsDefaultPM: self.configuration.allowsSetAsDefaultPM) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift index fb93ae948c6..b9a3360f61a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift @@ -207,11 +207,13 @@ class PaymentSheetFlowControllerViewController: UIViewController, FlowController isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, allowsRemovalOfPaymentMethods: elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), paymentSheetConfiguration: configuration, intent: intent, appearance: configuration.appearance, + elementsSession: elementsSession, cbcEligible: elementsSession.isCardBrandChoiceEligible, analyticsHelper: analyticsHelper ) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index f3888aba9cd..e7b5ed4249d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -288,6 +288,15 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo if let selection { return selection } + // read from back end + if configuration.allowsSetAsDefaultPM, + let customer = elementsSession.customer { + let defaultPaymentMethod = customer.paymentMethods.filter { + $0.stripeId == customer.defaultPaymentMethod + }.first + guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + return .saved(paymentMethod: defaultPaymentMethod) + } switch previousPaymentOption { case .applePay: diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift index 55265f254e9..63edd3181a4 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift @@ -175,11 +175,13 @@ class PaymentSheetViewController: UIViewController, PaymentSheetViewControllerPr isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, allowsRemovalOfPaymentMethods: loadResult.elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, + allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), paymentSheetConfiguration: configuration, intent: intent, appearance: configuration.appearance, + elementsSession: elementsSession, cbcEligible: elementsSession.isCardBrandChoiceEligible, analyticsHelper: analyticsHelper ) From 71c5516436ac0742c7dc98b5292bac7dd91ca30e Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Wed, 27 Nov 2024 13:34:21 -0800 Subject: [PATCH 02/26] revert accidental space changes; --- .../API Bindings/v1-elements-sessions/STPElementsSession.swift | 1 - .../PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift index ad3a3c0ba86..8d98ef38aaa 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/STPElementsSession.swift @@ -224,7 +224,6 @@ extension STPElementsSession { var isLinkCardBrand: Bool { linkSettings?.linkMode == .linkCardBrand } - } extension STPElementsSession { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift index 5dea071e1c3..63905b0c4be 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerAdapter/CustomerPaymentOption.swift @@ -60,7 +60,7 @@ public enum CustomerPaymentOption: Equatable { guard let value = UserDefaults.standard.customerToLastSelectedPaymentMethod?[key] else { return nil } - + return CustomerPaymentOption(value: value) } } From e3ecc2c9f89dfd1205364b311cd582b901a7a002 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Wed, 27 Nov 2024 14:13:47 -0800 Subject: [PATCH 03/26] customersheet default from back end --- .../v1-elements-sessions/ElementsCustomer.swift | 6 +++--- .../CustomerSessionAdapter/CustomerSessionAdapter.swift | 8 +++++++- .../Source/PaymentSheet/CustomerSheet/CustomerSheet.swift | 8 ++++++-- .../CustomerSheet/CustomerSheetDataSource.swift | 2 +- .../Embedded/EmbeddedPaymentElement+Internal.swift | 6 ++++-- .../Source/PaymentSheet/PaymentSheetLoader.swift | 8 ++++++-- .../PaymentSheetVerticalViewController.swift | 5 +++-- 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift index eb15b24ea95..a973e4a78a2 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift @@ -36,9 +36,9 @@ struct ElementsCustomer: Equatable, Hashable { } // Optional -// let defaultPaymentMethod = response["default_payment_method"] as? String - let defaultPaymentMethod: String? = "pm_1QPqgILu5o3P18ZpQNyB8VrP" - print("getting backend default payment method \(defaultPaymentMethod ?? "nil")") + // to test default payment methods reading from back end, hard-code a valid default payment method + // later, when API calls to get and update default payment method are available, that will no longer be needed + let defaultPaymentMethod = response["default_payment_method"] as? String return ElementsCustomer(paymentMethods: paymentMethods, defaultPaymentMethod: defaultPaymentMethod, customerSession: customerSession) } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift index 1eb5b5efc03..19951aa58eb 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift @@ -107,7 +107,13 @@ extension CustomerSessionAdapter { return stripePaymentMethodId } - func fetchSelectedPaymentOption(for customerId: String) -> CustomerPaymentOption? { + func fetchSelectedPaymentOption(for customerId: String, elementsSession: STPElementsSession? = nil) -> CustomerPaymentOption? { + if configuration.allowsSetAsDefaultPM, + let elementsSession = elementsSession, + let customer = elementsSession.customer, + let defaultPaymentMethod = customer.defaultPaymentMethod { + return CustomerPaymentOption.stripeId(defaultPaymentMethod) + } return CustomerPaymentOption.defaultPaymentMethod(for: customerId) } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift index 2ad965674b0..e250c16d8c5 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift @@ -319,8 +319,12 @@ extension CustomerSheet { let defaultPaymentMethod = customer.paymentMethods.filter { $0.stripeId == customer.defaultPaymentMethod }.first - guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } - selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + if let defaultPaymentMethod = defaultPaymentMethod { + selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) + } } else { selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift index 2645ac50316..6020bb1bebc 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift @@ -41,8 +41,8 @@ class CustomerSheetDataSource { // Ensure local specs are loaded prior to the ones from elementSession await loadFormSpecs() let customerId = try await customerSessionClientSecret.customerId - let paymentOption = customerSessionAdapter.fetchSelectedPaymentOption(for: customerId) let elementSession = try await elementsSessionResult + let paymentOption = customerSessionAdapter.fetchSelectedPaymentOption(for: customerId, elementsSession: elementSession) // Override with specs from elementSession _ = FormSpecProvider.shared.loadFrom(elementSession.paymentMethodSpecs as Any) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index ab1244e9e3f..de87f802e3f 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -43,8 +43,10 @@ extension EmbeddedPaymentElement { let defaultPaymentMethod = customer.paymentMethods.filter { $0.stripeId == customer.defaultPaymentMethod }.first - guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } - return .saved(paymentMethod: defaultPaymentMethod) + if let defaultPaymentMethod = defaultPaymentMethod { + return .saved(paymentMethod: defaultPaymentMethod) + } + } // Select the previous payment option diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift index 0724bbdc7f3..75ee52b6055 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift @@ -325,8 +325,12 @@ final class PaymentSheetLoader { let defaultPaymentMethod = customer.paymentMethods.filter { $0.stripeId == customer.defaultPaymentMethod }.first - guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } - defaultPaymentMethodOption = .stripeId(defaultPaymentMethod.stripeId) + if let defaultPaymentMethod = defaultPaymentMethod { + defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) + } } else { defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index e7b5ed4249d..1707dcf443b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -294,8 +294,9 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo let defaultPaymentMethod = customer.paymentMethods.filter { $0.stripeId == customer.defaultPaymentMethod }.first - guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } - return .saved(paymentMethod: defaultPaymentMethod) + if let defaultPaymentMethod = defaultPaymentMethod { + return .saved(paymentMethod: defaultPaymentMethod) + } } switch previousPaymentOption { From fb86faa0601dac66eca291f92b082b599e9ca187 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 2 Dec 2024 07:45:02 -0800 Subject: [PATCH 04/26] removed unused value --- ...stomerSavedPaymentMethodsCollectionViewController.swift | 3 +-- .../SavedPaymentMethodCollectionView.swift | 4 +--- .../SavedPaymentOptionsViewController.swift | 7 ++++--- .../SavedPaymentOptionsViewControllerSnapshotTests.swift | 3 ++- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index e5515627b86..8e26c6f108b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -389,8 +389,7 @@ extension CustomerSavedPaymentMethodsCollectionViewController: UICollectionViewD cell.setViewModel(viewModel.toSavedPaymentOptionsViewControllerSelection(), cbcEligible: cbcEligible, allowsPaymentMethodRemoval: configuration.paymentMethodRemove, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, - allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM) + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 927955e3e4d..84039502938 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -113,7 +113,6 @@ extension SavedPaymentMethodCollectionView { var cbcEligible: Bool = false var allowsPaymentMethodRemoval: Bool = true var alternateUpdatePaymentMethodNavigation: Bool = false - var allowsSetAsDefaultPM: Bool = false /// Indicates whether the cell should be editable or just removable. /// If the card is a co-branded card and the merchant is eligible for card brand choice, then @@ -219,7 +218,7 @@ extension SavedPaymentMethodCollectionView { // MARK: - Internal Methods - func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool, allowsSetAsDefaultPM: Bool) { + func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool) { paymentMethodLogo.isHidden = false plus.isHidden = true shadowRoundedRectangle.isHidden = false @@ -227,7 +226,6 @@ extension SavedPaymentMethodCollectionView { self.cbcEligible = cbcEligible self.allowsPaymentMethodRemoval = allowsPaymentMethodRemoval self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation - self.allowsSetAsDefaultPM = allowsSetAsDefaultPM update() } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index d0caf28e85a..6128f782736 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -449,9 +449,10 @@ class SavedPaymentOptionsViewController: UIViewController { var defaultPaymentMethodOption: CustomerPaymentOption? // read from back end if allowsSetAsDefaultPM, - let customer = elementsSession.customer { + let customer = elementsSession.customer, + let customerDefault = customer.defaultPaymentMethod { let defaultPaymentMethod = customer.paymentMethods.filter { - $0.stripeId == customer.defaultPaymentMethod + $0.stripeId == customerDefault }.first guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) @@ -511,7 +512,7 @@ extension SavedPaymentOptionsViewController: UICollectionViewDataSource, UIColle stpAssertionFailure() return UICollectionViewCell() } - cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation, allowsSetAsDefaultPM: self.configuration.allowsSetAsDefaultPM) + cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift index 7e4794aba79..097736d56a3 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift @@ -32,13 +32,14 @@ final class SavedPaymentOptionsViewControllerSnapshotTests: STPSnapshotTestCase STPPaymentMethod._testUSBankAccount(), STPPaymentMethod._testSEPA(), ] - let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true, alternateUpdatePaymentMethodNavigation: false) + let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true, alternateUpdatePaymentMethodNavigation: false, allowsSetAsDefaultPM: false) let intent = Intent.deferredIntent(intentConfig: .init(mode: .payment(amount: 0, currency: "USD", setupFutureUsage: nil, captureMethod: .automatic), confirmHandler: { _, _, _ in })) let sut = SavedPaymentOptionsViewController(savedPaymentMethods: paymentMethods, configuration: config, paymentSheetConfiguration: PaymentSheet.Configuration(), intent: intent, appearance: appearance, + elementsSession: .emptyElementsSession, analyticsHelper: ._testValue()) let testWindow = UIWindow() testWindow.isHidden = false From 9ed5ed2884424ab53decff08c802b5d56bf91702 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 2 Dec 2024 08:07:06 -0800 Subject: [PATCH 05/26] remove unused property --- .../CustomerSavedPaymentMethodsCollectionViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index 8e26c6f108b..a1554fbcac6 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -83,7 +83,6 @@ class CustomerSavedPaymentMethodsCollectionViewController: UIViewController { let paymentMethodRemove: Bool let isTestMode: Bool let alternateUpdatePaymentMethodNavigation: Bool - let allowsSetAsDefaultPM: Bool } /// Whether or not you can edit save payment methods by removing or updating them. From 3c36c97d34e091715dfe65a7bcad88ca1cf12f71 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 2 Dec 2024 08:22:12 -0800 Subject: [PATCH 06/26] fix build issue --- .../CustomerSavedPaymentMethodsViewController.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift index c8f839a59f0..6a070a3d844 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift @@ -104,8 +104,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, - allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation ), appearance: configuration.appearance, cbcEligible: cbcEligible, @@ -657,8 +656,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { allowsRemovalOfLastSavedPaymentMethod: configuration.allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, - allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM + alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation ), appearance: configuration.appearance, cbcEligible: cbcEligible, From aac6ae1f0412b8c4e54a389a89501148d4e1c52f Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 2 Dec 2024 08:32:36 -0800 Subject: [PATCH 07/26] fix test --- .../PaymentSheet/SavedPaymentOptionsViewControllerTests.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift index d8f97611e0a..675aefa0857 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift @@ -304,7 +304,8 @@ class SavedPaymentOptionsViewControllerTests: XCTestCase { isTestMode: true, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, allowsRemovalOfPaymentMethods: allowsRemovalOfPaymentMethods, - alternateUpdatePaymentMethodNavigation: false) + alternateUpdatePaymentMethodNavigation: false, + allowsSetAsDefaultPM: false) } func savedPaymentOptionsController(_ configuration: SavedPaymentOptionsViewController.Configuration, @@ -315,6 +316,7 @@ class SavedPaymentOptionsViewControllerTests: XCTestCase { paymentSheetConfiguration: paymentSheetConfiguration, intent: Intent._testValue(), appearance: .default, + elementsSession: .emptyElementsSession, cbcEligible: cbcEligible, analyticsHelper: ._testValue(), delegate: nil) From 0fca0dcfa0af59076f99f5122e92fff551152174 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 10:46:05 -0800 Subject: [PATCH 08/26] check default pm against savedPaymentMethods instead of customer.paymentMethods --- .../SavedPaymentOptionsViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 6128f782736..0c4fe88ccf3 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -451,7 +451,7 @@ class SavedPaymentOptionsViewController: UIViewController { if allowsSetAsDefaultPM, let customer = elementsSession.customer, let customerDefault = customer.defaultPaymentMethod { - let defaultPaymentMethod = customer.paymentMethods.filter { + let defaultPaymentMethod = savedPaymentMethods.filter { $0.stripeId == customerDefault }.first guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } From c226e09f2827e1fe5200926bb201a87e8f648f3c Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 11:45:46 -0800 Subject: [PATCH 09/26] removed alternateUpdatePaymentMethodNavigation, consolidated reading from elements session logic --- .../CustomerSheetTestPlayground.swift | 5 +-- ...ustomerSheetTestPlaygroundController.swift | 3 +- .../CustomerSheetTestPlaygroundSettings.swift | 8 ---- .../PaymentSheetTestPlayground.swift | 5 +-- .../PaymentSheetTestPlaygroundSettings.swift | 8 ---- .../PlaygroundController.swift | 4 +- .../CustomerSheetUITest.swift | 1 - .../PaymentSheetLPMUITest.swift | 1 - .../PaymentSheetUITest.swift | 18 -------- .../ElementsCustomer.swift | 5 +++ .../CustomerSessionAdapter.swift | 14 +++--- ...ymentMethodsCollectionViewController.swift | 4 +- ...merSavedPaymentMethodsViewController.swift | 6 +-- .../CustomerSheet/CustomerSheet.swift | 14 ++---- .../CustomerSheetConfiguration.swift | 7 +-- .../CustomerSheetDataSource.swift | 2 +- .../EmbeddedPaymentElement+Internal.swift | 14 ++---- .../EmbeddedPaymentElementConfiguration.swift | 7 +-- .../PaymentElementConfiguration.swift | 1 - .../PaymentSheetConfiguration.swift | 7 +-- .../PaymentSheet/PaymentSheetLoader.swift | 16 ++----- .../SavedPaymentMethodCollectionView.swift | 17 ++----- .../SavedPaymentOptionsViewController.swift | 16 +++---- .../SavedPaymentMethodRowButton.swift | 45 +++---------------- ...calSavedPaymentMethodsViewController.swift | 34 +++----------- ...entSheetFlowControllerViewController.swift | 1 - .../PaymentSheetVerticalViewController.swift | 15 +++---- .../PaymentSheetViewController.swift | 1 - ...MethodsCollectionViewControllerTests.swift | 3 +- ...ntOptionsViewControllerSnapshotTests.swift | 2 +- ...vedPaymentOptionsViewControllerTests.swift | 1 - 31 files changed, 62 insertions(+), 223 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift index fa1075c35ce..481c9161d0b 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift @@ -53,9 +53,7 @@ struct CustomerSheetTestPlayground: View { SettingPickerView(setting: $playgroundController.settings.paymentMethodRemove) SettingPickerView(setting: $playgroundController.settings.paymentMethodRemoveLast) SettingPickerView(setting: $playgroundController.settings.paymentMethodAllowRedisplayFilters) - if playgroundController.settings.alternateUpdatePaymentMethodNavigation == .on { - SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) - } + SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) } } } @@ -78,7 +76,6 @@ struct CustomerSheetTestPlayground: View { SettingView(setting: $playgroundController.settings.autoreload) TextField("headerTextForSelectionScreen", text: headerTextForSelectionScreenBinding) SettingView(setting: $playgroundController.settings.allowsRemovalOfLastSavedPaymentMethod) - SettingView(setting: $playgroundController.settings.alternateUpdatePaymentMethodNavigation) HStack { Text("Macros").font(.headline) Spacer() diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift index 31c5c94c517..519921e1063 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift @@ -4,7 +4,7 @@ // import Combine -@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet +@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet import SwiftUI class CustomerSheetTestPlaygroundController: ObservableObject { @@ -147,7 +147,6 @@ class CustomerSheetTestPlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift index 9715d1c236c..365140d361e 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift @@ -148,12 +148,6 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { case allowVisa } - enum AlternateUpdatePaymentMethodNavigation: String, PickerEnum { - static let enumName: String = "alternateUpdatePaymentMethodNavigation" - case on - case off - } - enum AllowsSetAsDefaultPM: String, PickerEnum { static let enumName: String = "allowsSetAsDefaultPM" case on @@ -181,7 +175,6 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { var paymentMethodRemoveLast: PaymentMethodRemoveLast var paymentMethodAllowRedisplayFilters: PaymentMethodAllowRedisplayFilters var cardBrandAcceptance: CardBrandAcceptance - var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation var allowsSetAsDefaultPM: AllowsSetAsDefaultPM static func defaultValues() -> CustomerSheetTestPlaygroundSettings { @@ -205,7 +198,6 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { paymentMethodRemoveLast: .enabled, paymentMethodAllowRedisplayFilters: .always, cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off, allowsSetAsDefaultPM: .off) } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift index e33226d2d7e..a1e559bf1d1 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift @@ -45,7 +45,6 @@ struct PaymentSheetTestPlayground: View { SettingView(setting: $playgroundController.settings.requireCVCRecollection) SettingView(setting: $playgroundController.settings.autoreload) SettingView(setting: $playgroundController.settings.shakeAmbiguousViews) - SettingView(setting: $playgroundController.settings.alternateUpdatePaymentMethodNavigation) SettingView(setting: $playgroundController.settings.instantDebitsIncentives) } @@ -120,9 +119,7 @@ struct PaymentSheetTestPlayground: View { if playgroundController.settings.paymentMethodRedisplay == .enabled { SettingPickerView(setting: $playgroundController.settings.paymentMethodAllowRedisplayFilters) } - if playgroundController.settings.alternateUpdatePaymentMethodNavigation == .on { - SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) - } + SettingPickerView(setting: $playgroundController.settings.allowsSetAsDefaultPM) } } } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift index 8906ca32329..c13fb0a485c 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift @@ -444,12 +444,6 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { case allowVisa } - enum AlternateUpdatePaymentMethodNavigation: String, PickerEnum { - static let enumName: String = "alternateUpdatePaymentMethodNavigation" - case on - case off - } - enum AllowsSetAsDefaultPM: String, PickerEnum { static let enumName: String = "allowsSetAsDefaultPM" case on @@ -502,7 +496,6 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { var formSheetAction: FormSheetAction var embeddedViewDisplaysMandateText: DisplaysMandateTextEnabled var cardBrandAcceptance: CardBrandAcceptance - var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation var allowsSetAsDefaultPM: AllowsSetAsDefaultPM static func defaultValues() -> PaymentSheetTestPlaygroundSettings { @@ -550,7 +543,6 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { formSheetAction: .confirm, embeddedViewDisplaysMandateText: .on, cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off, allowsSetAsDefaultPM: .off) } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift index addece2210c..d17cc15515b 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift @@ -14,7 +14,7 @@ import Contacts import PassKit @_spi(STP) import StripeCore @_spi(STP) import StripePayments -@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet +@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) @_spi(AllowsSetAsDefaultPM) import StripePaymentSheet import SwiftUI import UIKit @@ -184,7 +184,6 @@ class PlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } @@ -273,7 +272,6 @@ class PlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on configuration.allowsSetAsDefaultPM = settings.allowsSetAsDefaultPM == .on return configuration } diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift index 1ba33c1f416..7572ccebdfc 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift @@ -475,7 +475,6 @@ class CustomerSheetUITest: XCTestCase { func testCardBrandChoiceUpdateAndRemove() { var settings = CustomerSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.customerMode = .returning diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift index 4f1af8d851f..f78114c8b61 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift @@ -1027,7 +1027,6 @@ class PaymentSheetStandardLPMUICBCTests: PaymentSheetStandardLPMUICase { func testCardBrandChoiceUpdateAndRemove() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index fb7cfcb2377..fdaa590d31f 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -2562,7 +2562,6 @@ class PaymentSheetLinkUITests: PaymentSheetUITestCase { class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { func testDefaultSPMHorizontalNavigation() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning @@ -2578,7 +2577,6 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { } func testDefaultSPMVerticalNavigation() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning @@ -2592,22 +2590,6 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { XCTAssertEqual(app.buttons.matching(identifier: "chevron").count, 2) } - func testDefaultSPMNavigationFlagOff() { - var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .off - settings.merchantCountryCode = .FR - settings.currency = .eur - settings.customerMode = .returning - settings.layout = .horizontal - - loadPlayground(app, settings) - - app.buttons["Present PaymentSheet"].waitForExistenceAndTap() - - app.buttons["Edit"].waitForExistenceAndTap() - - XCTAssertEqual(app.buttons.matching(identifier: "CircularButton.Edit").count, 1) - } } // MARK: Helpers diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift index a973e4a78a2..dc74d4c1c9e 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift @@ -41,4 +41,9 @@ struct ElementsCustomer: Equatable, Hashable { let defaultPaymentMethod = response["default_payment_method"] as? String return ElementsCustomer(paymentMethods: paymentMethods, defaultPaymentMethod: defaultPaymentMethod, customerSession: customerSession) } + + static func getDefaultPaymentMethod(from customer: ElementsCustomer?) -> STPPaymentMethod? { + guard let customer = customer else { return nil } + return customer.paymentMethods.first { $0.stripeId == customer.defaultPaymentMethod } + } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift index 19951aa58eb..129adf47d06 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift @@ -107,14 +107,14 @@ extension CustomerSessionAdapter { return stripePaymentMethodId } - func fetchSelectedPaymentOption(for customerId: String, elementsSession: STPElementsSession? = nil) -> CustomerPaymentOption? { - if configuration.allowsSetAsDefaultPM, - let elementsSession = elementsSession, - let customer = elementsSession.customer, - let defaultPaymentMethod = customer.defaultPaymentMethod { - return CustomerPaymentOption.stripeId(defaultPaymentMethod) + func fetchSelectedPaymentOption(for customerId: String, customer: ElementsCustomer? = nil) -> CustomerPaymentOption? { + guard configuration.allowsSetAsDefaultPM, + let customer = customer, + let defaultPaymentMethod = customer.defaultPaymentMethod else { + return CustomerPaymentOption.defaultPaymentMethod(for: customerId) } - return CustomerPaymentOption.defaultPaymentMethod(for: customerId) + + return CustomerPaymentOption.stripeId(defaultPaymentMethod) } func detachPaymentMethod(paymentMethodId: String) async throws { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index a1554fbcac6..810aa01d43a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -82,7 +82,6 @@ class CustomerSavedPaymentMethodsCollectionViewController: UIViewController { let allowsRemovalOfLastSavedPaymentMethod: Bool let paymentMethodRemove: Bool let isTestMode: Bool - let alternateUpdatePaymentMethodNavigation: Bool } /// Whether or not you can edit save payment methods by removing or updating them. @@ -387,8 +386,7 @@ extension CustomerSavedPaymentMethodsCollectionViewController: UICollectionViewD cell.setViewModel(viewModel.toSavedPaymentOptionsViewControllerSelection(), cbcEligible: cbcEligible, - allowsPaymentMethodRemoval: configuration.paymentMethodRemove, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + allowsPaymentMethodRemoval: configuration.paymentMethodRemove) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift index dce7266e5e6..5eccaca37ed 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift @@ -104,8 +104,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { showApplePay: showApplePay, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + isTestMode: configuration.apiClient.isTestmode ), appearance: configuration.appearance, cbcEligible: cbcEligible, @@ -658,8 +657,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { showApplePay: isApplePayEnabled, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + isTestMode: configuration.apiClient.isTestmode ), appearance: configuration.appearance, cbcEligible: cbcEligible, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift index efd20fc50aa..501edeac3a8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift @@ -329,18 +329,10 @@ extension CustomerSheet { var selectedPaymentOption: CustomerPaymentOption? - // read from back end + // get default payment method from elements session if configuration.allowsSetAsDefaultPM, - let customer = elementsSession.customer { - let defaultPaymentMethod = customer.paymentMethods.filter { - $0.stripeId == customer.defaultPaymentMethod - }.first - if let defaultPaymentMethod = defaultPaymentMethod { - selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) - } - else { - selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) - } + let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { + selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } else { selectedPaymentOption = CustomerPaymentOption.defaultPaymentMethod(for: customerSessionClientSecret.customerId) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift index ba1b7c15457..c1f253c575c 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift @@ -82,12 +82,7 @@ extension CustomerSheet { @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false - - /// This is an experimental feature that may be removed at any time. - /// If true, users can set a payment method as default. + /// If true, users can set a payment method as default and sync their default payment method across web and mobile /// If false (default), users cannot set default payment methods. @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift index 6020bb1bebc..d714d540e20 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetDataSource.swift @@ -42,7 +42,7 @@ class CustomerSheetDataSource { await loadFormSpecs() let customerId = try await customerSessionClientSecret.customerId let elementSession = try await elementsSessionResult - let paymentOption = customerSessionAdapter.fetchSelectedPaymentOption(for: customerId, elementsSession: elementSession) + let paymentOption = customerSessionAdapter.fetchSelectedPaymentOption(for: customerId, customer: elementSession.customer) // Override with specs from elementSession _ = FormSpecProvider.shared.loadFrom(elementSession.paymentMethodSpecs as Any) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index c440786e25d..bc52c588a90 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -37,16 +37,10 @@ extension EmbeddedPaymentElement { isFlatCheckmarkStyle: configuration.appearance.embeddedPaymentElement.row.style == .flatWithCheckmark ) let initialSelection: EmbeddedPaymentMethodsView.Selection? = { - // read from back end + // get default payment method from elements session if configuration.allowsSetAsDefaultPM, - let customer = loadResult.elementsSession.customer { - let defaultPaymentMethod = customer.paymentMethods.filter { - $0.stripeId == customer.defaultPaymentMethod - }.first - if let defaultPaymentMethod = defaultPaymentMethod { + let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: loadResult.elementsSession.customer) { return .saved(paymentMethod: defaultPaymentMethod) - } - } // Select the previous payment option @@ -156,11 +150,11 @@ extension EmbeddedPaymentElement: EmbeddedPaymentMethodsViewDelegate { } func presentSavedPaymentMethods(selectedSavedPaymentMethod: STPPaymentMethod?) { - // Special case, only 1 card remaining but is co-branded (or alternateUpdatePaymentMethodNavigation), skip showing the list and show update view controller + // Special case, only 1 card remaining, skip showing the list and show update view controller if savedPaymentMethods.count == 1, let paymentMethod = savedPaymentMethods.first, paymentMethod.isCoBrandedCard, - elementsSession.isCardBrandChoiceEligible || configuration.alternateUpdatePaymentMethodNavigation { + elementsSession.isCardBrandChoiceEligible { let updateViewModel = UpdatePaymentMethodViewModel(paymentMethod: paymentMethod, appearance: configuration.appearance, hostedSurface: .paymentSheet, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift index e43d2da2b98..1e2e8c0a600 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift @@ -132,12 +132,7 @@ extension EmbeddedPaymentElement { @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false - - /// This is an experimental feature that may be removed at any time. - /// If true, users can set a payment method as default. + /// If true, users can set a payment method as default and sync their default payment method across web and mobile /// If false (default), users cannot set default payment methods. @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift index 0842e182394..8c85ecae2e9 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift @@ -37,7 +37,6 @@ protocol PaymentElementConfiguration: PaymentMethodRequirementProvider { var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance { get set } var analyticPayload: [String: Any] { get } var disableWalletPaymentMethodFiltering: Bool { get set } - var alternateUpdatePaymentMethodNavigation: Bool { get set } var allowsSetAsDefaultPM: Bool { get set } var linkPaymentMethodsOnly: Bool { get set } var forceNativeLinkEnabled: Bool { get set } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift index ac655d97d92..38bebcecf5d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift @@ -212,12 +212,7 @@ extension PaymentSheet { @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false - - /// This is an experimental feature that may be removed at any time. - /// If true, users can set a payment method as default. + /// If true, users can set a payment method as default and sync their default payment method across web and mobile /// If false (default), users cannot set default payment methods. @_spi(AllowsSetAsDefaultPM) public var allowsSetAsDefaultPM = false } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift index 75ee52b6055..fee9ddf93b2 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift @@ -118,7 +118,7 @@ final class PaymentSheetLoader { showApplePay: integrationShape.canDefaultToLinkOrApplePay ? isApplePayEnabled : false, showLink: integrationShape.canDefaultToLinkOrApplePay ? isLinkEnabled : false, allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM, - elementsSession: elementsSession + customer: elementsSession.customer ) let paymentMethodTypes = PaymentSheet.PaymentMethodType.filteredPaymentMethodTypes(from: intent, elementsSession: elementsSession, configuration: configuration, logAvailability: true) @@ -319,18 +319,10 @@ final class PaymentSheetLoader { // Move default PM to front if let customerID = configuration.customer?.id { var defaultPaymentMethodOption: CustomerPaymentOption? - // read from back end + // get default payment method from elements session if configuration.allowsSetAsDefaultPM, - let customer = elementsSession.customer { - let defaultPaymentMethod = customer.paymentMethods.filter { - $0.stripeId == customer.defaultPaymentMethod - }.first - if let defaultPaymentMethod = defaultPaymentMethod { - defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) - } - else { - defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) - } + let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { + defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } else { defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 84039502938..8c677706286 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -112,19 +112,11 @@ extension SavedPaymentMethodCollectionView { var cbcEligible: Bool = false var allowsPaymentMethodRemoval: Bool = true - var alternateUpdatePaymentMethodNavigation: Bool = false - /// Indicates whether the cell should be editable or just removable. - /// If the card is a co-branded card and the merchant is eligible for card brand choice, then - /// the cell should be editable. Otherwise, it should be just removable. + /// Indicates whether the cell should display the edit icon var shouldAllowEditing: Bool { - if alternateUpdatePaymentMethodNavigation { - return UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in - viewModel?.savedPaymentMethod?.type == type - } - } - else { - return (viewModel?.isCoBrandedCard ?? false) && cbcEligible + return UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in + viewModel?.savedPaymentMethod?.type == type } } @@ -218,14 +210,13 @@ extension SavedPaymentMethodCollectionView { // MARK: - Internal Methods - func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool) { + func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool) { paymentMethodLogo.isHidden = false plus.isHidden = true shadowRoundedRectangle.isHidden = false self.viewModel = viewModel self.cbcEligible = cbcEligible self.allowsPaymentMethodRemoval = allowsPaymentMethodRemoval - self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation update() } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 0c4fe88ccf3..8cea528a12e 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -103,7 +103,6 @@ class SavedPaymentOptionsViewController: UIViewController { let isTestMode: Bool let allowsRemovalOfLastSavedPaymentMethod: Bool let allowsRemovalOfPaymentMethods: Bool - let alternateUpdatePaymentMethodNavigation: Bool let allowsSetAsDefaultPM: Bool } @@ -370,7 +369,7 @@ class SavedPaymentOptionsViewController: UIViewController { showApplePay: configuration.showApplePay, showLink: configuration.showLink, allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM, - elementsSession: elementsSession + customer: elementsSession.customer ) collectionView.reloadData() @@ -444,17 +443,12 @@ class SavedPaymentOptionsViewController: UIViewController { /// Creates the list of viewmodels to display in the "saved payment methods" carousel e.g. `["+ Add", "Apple Pay", "Link", "Visa 4242"]` /// - Returns defaultSelectedIndex: The index of the view model that is the default e.g. in the above list, if "Visa 4242" is the default, the index is 3. - static func makeViewModels(savedPaymentMethods: [STPPaymentMethod], customerID: String?, showApplePay: Bool, showLink: Bool, allowsSetAsDefaultPM: Bool, elementsSession: STPElementsSession) -> (defaultSelectedIndex: Int, viewModels: [Selection]) { + static func makeViewModels(savedPaymentMethods: [STPPaymentMethod], customerID: String?, showApplePay: Bool, showLink: Bool, allowsSetAsDefaultPM: Bool, customer: ElementsCustomer?) -> (defaultSelectedIndex: Int, viewModels: [Selection]) { // Get the default var defaultPaymentMethodOption: CustomerPaymentOption? - // read from back end + // get default payment method from elements session if allowsSetAsDefaultPM, - let customer = elementsSession.customer, - let customerDefault = customer.defaultPaymentMethod { - let defaultPaymentMethod = savedPaymentMethods.filter { - $0.stripeId == customerDefault - }.first - guard let defaultPaymentMethod = defaultPaymentMethod else { fatalError("default payment method does not exist in saved payment methods") } + let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: customer) { defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } else { @@ -512,7 +506,7 @@ extension SavedPaymentOptionsViewController: UICollectionViewDataSource, UIColle stpAssertionFailure() return UICollectionViewCell() } - cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation) + cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift index 0ca1096d569..9bfe5d90817 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift @@ -33,11 +33,8 @@ final class SavedPaymentMethodRowButton: UIView { } rowButton.isSelected = isSelected - rowButton.isEnabled = !isEditing || alternateUpdatePaymentMethodNavigation - chevronButton.isHidden = !canUpdate || !alternateUpdatePaymentMethodNavigation - updateButton.isHidden = !canUpdate || alternateUpdatePaymentMethodNavigation - removeButton.isHidden = !canRemove || alternateUpdatePaymentMethodNavigation - stackView.isUserInteractionEnabled = isEditing + chevronButton.isHidden = !canUpdate + chevronButton.isUserInteractionEnabled = isEditing } } @@ -86,51 +83,23 @@ final class SavedPaymentMethodRowButton: UIView { private let appearance: PaymentSheet.Appearance // MARK: Private views - - private lazy var removeButton: CircularButton = { - let removeButton = CircularButton(style: .remove, iconColor: .white) - removeButton.backgroundColor = appearance.colors.danger - removeButton.isHidden = true - removeButton.addTarget(self, action: #selector(handleRemoveButtonTapped), for: .touchUpInside) - return removeButton - }() - - private lazy var updateButton: CircularButton = { - let updateButton = CircularButton(style: .edit, iconColor: .white) - updateButton.backgroundColor = appearance.colors.icon - updateButton.isHidden = true - updateButton.addTarget(self, action: #selector(handleUpdateButtonTapped), for: .touchUpInside) - return updateButton - }() - private lazy var chevronButton: RowButton.RightAccessoryButton = { let chevronButton = RowButton.RightAccessoryButton(accessoryType: .update, appearance: appearance, didTap: handleUpdateButtonTapped) chevronButton.isHidden = true + chevronButton.isUserInteractionEnabled = isEditing return chevronButton }() - private lazy var stackView: UIStackView = { - let stackView = UIStackView.makeRowButtonContentStackView(arrangedSubviews: [chevronButton, updateButton, removeButton]) - // margins handled by the `RowButton` - stackView.directionalLayoutMargins = .zero - stackView.isUserInteractionEnabled = isEditing - return stackView - }() - private lazy var rowButton: RowButton = { - let button: RowButton = .makeForSavedPaymentMethod(paymentMethod: paymentMethod, appearance: appearance, rightAccessoryView: stackView, didTap: handleRowButtonTapped) - + let button: RowButton = .makeForSavedPaymentMethod(paymentMethod: paymentMethod, appearance: appearance, rightAccessoryView: chevronButton, didTap: handleRowButtonTapped) + button.isEnabled = true return button }() - private let alternateUpdatePaymentMethodNavigation: Bool - init(paymentMethod: STPPaymentMethod, - appearance: PaymentSheet.Appearance, - alternateUpdatePaymentMethodNavigation: Bool = false) { + appearance: PaymentSheet.Appearance) { self.paymentMethod = paymentMethod self.appearance = appearance - self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation super.init(frame: .zero) addAndPinSubview(rowButton) @@ -150,7 +119,7 @@ final class SavedPaymentMethodRowButton: UIView { } @objc private func handleRowButtonTapped(_: RowButton) { - if alternateUpdatePaymentMethodNavigation && isEditing { + if isEditing { delegate?.didSelectUpdateButton(self, with: paymentMethod) } else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift index 99e66e644b6..d0129436caf 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift @@ -51,7 +51,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { paymentMethodRows.forEach { let allowsRemoval = canRemovePaymentMethods let paymentMethodType = $0.paymentMethod.type - let allowsUpdating = ($0.paymentMethod.isCoBrandedCard && isCBCEligible) || (configuration.alternateUpdatePaymentMethodNavigation && (UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in paymentMethodType == type })) + let allowsUpdating = ($0.paymentMethod.isCoBrandedCard && isCBCEligible) || UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in paymentMethodType == type } $0.state = .editing(allowsRemoval: allowsRemoval, allowsUpdating: allowsUpdating) } @@ -70,10 +70,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { } private var headerText: String { - if isRemoveOnlyMode { - return .Localized.remove_payment_method - } - if isEditingPaymentMethods { return paymentMethods.count == 1 ? .Localized.manage_payment_method : .Localized.manage_payment_methods } @@ -88,9 +84,8 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { } var canEdit: Bool { - // We can edit if there are removable or editable payment methods and we are not in remove only mode - // Or, under the new navigation flow, if any of the payment methods are cards, US bank accounts, or SEPA debit - return ((canRemovePaymentMethods || (hasCoBrandedCards && isCBCEligible)) && !isRemoveOnlyMode) || (configuration.alternateUpdatePaymentMethodNavigation && paymentMethods.contains { UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) }) + // We can edit if any of the payment methods are cards, US bank accounts, or SEPA debit + return paymentMethods.contains { UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) } } private var selectedPaymentMethod: STPPaymentMethod? { @@ -109,12 +104,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { SavedPaymentMethodManager(configuration: configuration, elementsSession: elementsSession) }() - /// Determines if the we should operate in "Remove Only Mode". This mode is enabled under the following conditions: - /// - There is exactly one payment method available at init time. - /// - The single available payment method is not a co-branded card. - /// In this mode, the user can only delete the payment method; updating or selecting other payment methods is disabled. - let isRemoveOnlyMode: Bool - // MARK: Internal properties weak var delegate: VerticalSavedPaymentMethodsViewControllerDelegate? @@ -171,15 +160,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { self.paymentMethodRemove = elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() self.isCBCEligible = elementsSession.isCardBrandChoiceEligible self.analyticsHelper = analyticsHelper - if configuration.alternateUpdatePaymentMethodNavigation { - self.isRemoveOnlyMode = false - } - else { - // Put in remove only mode and don't show the option to update PMs if: - // 1. We only have 1 payment method - // 2. The customer can't update the card brand - self.isRemoveOnlyMode = paymentMethods.count == 1 && (!paymentMethods[0].isCoBrandedCard || !isCBCEligible) - } super.init(nibName: nil, bundle: nil) self.paymentMethodRows = buildPaymentMethodRows(paymentMethods: paymentMethods) setInitialState(selectedPaymentMethod: selectedPaymentMethod) @@ -188,8 +168,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private func buildPaymentMethodRows(paymentMethods: [STPPaymentMethod]) -> [SavedPaymentMethodRowButton] { return paymentMethods.map { paymentMethod in let button = SavedPaymentMethodRowButton(paymentMethod: paymentMethod, - appearance: configuration.appearance, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + appearance: configuration.appearance) button.delegate = self return button } @@ -197,9 +176,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private func setInitialState(selectedPaymentMethod: STPPaymentMethod?) { paymentMethodRows.first { $0.paymentMethod.stripeId == selectedPaymentMethod?.stripeId }?.state = .selected - if isRemoveOnlyMode { - paymentMethodRows.first?.state = .editing(allowsRemoval: canRemovePaymentMethods, allowsUpdating: false) - } } required init?(coder: NSCoder) { @@ -388,7 +364,7 @@ extension VerticalSavedPaymentMethodsViewController: UpdatePaymentMethodViewCont } // Create the new button - let newButton = SavedPaymentMethodRowButton(paymentMethod: updatedPaymentMethod, appearance: configuration.appearance, alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + let newButton = SavedPaymentMethodRowButton(paymentMethod: updatedPaymentMethod, appearance: configuration.appearance) newButton.delegate = self newButton.previousSelectedState = oldButton.previousSelectedState newButton.state = oldButton.state diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift index f6de5a3c778..f93b164d8f2 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift @@ -207,7 +207,6 @@ class PaymentSheetFlowControllerViewController: UIViewController, FlowController isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: PaymentSheetViewController.allowsRemovalOfLastPaymentMethod(elementsSession: elementsSession, configuration: configuration), allowsRemovalOfPaymentMethods: elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), paymentSheetConfiguration: configuration, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index f87e91d6b76..50399267f3a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -288,15 +288,10 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo if let selection { return selection } - // read from back end + // get default payment method from elements session if configuration.allowsSetAsDefaultPM, - let customer = elementsSession.customer { - let defaultPaymentMethod = customer.paymentMethods.filter { - $0.stripeId == customer.defaultPaymentMethod - }.first - if let defaultPaymentMethod = defaultPaymentMethod { - return .saved(paymentMethod: defaultPaymentMethod) - } + let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { + return .saved(paymentMethod: defaultPaymentMethod) } switch previousPaymentOption { @@ -579,11 +574,11 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo @objc func presentManageScreen() { error = nil - // Special case, only 1 card remaining but is co-branded (or alternateUpdatePaymentMethodNavigation), skip showing the list and show update view controller + // Special case, only 1 card remaining, skip showing the list and show update view controller if savedPaymentMethods.count == 1, let paymentMethod = savedPaymentMethods.first, paymentMethod.isCoBrandedCard, - elementsSession.isCardBrandChoiceEligible || configuration.alternateUpdatePaymentMethodNavigation { + elementsSession.isCardBrandChoiceEligible { let updateViewModel = UpdatePaymentMethodViewModel(paymentMethod: paymentMethod, appearance: configuration.appearance, hostedSurface: .paymentSheet, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift index b893f3cb1bb..7406ed204db 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift @@ -175,7 +175,6 @@ class PaymentSheetViewController: UIViewController, PaymentSheetViewControllerPr isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: PaymentSheetViewController.allowsRemovalOfLastPaymentMethod(elementsSession: elementsSession, configuration: configuration), allowsRemovalOfPaymentMethods: loadResult.elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation, allowsSetAsDefaultPM: configuration.allowsSetAsDefaultPM ), paymentSheetConfiguration: configuration, diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift index da2caaa23d1..39d782ab68e 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift @@ -296,8 +296,7 @@ class CustomerSavedPaymentMethodsCollectionViewControllerTests: XCTestCase { return CustomerSavedPaymentMethodsCollectionViewController.Configuration(showApplePay: false, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: true, - alternateUpdatePaymentMethodNavigation: false) + isTestMode: true) } func customerSavedPaymentMethods(_ configuration: CustomerSavedPaymentMethodsCollectionViewController.Configuration, savedPaymentMethods: [STPPaymentMethod], diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift index 097736d56a3..08ece3c4cf9 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift @@ -32,7 +32,7 @@ final class SavedPaymentOptionsViewControllerSnapshotTests: STPSnapshotTestCase STPPaymentMethod._testUSBankAccount(), STPPaymentMethod._testSEPA(), ] - let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true, alternateUpdatePaymentMethodNavigation: false, allowsSetAsDefaultPM: false) + let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true, allowsSetAsDefaultPM: false) let intent = Intent.deferredIntent(intentConfig: .init(mode: .payment(amount: 0, currency: "USD", setupFutureUsage: nil, captureMethod: .automatic), confirmHandler: { _, _, _ in })) let sut = SavedPaymentOptionsViewController(savedPaymentMethods: paymentMethods, configuration: config, diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift index 675aefa0857..2d2d52a7772 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift @@ -304,7 +304,6 @@ class SavedPaymentOptionsViewControllerTests: XCTestCase { isTestMode: true, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, allowsRemovalOfPaymentMethods: allowsRemovalOfPaymentMethods, - alternateUpdatePaymentMethodNavigation: false, allowsSetAsDefaultPM: false) } From f8585360563b81d4c6463d440dff3d6d17d3ee59 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 16:39:40 -0800 Subject: [PATCH 10/26] remove alternateUpdatePaymentMethodNavigation flag and updated tests --- .../CustomerSheetTestPlayground.swift | 1 - ...ustomerSheetTestPlaygroundController.swift | 3 +- .../CustomerSheetTestPlaygroundSettings.swift | 10 +--- .../PaymentSheetTestPlayground.swift | 1 - .../PaymentSheetTestPlaygroundSettings.swift | 10 +--- .../PlaygroundController.swift | 4 +- .../CustomerSheetUITest.swift | 14 +++--- .../PaymentSheetUITest/EmbeddedUITest.swift | 17 +++++-- .../PaymentSheetLPMUITest.swift | 1 - .../PaymentSheetUITest.swift | 30 ++++-------- .../PaymentSheetVerticalUITest.swift | 9 ++-- ...ymentMethodsCollectionViewController.swift | 4 +- ...merSavedPaymentMethodsViewController.swift | 6 +-- .../CustomerSheetConfiguration.swift | 5 -- .../EmbeddedPaymentElement+Internal.swift | 6 +-- .../EmbeddedPaymentElementConfiguration.swift | 5 -- .../PaymentElementConfiguration.swift | 1 - .../PaymentSheetConfiguration.swift | 4 -- .../SavedPaymentMethodCollectionView.swift | 49 ++++++------------- .../SavedPaymentOptionsViewController.swift | 30 +----------- .../SavedPaymentMethodRowButton.swift | 41 ++-------------- ...calSavedPaymentMethodsViewController.swift | 22 +++------ ...entSheetFlowControllerViewController.swift | 3 +- .../PaymentSheetVerticalViewController.swift | 6 +-- .../PaymentSheetViewController.swift | 3 +- ...MethodsCollectionViewControllerTests.swift | 3 +- ...ntOptionsViewControllerSnapshotTests.swift | 2 +- ...vedPaymentOptionsViewControllerTests.swift | 3 +- 28 files changed, 80 insertions(+), 213 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift index 482c0724047..72a36a853e2 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlayground.swift @@ -75,7 +75,6 @@ struct CustomerSheetTestPlayground: View { SettingView(setting: $playgroundController.settings.autoreload) TextField("headerTextForSelectionScreen", text: headerTextForSelectionScreenBinding) SettingView(setting: $playgroundController.settings.allowsRemovalOfLastSavedPaymentMethod) - SettingView(setting: $playgroundController.settings.alternateUpdatePaymentMethodNavigation) HStack { Text("Macros").font(.headline) Spacer() diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift index 697f0af37e3..6d8f22e6665 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundController.swift @@ -4,7 +4,7 @@ // import Combine -@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) import StripePaymentSheet +@_spi(STP) @_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) import StripePaymentSheet import SwiftUI class CustomerSheetTestPlaygroundController: ObservableObject { @@ -147,7 +147,6 @@ class CustomerSheetTestPlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on return configuration } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift index d325e11fc93..ecf1cfe11d8 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/CustomerSheetTestPlaygroundSettings.swift @@ -148,12 +148,6 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { case allowVisa } - enum AlternateUpdatePaymentMethodNavigation: String, PickerEnum { - static let enumName: String = "alternateUpdatePaymentMethodNavigation" - case on - case off - } - var customerMode: CustomerMode var customerId: String? var customerKeyType: CustomerKeyType @@ -175,7 +169,6 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { var paymentMethodRemoveLast: PaymentMethodRemoveLast var paymentMethodAllowRedisplayFilters: PaymentMethodAllowRedisplayFilters var cardBrandAcceptance: CardBrandAcceptance - var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation static func defaultValues() -> CustomerSheetTestPlaygroundSettings { return CustomerSheetTestPlaygroundSettings(customerMode: .new, @@ -197,8 +190,7 @@ public struct CustomerSheetTestPlaygroundSettings: Codable, Equatable { paymentMethodRemove: .enabled, paymentMethodRemoveLast: .enabled, paymentMethodAllowRedisplayFilters: .always, - cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off) + cardBrandAcceptance: .all) } var base64Data: String { diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift index adcfafd3b48..5345a87d8bd 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlayground.swift @@ -45,7 +45,6 @@ struct PaymentSheetTestPlayground: View { SettingView(setting: $playgroundController.settings.requireCVCRecollection) SettingView(setting: $playgroundController.settings.autoreload) SettingView(setting: $playgroundController.settings.shakeAmbiguousViews) - SettingView(setting: $playgroundController.settings.alternateUpdatePaymentMethodNavigation) SettingView(setting: $playgroundController.settings.instantDebitsIncentives) } diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift index 3b426289685..d021cc74f36 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PaymentSheetTestPlaygroundSettings.swift @@ -444,12 +444,6 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { case allowVisa } - enum AlternateUpdatePaymentMethodNavigation: String, PickerEnum { - static let enumName: String = "alternateUpdatePaymentMethodNavigation" - case on - case off - } - var uiStyle: UIStyle var layout: Layout var mode: Mode @@ -496,7 +490,6 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { var formSheetAction: FormSheetAction var embeddedViewDisplaysMandateText: DisplaysMandateTextEnabled var cardBrandAcceptance: CardBrandAcceptance - var alternateUpdatePaymentMethodNavigation: AlternateUpdatePaymentMethodNavigation static func defaultValues() -> PaymentSheetTestPlaygroundSettings { return PaymentSheetTestPlaygroundSettings( @@ -542,8 +535,7 @@ struct PaymentSheetTestPlaygroundSettings: Codable, Equatable { collectAddress: .automatic, formSheetAction: .confirm, embeddedViewDisplaysMandateText: .on, - cardBrandAcceptance: .all, - alternateUpdatePaymentMethodNavigation: .off) + cardBrandAcceptance: .all) } static let nsUserDefaultsKey = "PaymentSheetTestPlaygroundSettings" diff --git a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift index 728c42df18c..16d1e3bd5d6 100644 --- a/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift +++ b/Example/PaymentSheet Example/PaymentSheet Example/PlaygroundController.swift @@ -14,7 +14,7 @@ import Contacts import PassKit @_spi(STP) import StripeCore @_spi(STP) import StripePayments -@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) @_spi(AlternateUpdatePaymentMethodNavigation) import StripePaymentSheet +@_spi(CustomerSessionBetaAccess) @_spi(STP) @_spi(PaymentSheetSkipConfirmation) @_spi(ExperimentalAllowsRemovalOfLastSavedPaymentMethodAPI) @_spi(EmbeddedPaymentElementPrivateBeta) @_spi(CardBrandFilteringBeta) import StripePaymentSheet import SwiftUI import UIKit @@ -184,7 +184,6 @@ class PlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on return configuration } @@ -272,7 +271,6 @@ class PlaygroundController: ObservableObject { case .allowVisa: configuration.cardBrandAcceptance = .allowed(brands: [.visa]) } - configuration.alternateUpdatePaymentMethodNavigation = settings.alternateUpdatePaymentMethodNavigation == .on return configuration } diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift index 1ba33c1f416..f8c1a893520 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift @@ -475,7 +475,6 @@ class CustomerSheetUITest: XCTestCase { func testCardBrandChoiceUpdateAndRemove() { var settings = CustomerSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.customerMode = .returning @@ -552,7 +551,8 @@ class CustomerSheetUITest: XCTestCase { XCTAssertTrue(app.staticTexts["Done"].waitForExistence(timeout: 1)) // Sanity check "Done" button is there // Remove one saved PM - XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")?.tap()) + XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Edit")?.tap()) + app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Sleep for 1 second to ensure animation has been completed @@ -573,7 +573,8 @@ class CustomerSheetUITest: XCTestCase { XCTAssertTrue(app.staticTexts["Done"].waitForExistence(timeout: 1)) // Sanity check "Done" button is there // Remove the 4242 saved PM - XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")?.tap()) + XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Edit")?.tap()) + app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Wait for alert view to disappear and removal animation to finish @@ -623,7 +624,6 @@ class CustomerSheetUITest: XCTestCase { // Assert there are no remove buttons on each tile and the update screen XCTAssertNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")) XCTAssertTrue(app.buttons["CircularButton.Edit"].waitForExistenceAndTap(timeout: timeout)) - XCTAssertFalse(app.buttons["Remove"].exists) // Dismiss Sheet app.buttons["Back"].waitForExistenceAndTap(timeout: timeout) @@ -658,7 +658,6 @@ class CustomerSheetUITest: XCTestCase { // Assert there are no remove buttons on each tile and the update screen XCTAssertNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")) XCTAssertTrue(app.buttons["CircularButton.Edit"].waitForExistenceAndTap(timeout: timeout)) - XCTAssertFalse(app.buttons["Remove"].exists) // Dismiss Sheet app.buttons["Back"].waitForExistenceAndTap(timeout: timeout) @@ -695,8 +694,9 @@ class CustomerSheetUITest: XCTestCase { } func removeFirstPaymentMethodInList(alertBody: String = "Visa •••• 4242") { - let removeButton1 = app.buttons["Remove"].firstMatch - removeButton1.tap() + let editButton = app.buttons["Edit"].firstMatch + editButton.tap() + app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: alertBody, alertTitle: "Remove card?", buttonToTap: "Remove") } diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift index 60c7f7c3786..81c9ea1e53c 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift @@ -211,6 +211,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is NOT on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) + app.buttons["CircularButton.Edit"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -278,6 +279,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) + app.buttons["CircularButton.Edit"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Cartes Bancaires •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -355,7 +357,8 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove selected 4242 card app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") app.buttons["Done"].waitForExistenceAndTap() @@ -367,7 +370,8 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 6789 & verify app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Bank account •••• 6789", alertTitle: "Remove bank account?", buttonToTap: "Remove") XCTAssertFalse(card4242Button.waitForExistence(timeout: 3.0)) @@ -425,7 +429,8 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove bank acct. while it isn't selected app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Bank account •••• 6789", alertTitle: "Remove bank account?", buttonToTap: "Remove") app.buttons["Done"].waitForExistenceAndTap() @@ -438,7 +443,8 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 4242 app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") XCTAssertFalse(card4242Button.waitForExistence(timeout: 3.0)) @@ -482,7 +488,8 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Delete one payment method so we only have one left, we should not auto select the last remaining saved PM XCTAssertTrue(app.buttons["View more"].waitForExistenceAndTap()) XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) - XCTAssertTrue(app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap()) + XCTAssertTrue(app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap()) + XCTAssertTrue(app.buttons["Remove"].waitForExistenceAndTap()) dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") XCTAssertTrue(app.buttons["Done"].waitForExistenceAndTap()) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift index 4f1af8d851f..f78114c8b61 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetLPMUITest.swift @@ -1027,7 +1027,6 @@ class PaymentSheetStandardLPMUICBCTests: PaymentSheetStandardLPMUICase { func testCardBrandChoiceUpdateAndRemove() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index 09a3cce8b54..71fa1d51ebe 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -330,6 +330,9 @@ class PaymentSheetStandardUITests: PaymentSheetUITestCase { XCTAssertTrue(editButton.waitForExistence(timeout: 60.0)) editButton.tap() + let circularEditButton = app.buttons["CircularButton.Edit"] + circularEditButton.waitForExistenceAndTap() + let removeButton = app.buttons["Remove"] XCTAssertTrue(removeButton.waitForExistence(timeout: 60.0)) removeButton.tap() @@ -1169,6 +1172,9 @@ class PaymentSheetDeferredServerSideUITests: PaymentSheetUITestCase { XCTAssertTrue(editButton.waitForExistence(timeout: 60.0)) editButton.tap() + let circularEditButton = app.buttons["CircularButton.Edit"] + circularEditButton.waitForExistenceAndTap() + let removeButton = app.buttons["Remove"] XCTAssertTrue(removeButton.waitForExistence(timeout: 60.0)) removeButton.tap() @@ -1451,7 +1457,8 @@ class PaymentSheetCustomerSessionDedupeUITests: PaymentSheetUITestCase { XCTAssertTrue(app.staticTexts["Done"].waitForExistence(timeout: 1)) // Sanity check "Done" button is there // Remove one saved PM - XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")?.tap()) + XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Edit")?.tap()) + XCTAssertTrue(app.buttons["Remove"].waitForExistenceAndTap()) XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Should be kicked out of edit mode now that we have one saved PM @@ -1533,7 +1540,8 @@ class PaymentSheetCustomerSessionDedupeUITests: PaymentSheetUITestCase { XCTAssertTrue(app.staticTexts["Done"].waitForExistence(timeout: 1)) // Sanity check "Done" button is there // Remove one saved PM - XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Remove")?.tap()) + XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Edit")?.tap()) + XCTAssertTrue(app.buttons["Remove"].waitForExistenceAndTap()) XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Should be kicked out of edit mode now that we have one saved PM @@ -2566,7 +2574,6 @@ class PaymentSheetLinkUITests: PaymentSheetUITestCase { class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { func testDefaultSPMHorizontalNavigation() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning @@ -2582,7 +2589,6 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { } func testDefaultSPMVerticalNavigation() { var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .on settings.merchantCountryCode = .FR settings.currency = .eur settings.customerMode = .returning @@ -2596,22 +2602,6 @@ class PaymentSheetDefaultSPMUITests: PaymentSheetUITestCase { XCTAssertEqual(app.buttons.matching(identifier: "chevron").count, 2) } - func testDefaultSPMNavigationFlagOff() { - var settings = PaymentSheetTestPlaygroundSettings.defaultValues() - settings.alternateUpdatePaymentMethodNavigation = .off - settings.merchantCountryCode = .FR - settings.currency = .eur - settings.customerMode = .returning - settings.layout = .horizontal - - loadPlayground(app, settings) - - app.buttons["Present PaymentSheet"].waitForExistenceAndTap() - - app.buttons["Edit"].waitForExistenceAndTap() - - XCTAssertEqual(app.buttons.matching(identifier: "CircularButton.Edit").count, 1) - } } // MARK: Helpers diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift index adf0f9d20d2..74d6103da4c 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift @@ -225,7 +225,8 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { app.buttons["View more"].waitForExistenceAndTap() XCTAssertTrue(firstPaymentMethod.isSelected) app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Remove"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() app.alerts.buttons["Remove"].waitForExistenceAndTap() XCTAssertFalse(firstPaymentMethod.exists) app.buttons["Done"].waitForExistenceAndTap() @@ -243,12 +244,13 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) // Remove the 4242 card - app.otherElements["•••• 4242"].buttons["CircularButton.Remove"].waitForExistenceAndTap() + app.otherElements["•••• 4242"].buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Exit edit mode, remove button should be hidden XCTAssertTrue(app.buttons["Done"].waitForExistenceAndTap()) - XCTAssertFalse(app.buttons["CircularButton.Remove"].waitForExistence(timeout: 2.0)) + XCTAssertFalse(app.buttons["CircularButton.Edit"].waitForExistence(timeout: 2.0)) // Update the card brand on the last card XCTAssertTrue(app.buttons["Cartes Bancaires ending in 1 0 0 1"].waitForExistence(timeout: 1.0)) // Cartes Bancaires card should be selected now that 4242 card is removed @@ -276,6 +278,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { // Reselect edit icon and delete the card from the update view controller app.buttons["Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index a1554fbcac6..810aa01d43a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -82,7 +82,6 @@ class CustomerSavedPaymentMethodsCollectionViewController: UIViewController { let allowsRemovalOfLastSavedPaymentMethod: Bool let paymentMethodRemove: Bool let isTestMode: Bool - let alternateUpdatePaymentMethodNavigation: Bool } /// Whether or not you can edit save payment methods by removing or updating them. @@ -387,8 +386,7 @@ extension CustomerSavedPaymentMethodsCollectionViewController: UICollectionViewD cell.setViewModel(viewModel.toSavedPaymentOptionsViewControllerSelection(), cbcEligible: cbcEligible, - allowsPaymentMethodRemoval: configuration.paymentMethodRemove, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + allowsPaymentMethodRemoval: configuration.paymentMethodRemove) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift index dce7266e5e6..5eccaca37ed 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsViewController.swift @@ -104,8 +104,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { showApplePay: showApplePay, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + isTestMode: configuration.apiClient.isTestmode ), appearance: configuration.appearance, cbcEligible: cbcEligible, @@ -658,8 +657,7 @@ class CustomerSavedPaymentMethodsViewController: UIViewController { showApplePay: isApplePayEnabled, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: configuration.apiClient.isTestmode, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + isTestMode: configuration.apiClient.isTestmode ), appearance: configuration.appearance, cbcEligible: cbcEligible, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift index 03508b9be46..67f6785c5df 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheetConfiguration.swift @@ -81,11 +81,6 @@ extension CustomerSheet { /// Note: Card brand filtering is not currently supported by Link. @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all - /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false - public init () { } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 7ad84a15724..7d5541b722b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -144,11 +144,9 @@ extension EmbeddedPaymentElement: EmbeddedPaymentMethodsViewDelegate { } func presentSavedPaymentMethods(selectedSavedPaymentMethod: STPPaymentMethod?) { - // Special case, only 1 card remaining but is co-branded (or alternateUpdatePaymentMethodNavigation), skip showing the list and show update view controller + // Special case, only 1 card remaining, skip showing the list and show update view controller if savedPaymentMethods.count == 1, - let paymentMethod = savedPaymentMethods.first, - paymentMethod.isCoBrandedCard, - elementsSession.isCardBrandChoiceEligible || configuration.alternateUpdatePaymentMethodNavigation { + let paymentMethod = savedPaymentMethods.first { let updateViewModel = UpdatePaymentMethodViewModel(paymentMethod: paymentMethod, appearance: configuration.appearance, hostedSurface: .paymentSheet, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift index b224a784d1d..4a6dbf14d50 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElementConfiguration.swift @@ -131,11 +131,6 @@ extension EmbeddedPaymentElement { /// Note: Card brand filtering is not currently supported by Link. @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all - /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false - /// The view can display payment methods like “Card” that, when tapped, open a form sheet where customers enter their payment method details. The sheet has a button at the bottom. `FormSheetAction` enumerates the actions the button can perform. public enum FormSheetAction { /// The button says “Pay” or “Setup”. When tapped, we confirm the payment or setup in the form sheet. diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift index 09334d57a50..c548c2080be 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentElementConfiguration.swift @@ -37,7 +37,6 @@ protocol PaymentElementConfiguration: PaymentMethodRequirementProvider { var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance { get set } var analyticPayload: [String: Any] { get } var disableWalletPaymentMethodFiltering: Bool { get set } - var alternateUpdatePaymentMethodNavigation: Bool { get set } var linkPaymentMethodsOnly: Bool { get set } var forceNativeLinkEnabled: Bool { get set } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift index f85122850fb..85934702a49 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetConfiguration.swift @@ -211,10 +211,6 @@ extension PaymentSheet { /// Note: Card brand filtering is not currently supported by Link. @_spi(CardBrandFilteringBeta) public var cardBrandAcceptance: PaymentSheet.CardBrandAcceptance = .all - /// This is an experimental feature that may be removed at any time. - /// If true, when editing, cards and us bank accounts will have the edit icon and users cannot remove them from the list screen. - /// If false (default), only card brand choice eligible cards can be edited and users can remove payment methods from the list screen. - @_spi(AlternateUpdatePaymentMethodNavigation) public var alternateUpdatePaymentMethodNavigation = false } /// Defines the layout orientations available for displaying payment methods in PaymentSheet. diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 84039502938..af6082dd479 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -56,8 +56,6 @@ class SavedPaymentMethodCollectionView: UICollectionView { // MARK: - Cells protocol PaymentOptionCellDelegate: AnyObject { - func paymentOptionCellDidSelectRemove( - _ paymentOptionCell: SavedPaymentMethodCollectionView.PaymentOptionCell) func paymentOptionCellDidSelectEdit( _ paymentOptionCell: SavedPaymentMethodCollectionView.PaymentOptionCell) } @@ -85,12 +83,15 @@ extension SavedPaymentMethodCollectionView { return ShadowedRoundedRectangle(appearance: appearance) }() lazy var accessoryButton: CircularButton = { - let button = CircularButton(style: .remove, + let button = CircularButton(style: .edit, dangerColor: appearance.colors.danger) - button.backgroundColor = appearance.colors.danger + button.set(style: .edit, with: appearance.colors.danger) + button.backgroundColor = UIColor.dynamic( + light: .systemGray5, dark: appearance.colors.componentBackground.lighten(by: 0.075)) + button.iconColor = appearance.colors.icon button.isAccessibilityElement = true - button.accessibilityLabel = String.Localized.remove - button.accessibilityIdentifier = "Remove" + button.accessibilityLabel = String.Localized.edit + button.accessibilityIdentifier = "Edit" return button }() @@ -112,20 +113,14 @@ extension SavedPaymentMethodCollectionView { var cbcEligible: Bool = false var allowsPaymentMethodRemoval: Bool = true - var alternateUpdatePaymentMethodNavigation: Bool = false - /// Indicates whether the cell should be editable or just removable. - /// If the card is a co-branded card and the merchant is eligible for card brand choice, then - /// the cell should be editable. Otherwise, it should be just removable. + /// Indicates whether the cell should display the edit icon. + /// True if supported saved pm (card, US bank account, or SEPA debit) var shouldAllowEditing: Bool { - if alternateUpdatePaymentMethodNavigation { - return UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in - viewModel?.savedPaymentMethod?.type == type - } - } - else { - return (viewModel?.isCoBrandedCard ?? false) && cbcEligible + guard UpdatePaymentMethodViewModel.supportedPaymentMethods.contains(where: { viewModel?.savedPaymentMethod?.type == $0 }) else { + fatalError("Payment method does not match supported saved payment methods.") } + return true } // MARK: - UICollectionViewCell @@ -218,14 +213,13 @@ extension SavedPaymentMethodCollectionView { // MARK: - Internal Methods - func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool, alternateUpdatePaymentMethodNavigation: Bool) { + func setViewModel(_ viewModel: SavedPaymentOptionsViewController.Selection, cbcEligible: Bool, allowsPaymentMethodRemoval: Bool) { paymentMethodLogo.isHidden = false plus.isHidden = true shadowRoundedRectangle.isHidden = false self.viewModel = viewModel self.cbcEligible = cbcEligible self.allowsPaymentMethodRemoval = allowsPaymentMethodRemoval - self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation update() } @@ -249,8 +243,6 @@ extension SavedPaymentMethodCollectionView { private func didSelectAccessory() { if shouldAllowEditing { delegate?.paymentOptionCellDidSelectEdit(self) - } else if allowsPaymentMethodRemoval { - delegate?.paymentOptionCellDidSelectRemove(self) } } @@ -336,19 +328,8 @@ extension SavedPaymentMethodCollectionView { } if isRemovingPaymentMethods { - if case .saved = viewModel { - if shouldAllowEditing { - accessoryButton.isHidden = false - accessoryButton.set(style: .edit, with: appearance.colors.danger) - accessoryButton.backgroundColor = UIColor.dynamic( - light: .systemGray5, dark: appearance.colors.componentBackground.lighten(by: 0.075)) - accessoryButton.iconColor = appearance.colors.icon - } else if allowsPaymentMethodRemoval { - accessoryButton.isHidden = false - accessoryButton.set(style: .remove, with: appearance.colors.danger) - accessoryButton.backgroundColor = appearance.colors.danger - accessoryButton.iconColor = appearance.colors.danger.contrastingColor - } + if case .saved = viewModel, shouldAllowEditing { + accessoryButton.isHidden = false contentView.bringSubviewToFront(accessoryButton) applyDefaultStyle() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 2bbf324ec40..4b2e406271c 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -103,7 +103,6 @@ class SavedPaymentOptionsViewController: UIViewController { let isTestMode: Bool let allowsRemovalOfLastSavedPaymentMethod: Bool let allowsRemovalOfPaymentMethods: Bool - let alternateUpdatePaymentMethodNavigation: Bool } // MARK: - Internal Properties @@ -493,7 +492,7 @@ extension SavedPaymentOptionsViewController: UICollectionViewDataSource, UIColle stpAssertionFailure() return UICollectionViewCell() } - cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods, alternateUpdatePaymentMethodNavigation: self.configuration.alternateUpdatePaymentMethodNavigation) + cell.setViewModel(viewModel, cbcEligible: cbcEligible, allowsPaymentMethodRemoval: self.configuration.allowsRemovalOfPaymentMethods) cell.delegate = self cell.isRemovingPaymentMethods = self.collectionView.isRemovingPaymentMethods cell.appearance = appearance @@ -569,33 +568,6 @@ extension SavedPaymentOptionsViewController: PaymentOptionCellDelegate { self.bottomSheetController?.pushContentViewController(editVc) } - func paymentOptionCellDidSelectRemove( - _ paymentOptionCell: SavedPaymentMethodCollectionView.PaymentOptionCell - ) { - guard let indexPath = collectionView.indexPath(for: paymentOptionCell), - case .saved(let paymentMethod) = viewModels[indexPath.row] - else { - let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentSheetError, - error: Error.paymentOptionCellDidSelectRemoveOnNonSavedItem, - additionalNonPIIParams: [ - "indexPathRow": collectionView.indexPath(for: paymentOptionCell)?.row ?? "nil", - "viewModels": viewModels.map { $0.analyticsValue }, - ] - ) - STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) - stpAssertionFailure() - return - } - - let alertController = UIAlertController.makeRemoveAlertController(paymentMethod: paymentMethod, - removeSavedPaymentMethodMessage: configuration.removeSavedPaymentMethodMessage) { [weak self] in - guard let self = self else { return } - self.removePaymentMethod(paymentMethod) - } - - present(alertController, animated: true, completion: nil) - } - private func removePaymentMethod(_ paymentMethod: STPPaymentMethod) { guard let row = viewModels.firstIndex(where: { $0.savedPaymentMethod?.stripeId == paymentMethod.stripeId }) else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift index 0ca1096d569..660fe74741e 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift @@ -33,11 +33,7 @@ final class SavedPaymentMethodRowButton: UIView { } rowButton.isSelected = isSelected - rowButton.isEnabled = !isEditing || alternateUpdatePaymentMethodNavigation - chevronButton.isHidden = !canUpdate || !alternateUpdatePaymentMethodNavigation - updateButton.isHidden = !canUpdate || alternateUpdatePaymentMethodNavigation - removeButton.isHidden = !canRemove || alternateUpdatePaymentMethodNavigation - stackView.isUserInteractionEnabled = isEditing + chevronButton.isHidden = !canUpdate } } @@ -87,50 +83,23 @@ final class SavedPaymentMethodRowButton: UIView { // MARK: Private views - private lazy var removeButton: CircularButton = { - let removeButton = CircularButton(style: .remove, iconColor: .white) - removeButton.backgroundColor = appearance.colors.danger - removeButton.isHidden = true - removeButton.addTarget(self, action: #selector(handleRemoveButtonTapped), for: .touchUpInside) - return removeButton - }() - - private lazy var updateButton: CircularButton = { - let updateButton = CircularButton(style: .edit, iconColor: .white) - updateButton.backgroundColor = appearance.colors.icon - updateButton.isHidden = true - updateButton.addTarget(self, action: #selector(handleUpdateButtonTapped), for: .touchUpInside) - return updateButton - }() - private lazy var chevronButton: RowButton.RightAccessoryButton = { let chevronButton = RowButton.RightAccessoryButton(accessoryType: .update, appearance: appearance, didTap: handleUpdateButtonTapped) chevronButton.isHidden = true + chevronButton.isUserInteractionEnabled = isEditing return chevronButton }() - private lazy var stackView: UIStackView = { - let stackView = UIStackView.makeRowButtonContentStackView(arrangedSubviews: [chevronButton, updateButton, removeButton]) - // margins handled by the `RowButton` - stackView.directionalLayoutMargins = .zero - stackView.isUserInteractionEnabled = isEditing - return stackView - }() - private lazy var rowButton: RowButton = { - let button: RowButton = .makeForSavedPaymentMethod(paymentMethod: paymentMethod, appearance: appearance, rightAccessoryView: stackView, didTap: handleRowButtonTapped) + let button: RowButton = .makeForSavedPaymentMethod(paymentMethod: paymentMethod, appearance: appearance, rightAccessoryView: chevronButton, didTap: handleRowButtonTapped) return button }() - private let alternateUpdatePaymentMethodNavigation: Bool - init(paymentMethod: STPPaymentMethod, - appearance: PaymentSheet.Appearance, - alternateUpdatePaymentMethodNavigation: Bool = false) { + appearance: PaymentSheet.Appearance) { self.paymentMethod = paymentMethod self.appearance = appearance - self.alternateUpdatePaymentMethodNavigation = alternateUpdatePaymentMethodNavigation super.init(frame: .zero) addAndPinSubview(rowButton) @@ -150,7 +119,7 @@ final class SavedPaymentMethodRowButton: UIView { } @objc private func handleRowButtonTapped(_: RowButton) { - if alternateUpdatePaymentMethodNavigation && isEditing { + if isEditing { delegate?.didSelectUpdateButton(self, with: paymentMethod) } else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift index 99e66e644b6..e4831aa59b2 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift @@ -51,7 +51,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { paymentMethodRows.forEach { let allowsRemoval = canRemovePaymentMethods let paymentMethodType = $0.paymentMethod.type - let allowsUpdating = ($0.paymentMethod.isCoBrandedCard && isCBCEligible) || (configuration.alternateUpdatePaymentMethodNavigation && (UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in paymentMethodType == type })) + let allowsUpdating = UpdatePaymentMethodViewModel.supportedPaymentMethods.contains { type in paymentMethodType == type } $0.state = .editing(allowsRemoval: allowsRemoval, allowsUpdating: allowsUpdating) } @@ -90,7 +90,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { var canEdit: Bool { // We can edit if there are removable or editable payment methods and we are not in remove only mode // Or, under the new navigation flow, if any of the payment methods are cards, US bank accounts, or SEPA debit - return ((canRemovePaymentMethods || (hasCoBrandedCards && isCBCEligible)) && !isRemoveOnlyMode) || (configuration.alternateUpdatePaymentMethodNavigation && paymentMethods.contains { UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) }) + return !isRemoveOnlyMode && paymentMethods.contains { UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) } } private var selectedPaymentMethod: STPPaymentMethod? { @@ -171,15 +171,10 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { self.paymentMethodRemove = elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() self.isCBCEligible = elementsSession.isCardBrandChoiceEligible self.analyticsHelper = analyticsHelper - if configuration.alternateUpdatePaymentMethodNavigation { - self.isRemoveOnlyMode = false - } - else { - // Put in remove only mode and don't show the option to update PMs if: - // 1. We only have 1 payment method - // 2. The customer can't update the card brand - self.isRemoveOnlyMode = paymentMethods.count == 1 && (!paymentMethods[0].isCoBrandedCard || !isCBCEligible) - } + // Put in remove only mode and don't show the option to update PMs if: + // 1. We only have 1 payment method + // 2. The customer can't update the card brand + self.isRemoveOnlyMode = paymentMethods.count == 1 && (!paymentMethods[0].isCoBrandedCard || !isCBCEligible) super.init(nibName: nil, bundle: nil) self.paymentMethodRows = buildPaymentMethodRows(paymentMethods: paymentMethods) setInitialState(selectedPaymentMethod: selectedPaymentMethod) @@ -188,8 +183,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private func buildPaymentMethodRows(paymentMethods: [STPPaymentMethod]) -> [SavedPaymentMethodRowButton] { return paymentMethods.map { paymentMethod in let button = SavedPaymentMethodRowButton(paymentMethod: paymentMethod, - appearance: configuration.appearance, - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + appearance: configuration.appearance) button.delegate = self return button } @@ -388,7 +382,7 @@ extension VerticalSavedPaymentMethodsViewController: UpdatePaymentMethodViewCont } // Create the new button - let newButton = SavedPaymentMethodRowButton(paymentMethod: updatedPaymentMethod, appearance: configuration.appearance, alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation) + let newButton = SavedPaymentMethodRowButton(paymentMethod: updatedPaymentMethod, appearance: configuration.appearance) newButton.delegate = self newButton.previousSelectedState = oldButton.previousSelectedState newButton.state = oldButton.state diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift index 6294faea7d5..ebb367770b8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetFlowControllerViewController.swift @@ -206,8 +206,7 @@ class PaymentSheetFlowControllerViewController: UIViewController, FlowController isCVCRecollectionEnabled: false, isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: PaymentSheetViewController.allowsRemovalOfLastPaymentMethod(elementsSession: elementsSession, configuration: configuration), - allowsRemovalOfPaymentMethods: elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + allowsRemovalOfPaymentMethods: elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() ), paymentSheetConfiguration: configuration, intent: intent, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index df40729ae11..59d7c70479f 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -569,11 +569,9 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo @objc func presentManageScreen() { error = nil - // Special case, only 1 card remaining but is co-branded (or alternateUpdatePaymentMethodNavigation), skip showing the list and show update view controller + // Special case, only 1 card remaining, skip showing the list and show update view controller if savedPaymentMethods.count == 1, - let paymentMethod = savedPaymentMethods.first, - paymentMethod.isCoBrandedCard, - elementsSession.isCardBrandChoiceEligible || configuration.alternateUpdatePaymentMethodNavigation { + let paymentMethod = savedPaymentMethods.first { let updateViewModel = UpdatePaymentMethodViewModel(paymentMethod: paymentMethod, appearance: configuration.appearance, hostedSurface: .paymentSheet, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift index 29da3db04a6..bd97053d0f9 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetViewController.swift @@ -174,8 +174,7 @@ class PaymentSheetViewController: UIViewController, PaymentSheetViewControllerPr isCVCRecollectionEnabled: isCVCRecollectionEnabled, isTestMode: configuration.apiClient.isTestmode, allowsRemovalOfLastSavedPaymentMethod: PaymentSheetViewController.allowsRemovalOfLastPaymentMethod(elementsSession: elementsSession, configuration: configuration), - allowsRemovalOfPaymentMethods: loadResult.elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet(), - alternateUpdatePaymentMethodNavigation: configuration.alternateUpdatePaymentMethodNavigation + allowsRemovalOfPaymentMethods: loadResult.elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() ), paymentSheetConfiguration: configuration, intent: intent, diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift index da2caaa23d1..39d782ab68e 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewControllerTests.swift @@ -296,8 +296,7 @@ class CustomerSavedPaymentMethodsCollectionViewControllerTests: XCTestCase { return CustomerSavedPaymentMethodsCollectionViewController.Configuration(showApplePay: false, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, paymentMethodRemove: paymentMethodRemove, - isTestMode: true, - alternateUpdatePaymentMethodNavigation: false) + isTestMode: true) } func customerSavedPaymentMethods(_ configuration: CustomerSavedPaymentMethodsCollectionViewController.Configuration, savedPaymentMethods: [STPPaymentMethod], diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift index 7e4794aba79..bdba1b9a487 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerSnapshotTests.swift @@ -32,7 +32,7 @@ final class SavedPaymentOptionsViewControllerSnapshotTests: STPSnapshotTestCase STPPaymentMethod._testUSBankAccount(), STPPaymentMethod._testSEPA(), ] - let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true, alternateUpdatePaymentMethodNavigation: false) + let config = SavedPaymentOptionsViewController.Configuration(customerID: "cus_123", showApplePay: true, showLink: true, removeSavedPaymentMethodMessage: nil, merchantDisplayName: "Test Merchant", isCVCRecollectionEnabled: false, isTestMode: false, allowsRemovalOfLastSavedPaymentMethod: false, allowsRemovalOfPaymentMethods: true) let intent = Intent.deferredIntent(intentConfig: .init(mode: .payment(amount: 0, currency: "USD", setupFutureUsage: nil, captureMethod: .automatic), confirmHandler: { _, _, _ in })) let sut = SavedPaymentOptionsViewController(savedPaymentMethods: paymentMethods, configuration: config, diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift index d8f97611e0a..9dec04ccc72 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/SavedPaymentOptionsViewControllerTests.swift @@ -303,8 +303,7 @@ class SavedPaymentOptionsViewControllerTests: XCTestCase { isCVCRecollectionEnabled: true, isTestMode: true, allowsRemovalOfLastSavedPaymentMethod: allowsRemovalOfLastSavedPaymentMethod, - allowsRemovalOfPaymentMethods: allowsRemovalOfPaymentMethods, - alternateUpdatePaymentMethodNavigation: false) + allowsRemovalOfPaymentMethods: allowsRemovalOfPaymentMethods) } func savedPaymentOptionsController(_ configuration: SavedPaymentOptionsViewController.Configuration, From 8d1c90bdb01e53832dfd252ba6de395e2308ee4b Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 16:54:59 -0800 Subject: [PATCH 11/26] update snapshot tests --- ...wButton_editing_canRemove_canUpdate@3x.png | Bin 17089 -> 8435 bytes ...Button_editing_canRemove_cantUpdate@3x.png | Bin 12340 -> 8157 bytes ...Button_editing_cantRemove_canUpdate@3x.png | Bin 13473 -> 8435 bytes ...wButton_newPaymentMethod_unselected@3x.png | Bin 7836 -> 7824 bytes ...wPaymentMethod_withPromo_unselected@3x.png | Bin 11416 -> 11404 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 38455 -> 34445 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 38206 -> 34196 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_canUpdate@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_canUpdate@3x.png index 9332e94500014322f46b207077eaa6643d247e56..6e7936fb11416bbbd3a1294974b99268e654525f 100644 GIT binary patch literal 8435 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 F{6871K-d5P diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_cantUpdate@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_cantUpdate@3x.png index b53141db515cf8b41ff63bdf948ba4b24072cfa5..20c39508cda4dba6bebc575cfcbd6c2d9a2ebfcb 100644 GIT binary patch literal 8157 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 XzylV{gT;Uh9Yf)cs!XA@>Er(aqLIiT literal 12340 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 Qukw1CXNnTVU_-zE0m?%tYybcN diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_cantRemove_canUpdate@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_cantRemove_canUpdate@3x.png index a1cf81691cef91d4b28cb645a7db96e81a89d570..6e7936fb11416bbbd3a1294974b99268e654525f 100644 GIT binary patch literal 8435 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 R@$-Lq6lK+9D#0c%{~yM6Wi9{! diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_unselected@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_unselected@3x.png index 838eafa6586a6adade5c9358b714a8b741b6cf4b..d0ea7bb2be25da99c6fce1222e8734cc9afa8e69 100644 GIT binary patch delta 82 zcmbPZJHd8>p?ywjglC$sFM}2X0|N&G3!@YRE0Dzq#CD9*aJCzx1_Lu#oQZ*W|(jS)-g+f1{Kn_kHR0Qcw(CIA2c delta 94 zcmbPWJI8i{p?5`UglC$sFM}2X0|N&GE29ttGmymygba*Q46I-_1A`Z%G@Kp8r~y^O a#K6#=$-n|t6E(5Gh-H;dM8(EsuVev8W(^ns diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_withPromo_unselected@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_withPromo_unselected@3x.png index 6142a4d44cfd3d1eb2e10f5f6a1f05d5fdc75b60..9170b468bdc1c10329fb3cc8e5123a51b42bde13 100644 GIT binary patch delta 82 zcmbOc*%LXz&^{+M!ZXd+mqCkxfq{d8g;9!u706-)Vmn4@INOa;gMk?=&cwjbp2@%h TRpU3Y#)u{LZKm1AP0zFe2K5bK delta 94 zcmeB)oDn&}(7Pfv!ZXd+mqCkxfq{d8l~IU+8OUM;LIy@D239befx(MW8qN-4)PSmC aVqj>`WMF}+iJDko#Ii~!qGIE+XW9T+qzzI4 diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png index f2760aa40c8bcdfa30bb9ca7a5911b75377e9c17..713570cb4f801e610ce2731d679aa134087dc459 100644 GIT binary patch delta 15631 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 diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png index 306782b900dcccc9a1b4f232d8f203d0d0d1c3d0..6a87abf13f600c804523a183d23c87db89d7a221 100644 GIT binary patch delta 15598 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 Tb>s)`Kcinv4`&#h`|W=KI5xb2 delta 19642 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 K*(a~v`Tqc3a<=0D From c70c6a1e6654ffdd247642761be87dbed476b987 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 16:57:48 -0800 Subject: [PATCH 12/26] update row button chevron condition --- .../SavedPaymentMethodRowButton.swift | 2 +- ...Button_editing_canRemove_cantUpdate@3x.png | Bin 8157 -> 8435 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 34445 -> 34748 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 34196 -> 34499 bytes 4 files changed, 1 insertion(+), 1 deletion(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift index 660fe74741e..2f06197eff7 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift @@ -33,7 +33,7 @@ final class SavedPaymentMethodRowButton: UIView { } rowButton.isSelected = isSelected - chevronButton.isHidden = !canUpdate + chevronButton.isHidden = !canUpdate && !canRemove } } diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_cantUpdate@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_editing_canRemove_cantUpdate@3x.png index 20c39508cda4dba6bebc575cfcbd6c2d9a2ebfcb..6e7936fb11416bbbd3a1294974b99268e654525f 100644 GIT binary patch literal 8435 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 XzylV{gT;Uh9Yf)cs!XA@>Er(aqLIiT diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png index 713570cb4f801e610ce2731d679aa134087dc459..4cdea560c9721b0135de2451e5ebd899e6de44de 100644 GIT binary patch delta 15327 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

ho%y zThzHHvp!MufZq9pa{hXA-|;0G7_^3TN7XT|)j|v}ndD&PLKpU^F~+pfS|vRs(}cVQi2s~Tt|9yyN>_;v=#y(3-8w-&858fQdb{g0%{l@3^HN5Wq=0a z)KE^@#N9!MZno7n2FXXY8N%Q;L@}Y*#Q-ME?e9B5bW2R6vr0p)@RjLf?>mBIq}9FO zP9Kl55&@UAaCvXIAt=|oKCNbA)wLDyq728i-r^D@S2((SW;oRz>%VjxNSHf32$12< z2{|@d8C(|;C7P#my|?BC$b*iz2k}^bG~U@9gP4558@4L!j_p6}bysI*;6t<)kU*d9 ztsyc4a;eA$4!bRRv-b5>8>6-~MVLP+?r|#1CevhGNEiHy4s{f)7HBI&kIh$ZsgK|Z z*V-g|FftgRi`}nuXgvNp&b@;xb9NL$w&0kjT?Hf0E%|IHe1+5d=D8kQk0+szLqx9cUE?b+9sXhXi(BJUWddt6zy}C7=m4XRVRXTg8 zmO)jxr86@Pf|ihHq_YORaT)VUiPMn=MP6~7!aOH`KT{$E-bjfe*+YBuEqq3HyAgbq zsJ)z-d00W!O$|^5q0)#;YTQ6eJMVo}McQDorOpiYgO(Q5!e`WF17f({O$eHaMK)|q zlPulq;AsZ}Q-#dZE%(8blhm_QZm!hX0pE;`5w~yp!uG({8ExhVX?}R?Z-mY-**r8F z(y&juB)VBty=tJ+QkgmusIq7k4uuIG28_1Kz$FS)Bljtr*_Bl4h`$PY&@xJ4H}RYQ zZpt5a=r-7pYAjd|s{_x)7rfW^;tWN%%IZmBua#Y!sccD$-=lnDV3qkZ?XPF>qxbOO3ABM^``wW2#;!L7ZC&P=$$cBM%*o*;iiOn1Pf%^n3HRJ(m`GpugI5eSl|ip`sHq|>(4p( zW~)YIl%5te+!=U6cAlDXo65AVyN?nu8^R^D#>3T$t|rZV2NCO29KB3zoE?K3>3 zdZBbS*&5wcw8mV3Rq^JNvkjaoMKOm(IvhtocI(@LfJ6L2&;$Ek9K%L{SyO)}c+F>4 zg;%Y51-+7I&tOq8^uoK+gT)sG>LUZq8}AU^dHxbZ67oK|d+(J#|Fe3_4tJw$vuh$M zrY1Grnc^?10U%;3-s|}Fm8za9WvE*-n=otl`pla#`jUs@7aJJkcP)Mkyj~rW#!+pG zgVqMI)Mc#K#^`0x2U_JJcJUv}JO|Nk$vTM6_rC|Zd%mhgoa9%`T>q2tmi!+)^@okK?y8m>=e;52_?`72YuMPi0NBq}q{&kyw zv(0~!vj2^geCpYn;d;r^JR$K1M;XV!r~r%#+c Ko}*!P`~LvfKfBQY delta 14434 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 delta 14404 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 Date: Thu, 5 Dec 2024 21:15:04 -0800 Subject: [PATCH 13/26] remove removeonly mode, update tests --- .../PaymentSheetUITest/EmbeddedUITest.swift | 16 ++-- .../PaymentSheetUITest.swift | 6 +- .../PaymentSheetVerticalUITest.swift | 10 +-- ...ymentMethodsCollectionViewController.swift | 33 -------- .../SavedPaymentMethodCollectionView.swift | 12 +-- .../SavedPaymentOptionsViewController.swift | 7 +- .../SavedPaymentMethodRowButton.swift | 5 -- ...calSavedPaymentMethodsViewController.swift | 45 +++------- .../Views/PaymentSheetUIKitAdditions.swift | 14 ---- ...vedPaymentMethodsViewControllerTests.swift | 77 ++---------------- ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 34748 -> 30017 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 34499 -> 29825 bytes 12 files changed, 43 insertions(+), 182 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift index 81c9ea1e53c..5b80f58a536 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift @@ -211,7 +211,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is NOT on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) - app.buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -279,7 +279,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) - app.buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Cartes Bancaires •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -311,7 +311,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Switch from 1001 to 4242 app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.buttons["chevron"].waitForExistenceAndTap() app.otherElements["Card Brand Dropdown"].waitForExistenceAndTap() app.pickerWheels.firstMatch.swipeUp() app.buttons["Done"].waitForExistenceAndTap() @@ -357,7 +357,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove selected 4242 card app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") app.buttons["Done"].waitForExistenceAndTap() @@ -370,7 +370,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 6789 & verify app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Bank account •••• 6789", alertTitle: "Remove bank account?", buttonToTap: "Remove") @@ -429,7 +429,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove bank acct. while it isn't selected app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Bank account •••• 6789", alertTitle: "Remove bank account?", buttonToTap: "Remove") app.buttons["Done"].waitForExistenceAndTap() @@ -443,7 +443,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 4242 app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -488,7 +488,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Delete one payment method so we only have one left, we should not auto select the last remaining saved PM XCTAssertTrue(app.buttons["View more"].waitForExistenceAndTap()) XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) - XCTAssertTrue(app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap()) + XCTAssertTrue(app.buttons["chevron"].firstMatch.waitForExistenceAndTap()) XCTAssertTrue(app.buttons["Remove"].waitForExistenceAndTap()) dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") XCTAssertTrue(app.buttons["Done"].waitForExistenceAndTap()) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index 71fa1d51ebe..61d455462ff 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -330,8 +330,7 @@ class PaymentSheetStandardUITests: PaymentSheetUITestCase { XCTAssertTrue(editButton.waitForExistence(timeout: 60.0)) editButton.tap() - let circularEditButton = app.buttons["CircularButton.Edit"] - circularEditButton.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].waitForExistenceAndTap() let removeButton = app.buttons["Remove"] XCTAssertTrue(removeButton.waitForExistence(timeout: 60.0)) @@ -1172,8 +1171,7 @@ class PaymentSheetDeferredServerSideUITests: PaymentSheetUITestCase { XCTAssertTrue(editButton.waitForExistence(timeout: 60.0)) editButton.tap() - let circularEditButton = app.buttons["CircularButton.Edit"] - circularEditButton.waitForExistenceAndTap() + app.buttons["CircularButton.Edit"].waitForExistenceAndTap() let removeButton = app.buttons["Remove"] XCTAssertTrue(removeButton.waitForExistence(timeout: 60.0)) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift index 74d6103da4c..6ba5d8fecab 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift @@ -225,7 +225,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { app.buttons["View more"].waitForExistenceAndTap() XCTAssertTrue(firstPaymentMethod.isSelected) app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() app.alerts.buttons["Remove"].waitForExistenceAndTap() XCTAssertFalse(firstPaymentMethod.exists) @@ -244,18 +244,18 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) // Remove the 4242 card - app.otherElements["•••• 4242"].buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.otherElements["•••• 4242"].buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) // Exit edit mode, remove button should be hidden XCTAssertTrue(app.buttons["Done"].waitForExistenceAndTap()) - XCTAssertFalse(app.buttons["CircularButton.Edit"].waitForExistence(timeout: 2.0)) + XCTAssertFalse(app.buttons["chevron"].waitForExistence(timeout: 2.0)) // Update the card brand on the last card XCTAssertTrue(app.buttons["Cartes Bancaires ending in 1 0 0 1"].waitForExistence(timeout: 1.0)) // Cartes Bancaires card should be selected now that 4242 card is removed XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) - app.buttons["CircularButton.Edit"].firstMatch.waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() // Should present the update card view controller XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 2.0)) @@ -278,7 +278,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { // Reselect edit icon and delete the card from the update view controller app.buttons["Edit"].firstMatch.waitForExistenceAndTap() - app.buttons["CircularButton.Edit"].waitForExistenceAndTap() + app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index 810aa01d43a..c9961149407 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -444,39 +444,6 @@ extension CustomerSavedPaymentMethodsCollectionViewController: PaymentOptionCell self.bottomSheetController?.pushContentViewController(editVc) } - func paymentOptionCellDidSelectRemove( - _ paymentOptionCell: SavedPaymentMethodCollectionView.PaymentOptionCell - ) { - guard let indexPath = collectionView.indexPath(for: paymentOptionCell), - case .saved(let paymentMethod) = viewModels[indexPath.row] - else { - let errorAnalytic = ErrorAnalytic(event: .unexpectedCustomerSheetError, - error: Error.didSelectRemoveOnInvalidItem) - STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) - stpAssertionFailure() - return - } - let alert = UIAlertAction( - title: String.Localized.remove, style: .destructive - ) { (_) in - self.removePaymentMethod(indexPath: indexPath, paymentMethod: paymentMethod) - } - let cancel = UIAlertAction( - title: String.Localized.cancel, - style: .cancel, handler: nil - ) - - let alertController = UIAlertController( - title: paymentMethod.removalMessage.title, - message: self.savedPaymentMethodsConfiguration.removeSavedPaymentMethodMessage ?? paymentMethod.removalMessage.message, - preferredStyle: .alert - ) - - alertController.addAction(cancel) - alertController.addAction(alert) - present(alertController, animated: true, completion: nil) - } - private func removePaymentMethod(indexPath: IndexPath, paymentMethod: STPPaymentMethod) { Task { guard let delegate = self.delegate else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index af6082dd479..33a293f8c20 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -114,13 +114,13 @@ extension SavedPaymentMethodCollectionView { var cbcEligible: Bool = false var allowsPaymentMethodRemoval: Bool = true - /// Indicates whether the cell should display the edit icon. - /// True if supported saved pm (card, US bank account, or SEPA debit) - var shouldAllowEditing: Bool { + /// Indicates whether the cell for a saved payment method should display the edit icon. + /// True if payment methods can be removed or edited (will update this to include allowing set as default) + var showEditIcon: Bool { guard UpdatePaymentMethodViewModel.supportedPaymentMethods.contains(where: { viewModel?.savedPaymentMethod?.type == $0 }) else { fatalError("Payment method does not match supported saved payment methods.") } - return true + return allowsPaymentMethodRemoval || (viewModel?.savedPaymentMethod?.isCoBrandedCard ?? false && cbcEligible) } // MARK: - UICollectionViewCell @@ -241,7 +241,7 @@ extension SavedPaymentMethodCollectionView { // MARK: - Private Methods @objc private func didSelectAccessory() { - if shouldAllowEditing { + if showEditIcon { delegate?.paymentOptionCellDidSelectEdit(self) } } @@ -328,7 +328,7 @@ extension SavedPaymentMethodCollectionView { } if isRemovingPaymentMethods { - if case .saved = viewModel, shouldAllowEditing { + if case .saved = viewModel, showEditIcon { accessoryButton.isHidden = false contentView.bringSubviewToFront(accessoryButton) applyDefaultStyle() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 4b2e406271c..490a16ea32a 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -34,7 +34,6 @@ class SavedPaymentOptionsViewController: UIViewController { case collectionViewDidSelectItemAtAdd case unableToDequeueReusableCell case paymentOptionCellDidSelectEditOnNonSavedItem - case paymentOptionCellDidSelectRemoveOnNonSavedItem case removePaymentMethodOnNonSavedItem } // MARK: - Types @@ -572,7 +571,11 @@ extension SavedPaymentOptionsViewController: PaymentOptionCellDelegate { guard let row = viewModels.firstIndex(where: { $0.savedPaymentMethod?.stripeId == paymentMethod.stripeId }) else { let errorAnalytic = ErrorAnalytic(event: .unexpectedPaymentSheetError, - error: Error.removePaymentMethodOnNonSavedItem) + error: Error.removePaymentMethodOnNonSavedItem, + additionalNonPIIParams: [ + "viewModels": viewModels.map { $0.analyticsValue }, + ] + ) STPAnalyticsClient.sharedClient.log(analytic: errorAnalytic) stpAssertionFailure() return diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift index 2f06197eff7..c8dc6068aa8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift @@ -13,7 +13,6 @@ import UIKit protocol SavedPaymentMethodRowButtonDelegate: AnyObject { func didSelectButton(_ button: SavedPaymentMethodRowButton, with paymentMethod: STPPaymentMethod) - func didSelectRemoveButton(_ button: SavedPaymentMethodRowButton, with paymentMethod: STPPaymentMethod) func didSelectUpdateButton(_ button: SavedPaymentMethodRowButton, with paymentMethod: STPPaymentMethod) } @@ -114,10 +113,6 @@ final class SavedPaymentMethodRowButton: UIView { delegate?.didSelectUpdateButton(self, with: paymentMethod) } - @objc private func handleRemoveButtonTapped() { - delegate?.didSelectRemoveButton(self, with: paymentMethod) - } - @objc private func handleRowButtonTapped(_: RowButton) { if isEditing { delegate?.didSelectUpdateButton(self, with: paymentMethod) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift index e4831aa59b2..f16823b3e5c 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift @@ -70,10 +70,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { } private var headerText: String { - if isRemoveOnlyMode { - return .Localized.remove_payment_method - } - if isEditingPaymentMethods { return paymentMethods.count == 1 ? .Localized.manage_payment_method : .Localized.manage_payment_methods } @@ -87,10 +83,14 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { return (paymentMethodRows.count > 1 ? true : configuration.allowsRemovalOfLastSavedPaymentMethod) && paymentMethodRemove } - var canEdit: Bool { - // We can edit if there are removable or editable payment methods and we are not in remove only mode - // Or, under the new navigation flow, if any of the payment methods are cards, US bank accounts, or SEPA debit - return !isRemoveOnlyMode && paymentMethods.contains { UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) } + /// Indicates whether the chevron should be shown + /// True if any saved payment methods can be removed or edited (will update this to include allowing set as default) + var canRemoveOrEdit: Bool { + let hasSupportedSavedPaymentMethods = paymentMethods.allSatisfy{ UpdatePaymentMethodViewModel.supportedPaymentMethods.contains($0.type) } + guard hasSupportedSavedPaymentMethods else { + fatalError("Saved payment methods contain unsupported payment methods.") + } + return canRemovePaymentMethods || (hasCoBrandedCards && isCBCEligible) } private var selectedPaymentMethod: STPPaymentMethod? { @@ -109,12 +109,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { SavedPaymentMethodManager(configuration: configuration, elementsSession: elementsSession) }() - /// Determines if the we should operate in "Remove Only Mode". This mode is enabled under the following conditions: - /// - There is exactly one payment method available at init time. - /// - The single available payment method is not a co-branded card. - /// In this mode, the user can only delete the payment method; updating or selecting other payment methods is disabled. - let isRemoveOnlyMode: Bool - // MARK: Internal properties weak var delegate: VerticalSavedPaymentMethodsViewControllerDelegate? @@ -171,10 +165,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { self.paymentMethodRemove = elementsSession.allowsRemovalOfPaymentMethodsForPaymentSheet() self.isCBCEligible = elementsSession.isCardBrandChoiceEligible self.analyticsHelper = analyticsHelper - // Put in remove only mode and don't show the option to update PMs if: - // 1. We only have 1 payment method - // 2. The customer can't update the card brand - self.isRemoveOnlyMode = paymentMethods.count == 1 && (!paymentMethods[0].isCoBrandedCard || !isCBCEligible) super.init(nibName: nil, bundle: nil) self.paymentMethodRows = buildPaymentMethodRows(paymentMethods: paymentMethods) setInitialState(selectedPaymentMethod: selectedPaymentMethod) @@ -191,9 +181,6 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private func setInitialState(selectedPaymentMethod: STPPaymentMethod?) { paymentMethodRows.first { $0.paymentMethod.stripeId == selectedPaymentMethod?.stripeId }?.state = .selected - if isRemoveOnlyMode { - paymentMethodRows.first?.state = .editing(allowsRemoval: canRemovePaymentMethods, allowsUpdating: false) - } } required init?(coder: NSCoder) { @@ -216,9 +203,9 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { private func navigationBarStyle() -> SheetNavigationBar.Style { if let bottomSheet = self.bottomSheetController, bottomSheet.contentStack.count > 1 { - return .back(showAdditionalButton: canEdit) + return .back(showAdditionalButton: canRemoveOrEdit) } else { - return .close(showAdditionalButton: canEdit) + return .close(showAdditionalButton: canRemoveOrEdit) } } @@ -244,7 +231,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { } // Update the editing state if needed - isEditingPaymentMethods = canEdit + isEditingPaymentMethods = canRemoveOrEdit // If we deleted the last payment method kick back out to the main screen if paymentMethodRows.isEmpty { @@ -327,16 +314,6 @@ extension VerticalSavedPaymentMethodsViewController: SavedPaymentMethodRowButton self.complete() } - func didSelectRemoveButton(_ button: SavedPaymentMethodRowButton, with paymentMethod: STPPaymentMethod) { - let alertController = UIAlertController.makeRemoveAlertController(paymentMethod: paymentMethod, - removeSavedPaymentMethodMessage: configuration.removeSavedPaymentMethodMessage) { [weak self] in - guard let self else { return } - self.remove(paymentMethod: paymentMethod) - } - - present(alertController, animated: true, completion: nil) - } - func didSelectUpdateButton(_ button: SavedPaymentMethodRowButton, with paymentMethod: STPPaymentMethod) { let updateViewModel = UpdatePaymentMethodViewModel(paymentMethod: paymentMethod, appearance: configuration.appearance, diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/PaymentSheetUIKitAdditions.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/PaymentSheetUIKitAdditions.swift index 270d7230563..a5da0ebdb81 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/PaymentSheetUIKitAdditions.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Views/PaymentSheetUIKitAdditions.swift @@ -158,17 +158,3 @@ extension UIFont { return UIFont(descriptor: descriptor, size: pointSize) } } - -extension UIStackView { - /// Convenience DRY method that creates a stackview for use in horizontal "row button" content - static func makeRowButtonContentStackView(arrangedSubviews: [UIView]) -> UIStackView { - let margin = 12.0 - let stackView = UIStackView(arrangedSubviews: arrangedSubviews) - stackView.axis = .horizontal - stackView.alignment = .center - stackView.directionalLayoutMargins = .init(top: margin, leading: margin, bottom: margin, trailing: margin) - stackView.spacing = margin - stackView.isLayoutMarginsRelativeArrangement = true - return stackView - } -} diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift index 8e832a296fa..5a4a4f8ea1b 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift @@ -103,7 +103,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: paymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertTrue(viewController.canEdit) + XCTAssertTrue(viewController.canRemoveOrEdit) } func testCanEdit_singlePaymentMethod_returnsFalse() { @@ -113,9 +113,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertFalse(viewController.canEdit) - // Should be in remove only mode - XCTAssertTrue(viewController.isRemoveOnlyMode) + XCTAssertFalse(viewController.canRemoveOrEdit) } func testCanEdit_singleRemovableCoBrandedCard_returnsFalse() { @@ -125,8 +123,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertFalse(viewController.canEdit) // Can't edit, merchant is not eligible for CBC - XCTAssertTrue(viewController.isRemoveOnlyMode) // Only operation we can make with a single payment method in this case is remove + XCTAssertFalse(viewController.canRemoveOrEdit) // Can't edit, merchant is not eligible for CBC } func testCanEdit_singlePaymentMethod_disallowsLastRemoval_returnsFalse() { @@ -137,7 +134,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertFalse(viewController.canEdit) + XCTAssertFalse(viewController.canRemoveOrEdit) } func testCanEdit_oneEditablePaymentMethod_disallowsLastRemoval_notCBCEligible_returnsFalse() { @@ -148,7 +145,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertFalse(viewController.canEdit) + XCTAssertFalse(viewController.canRemoveOrEdit) } func testCanEdit_oneEditablePaymentMethod_disallowsLastRemoval_isCBCEligible_returnsFalse() { @@ -163,69 +160,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { "eligible": true]), analyticsHelper: ._testValue() ) - XCTAssertTrue(viewController.canEdit) - } - - // MARK: Remove only mode - - func testIsRemoveOnlyMode_singlePaymentMethod_isNotCBCEligible_returnsTrue() { - configuration.allowsRemovalOfLastSavedPaymentMethod = true - let singlePaymentMethods = [STPPaymentMethod._testCard()] - let viewController = VerticalSavedPaymentMethodsViewController( - configuration: configuration, - selectedPaymentMethod: singlePaymentMethods.first, - paymentMethods: singlePaymentMethods, - elementsSession: ._testValue(paymentMethodTypes: ["card"]), - analyticsHelper: ._testValue() - ) - // The card is NOT co-branded and, we can't edit, enter remove only mode - XCTAssertTrue(viewController.isRemoveOnlyMode) - } - - func testIsRemoveOnlyMode_singlePaymentMethod_isCBCEligible_returnsTrue() { - configuration.allowsRemovalOfLastSavedPaymentMethod = true - let singlePaymentMethods = [STPPaymentMethod._testCard()] - let viewController = VerticalSavedPaymentMethodsViewController( - configuration: configuration, - selectedPaymentMethod: singlePaymentMethods.first, - paymentMethods: singlePaymentMethods, - elementsSession: ._testValue(paymentMethodTypes: ["card"], - cardBrandChoiceData: ["eligible": true]), - analyticsHelper: ._testValue() - ) - // The card is NOT co-branded and, we can't edit, enter remove only mode - XCTAssertTrue(viewController.isRemoveOnlyMode) - } - - func testIsRemoveOnlyMode_singleCobrandedPaymentMethod_isCBCEligible_returnsFalse() { - configuration.allowsRemovalOfLastSavedPaymentMethod = true - let singlePaymentMethods = [STPPaymentMethod._testCardCoBranded()] - let viewController = VerticalSavedPaymentMethodsViewController( - configuration: configuration, - selectedPaymentMethod: singlePaymentMethods.first, - paymentMethods: singlePaymentMethods, - elementsSession: ._testValue(paymentMethodTypes: ["card"], - cardBrandChoiceData: ["eligible": true]), - analyticsHelper: ._testValue() - ) - - // The card is co-branded and the merchant is CBC eligible, we can edit, don't enter remove only mode - XCTAssertFalse(viewController.isRemoveOnlyMode) - } - - func testIsRemoveOnlyMode_singleCobrandedPaymentMethod_isNotCBCEligible_returnsFalse() { - configuration.allowsRemovalOfLastSavedPaymentMethod = true - let singlePaymentMethods = [STPPaymentMethod._testCardCoBranded()] - let viewController = VerticalSavedPaymentMethodsViewController( - configuration: configuration, - selectedPaymentMethod: singlePaymentMethods.first, - paymentMethods: singlePaymentMethods, - elementsSession: ._testValue(paymentMethodTypes: ["card"]), - analyticsHelper: ._testValue() - ) - - // The card is co-branded but the merchant is NOT CBC eligible, we can't edit, enter remove only mode - XCTAssertTrue(viewController.isRemoveOnlyMode) + XCTAssertTrue(viewController.canRemoveOrEdit) } } diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png index 4cdea560c9721b0135de2451e5ebd899e6de44de..c29dbb1e17e42cbfd644b63fa12f157b93d4a405 100644 GIT binary patch literal 30017 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 literal 34748 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 diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png index 2b53b4e9d6157e96e1db81caf49dd8819af74eee..d6cd8ef1bab2a9389dc655a45796ac733be7eef1 100644 GIT binary patch literal 29825 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 K&2)9Efd2sn!8A(% From ac5189af4fd3e67fbac99e73230d96b9524a015d Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 22:22:05 -0800 Subject: [PATCH 14/26] fixing tests --- .../PaymentSheetUITest/EmbeddedUITest.swift | 10 ++++------ .../Source/Categories/String+Localized.swift | 7 ------- ...erSavedPaymentMethodsCollectionViewController.swift | 1 - .../SavedPaymentMethodCollectionView.swift | 5 +---- 4 files changed, 5 insertions(+), 18 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift index 5b80f58a536..870f59e8368 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift @@ -311,7 +311,7 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Switch from 1001 to 4242 app.buttons["View more"].waitForExistenceAndTap() app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["chevron"].waitForExistenceAndTap() + app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.otherElements["Card Brand Dropdown"].waitForExistenceAndTap() app.pickerWheels.firstMatch.swipeUp() app.buttons["Done"].waitForExistenceAndTap() @@ -370,7 +370,6 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 6789 & verify app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Bank account •••• 6789", alertTitle: "Remove bank account?", buttonToTap: "Remove") @@ -379,13 +378,13 @@ class EmbeddedUITests: PaymentSheetUITestCase { XCTAssertFalse(app.textViews["By continuing, you agree to authorize payments pursuant to these terms."].waitForExistence(timeout: 3.0)) let events = analyticsLog.compactMap({ $0[string: "event"] }) .filter({ !$0.starts(with: "luxe") }) - .suffix(5) + .suffix(7) XCTAssertEqual( events, ["mc_embedded_paymentoption_savedpm_select", - "mc_carousel_payment_method_tapped", "mc_embedded_paymentoption_removed", - "mc_carousel_payment_method_tapped", "mc_embedded_paymentoption_removed", + "mc_carousel_payment_method_tapped", "mc_open_edit_screen", "mc_embedded_paymentoption_removed", + "mc_carousel_payment_method_tapped", "mc_open_edit_screen", "mc_embedded_paymentoption_removed", ] ) } @@ -443,7 +442,6 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove 4242 app.buttons["Edit"].waitForExistenceAndTap() - app.buttons["chevron"].firstMatch.waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 4242", alertTitle: "Remove card?", buttonToTap: "Remove") diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift index 97ed0d3b54f..e3bab126f25 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Categories/String+Localized.swift @@ -349,13 +349,6 @@ extension String.Localized { ) } - static var remove_payment_method: String { - STPLocalizedString( - "Remove payment method", - "Title shown above a view containing a customer's payment method that they can delete" - ) - } - static var view_more: String { STPLocalizedString( "View more", diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift index c9961149407..005e56937d4 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSavedPaymentMethodsCollectionViewController.swift @@ -40,7 +40,6 @@ protocol CustomerSavedPaymentMethodsCollectionViewControllerDelegate: AnyObject @objc(STP_Internal_SavedPaymentMethodsCollectionViewController) class CustomerSavedPaymentMethodsCollectionViewController: UIViewController { enum Error: Swift.Error { - case didSelectRemoveOnInvalidItem case didSelectEditOnInvalidItem case removedInvalidItemWithUpdateCardFlow case unableToDequeueReusableCell diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift index 33a293f8c20..2d37e3e7672 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentMethodCollectionView.swift @@ -83,15 +83,12 @@ extension SavedPaymentMethodCollectionView { return ShadowedRoundedRectangle(appearance: appearance) }() lazy var accessoryButton: CircularButton = { - let button = CircularButton(style: .edit, - dangerColor: appearance.colors.danger) - button.set(style: .edit, with: appearance.colors.danger) + let button = CircularButton(style: .edit) button.backgroundColor = UIColor.dynamic( light: .systemGray5, dark: appearance.colors.componentBackground.lighten(by: 0.075)) button.iconColor = appearance.colors.icon button.isAccessibilityElement = true button.accessibilityLabel = String.Localized.edit - button.accessibilityIdentifier = "Edit" return button }() From ffcf6e416c0002c83ad5ee37fb721e02179c518a Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 22:25:59 -0800 Subject: [PATCH 15/26] remove wait for chevron existence in tests for last card --- .../PaymentSheetUITest/EmbeddedUITest.swift | 2 -- .../PaymentSheetUITest/PaymentSheetVerticalUITest.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift index 870f59e8368..d7e1d0335a0 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/EmbeddedUITest.swift @@ -211,7 +211,6 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is NOT on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) - app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Visa •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") @@ -279,7 +278,6 @@ class EmbeddedUITests: PaymentSheetUITestCase { // Remove last card while selected state is on the card app.buttons["Edit"].waitForExistenceAndTap() XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 3.0)) - app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() dismissAlertView(alertBody: "Cartes Bancaires •••• 1001", alertTitle: "Remove card?", buttonToTap: "Remove") diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift index 6ba5d8fecab..d576bc0c30e 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift @@ -255,7 +255,6 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { // Update the card brand on the last card XCTAssertTrue(app.buttons["Cartes Bancaires ending in 1 0 0 1"].waitForExistence(timeout: 1.0)) // Cartes Bancaires card should be selected now that 4242 card is removed XCTAssertTrue(app.buttons["Edit"].waitForExistenceAndTap()) - app.buttons["chevron"].firstMatch.waitForExistenceAndTap() // Should present the update card view controller XCTAssertTrue(app.staticTexts["Manage card"].waitForExistence(timeout: 2.0)) @@ -278,7 +277,6 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { // Reselect edit icon and delete the card from the update view controller app.buttons["Edit"].firstMatch.waitForExistenceAndTap() - app.buttons["chevron"].waitForExistenceAndTap() app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) From 403fdcb99d4d443dcacb0b4d824da66d8e7bb744 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Thu, 5 Dec 2024 22:36:54 -0800 Subject: [PATCH 16/26] localizable string --- .../Resources/Localizations/en.lproj/Localizable.strings | 3 --- .../VerticalSavedPaymentMethodsViewController.swift | 6 +++++- .../VerticalSavedPaymentMethodsViewControllerTests.swift | 6 ++++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings b/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings index 50c3a96d1f0..2553cfc5365 100644 --- a/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings +++ b/StripePaymentSheet/StripePaymentSheet/Resources/Localizations/en.lproj/Localizable.strings @@ -277,9 +277,6 @@ e.g, 'Pay faster at Example, Inc. and thousands of businesses.' */ /* Title for a button that when tapped removes a linked bank account. */ "Remove linked account" = "Remove linked account"; -/* Title shown above a view containing a customer's payment method that they can delete */ -"Remove payment method" = "Remove payment method"; - /* Label for a button that re-sends the a login code when tapped */ "Resend code" = "Resend code"; diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift index f16823b3e5c..ae991262c8d 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/VerticalSavedPaymentMethodsViewController.swift @@ -83,6 +83,10 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { return (paymentMethodRows.count > 1 ? true : configuration.allowsRemovalOfLastSavedPaymentMethod) && paymentMethodRemove } + var canEditPaymentMethods: Bool { + return hasCoBrandedCards && isCBCEligible + } + /// Indicates whether the chevron should be shown /// True if any saved payment methods can be removed or edited (will update this to include allowing set as default) var canRemoveOrEdit: Bool { @@ -90,7 +94,7 @@ class VerticalSavedPaymentMethodsViewController: UIViewController { guard hasSupportedSavedPaymentMethods else { fatalError("Saved payment methods contain unsupported payment methods.") } - return canRemovePaymentMethods || (hasCoBrandedCards && isCBCEligible) + return canRemovePaymentMethods || canEditPaymentMethods } private var selectedPaymentMethod: STPPaymentMethod? { diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift index 5a4a4f8ea1b..fbc44ebc5f6 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerTests.swift @@ -108,7 +108,9 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { func testCanEdit_singlePaymentMethod_returnsFalse() { let singlePaymentMethods = [STPPaymentMethod._testCard()] - let viewController = VerticalSavedPaymentMethodsViewController(configuration: configuration, + var noRemovalConfiguration = PaymentSheet.Configuration() + noRemovalConfiguration.allowsRemovalOfLastSavedPaymentMethod = false + let viewController = VerticalSavedPaymentMethodsViewController(configuration: noRemovalConfiguration, selectedPaymentMethod: singlePaymentMethods.first, paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), @@ -123,7 +125,7 @@ class VerticalSavedPaymentMethodsViewControllerTests: XCTestCase { paymentMethods: singlePaymentMethods, elementsSession: ._testValue(paymentMethodTypes: ["card"]), analyticsHelper: ._testValue()) - XCTAssertFalse(viewController.canRemoveOrEdit) // Can't edit, merchant is not eligible for CBC + XCTAssertFalse(viewController.canEditPaymentMethods) // Can't edit, merchant is not eligible for CBC } func testCanEdit_singlePaymentMethod_disallowsLastRemoval_returnsFalse() { From c8fbfef9497763b0f8a402c5025937fb46806d28 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Fri, 6 Dec 2024 00:26:21 -0800 Subject: [PATCH 17/26] edit a test --- .../PaymentSheetUITest/PaymentSheetUITest.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index 61d455462ff..1ef45cdfffc 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -1181,8 +1181,8 @@ class PaymentSheetDeferredServerSideUITests: PaymentSheetUITestCase { XCTAssertTrue(confirmRemoval.waitForExistence(timeout: 60.0)) confirmRemoval.tap() - // Should still show "+ Add" and Link - XCTAssertEqual(app.cells.count, 2) + // Should still show "+ Add". Should show Link for a split second, but then it fades out because there is no wallet or other saved pm + XCTAssertTrue(app.staticTexts["+ Add"].waitForExistence(timeout: 3)) } } From 0a92c87f2f8affb609e4478d0e7c67ff75250f0e Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Fri, 6 Dec 2024 00:54:11 -0800 Subject: [PATCH 18/26] remove removeOnly snapshots --- ...ntMethodsViewControllerSnapshotTests.swift | 12 ++---------- ...wButton_newPaymentMethod_unselected@3x.png | Bin 7824 -> 7836 bytes ...wPaymentMethod_withPromo_unselected@3x.png | Bin 11404 -> 11416 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 30017 -> 0 bytes ...ntrollerSnapshotTestsRemoveOnlyMode@3x.png | Bin 29825 -> 0 bytes 5 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png delete mode 100644 Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift index fdea400f34d..4e80480fdb0 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/VerticalSavedPaymentMethodsViewControllerSnapshotTests.swift @@ -24,10 +24,6 @@ final class VerticalSavedPaymentMethodsViewControllerSnapshotTests: STPSnapshotT _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: false, appearance: ._testMSPaintTheme) } - func test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode() { - _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: false, isRemoveOnlyMode: true) - } - func test_Embedded_VerticalSavedPaymentOptionsViewControllerSnapshotTestsDarkMode() { _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: true, isEmbedded: true) } @@ -40,14 +36,10 @@ final class VerticalSavedPaymentMethodsViewControllerSnapshotTests: STPSnapshotT _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: false, appearance: ._testMSPaintTheme, isEmbedded: true) } - func test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode() { - _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: false, isEmbedded: true, isRemoveOnlyMode: true) - } - - func _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: Bool, appearance: PaymentSheet.Appearance = .default, isEmbedded: Bool = false, isRemoveOnlyMode: Bool = false) { + func _test_VerticalSavedPaymentMethodsViewControllerSnapshotTests(darkMode: Bool, appearance: PaymentSheet.Appearance = .default, isEmbedded: Bool = false) { var configuration = PaymentSheet.Configuration() configuration.appearance = appearance - let paymentMethods = isRemoveOnlyMode ? [STPPaymentMethod._testCardAmex()] : generatePaymentMethods() + let paymentMethods = generatePaymentMethods() let sut = VerticalSavedPaymentMethodsViewController(configuration: configuration, selectedPaymentMethod: paymentMethods.first, diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_unselected@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_unselected@3x.png index d0ea7bb2be25da99c6fce1222e8734cc9afa8e69..838eafa6586a6adade5c9358b714a8b741b6cf4b 100644 GIT binary patch delta 94 zcmbPWJI8i{p?5`UglC$sFM}2X0|N&GE29ttGmymygba*Q46I-_1A`Z%G@Kp8r~y^O a#K6#=$-n|t6E(5Gh-H;dM8(EsuVev8W(^ns delta 82 zcmbPZJHd8>p?ywjglC$sFM}2X0|N&G3!@YRE0Dzq#CD9*aJCzx1_Lu#oQZ*W|(jS)-g+f1{Kn_kHR0Qcw(CIA2c diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_withPromo_unselected@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.PaymentMethodRowButtonSnapshotTests/testPaymentMethodRowButton_newPaymentMethod_withPromo_unselected@3x.png index 9170b468bdc1c10329fb3cc8e5123a51b42bde13..6142a4d44cfd3d1eb2e10f5f6a1f05d5fdc75b60 100644 GIT binary patch delta 94 zcmeB)oDn&}(7Pfv!ZXd+mqCkxfq{d8l~IU+8OUM;LIy@D239befx(MW8qN-4)PSmC aVqj>`WMF}+iJDko#Ii~!qGIE+XW9T+qzzI4 delta 82 zcmbOc*%LXz&^{+M!ZXd+mqCkxfq{d8g;9!u706-)Vmn4@INOa;gMk?=&cwjbp2@%h TRpU3Y#)u{LZKm1AP0zFe2K5bK diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_Embedded_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png deleted file mode 100644 index c29dbb1e17e42cbfd644b63fa12f157b93d4a405..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30017 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 diff --git a/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png b/Tests/ReferenceImages_64/StripePaymentSheetTests.VerticalSavedPaymentMethodsViewControllerSnapshotTests/test_VerticalSavedPaymentMethodsViewControllerSnapshotTestsRemoveOnlyMode@3x.png deleted file mode 100644 index d6cd8ef1bab2a9389dc655a45796ac733be7eef1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29825 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 wt7f!0Iog~Aj;M~d<3~GC0z Date: Fri, 6 Dec 2024 12:58:35 -0800 Subject: [PATCH 19/26] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f07da83835..3069471fa38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,5 @@ +### PaymentSheet, CustomerSheet +* [Changed] Changed the edit and remove saved payment method flow so that tapping 'Edit' displays an icon that leads to a new update payment method screen ## 24.1.2 2024-12-05 ### PaymentSheet * [Fixed] Fixed an issue where FlowController returned incorrect `PaymentOptionDisplayData` for Link card brand transactions. From f239d203298874453af3b572b98ef3deffcd691c Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Fri, 6 Dec 2024 14:04:28 -0800 Subject: [PATCH 20/26] update changelog and tests --- CHANGELOG.md | 3 ++- .../PaymentSheetUITest/CustomerSheetUITest.swift | 5 ++++- .../PaymentSheetUITest/PaymentSheetUITest.swift | 6 +++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3069471fa38..bcd8f35467e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### PaymentSheet, CustomerSheet -* [Changed] Changed the edit and remove saved payment method flow so that tapping 'Edit' displays an icon that leads to a new update payment method screen +* [Changed] Changed the edit and remove saved payment method flow so that tapping 'Edit' displays an icon that leads to a new update payment method screen that displays payment method details for card (last 4 digits of card number, cvc and expiry date fields), US Bank account (name, email, last 4 digits of bank acocunt), and SEPA debit (name, email, last 4 digits of IBAN). + ## 24.1.2 2024-12-05 ### PaymentSheet * [Fixed] Fixed an issue where FlowController returned incorrect `PaymentOptionDisplayData` for Link card brand transactions. diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift index f8c1a893520..24e0091b26a 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/CustomerSheetUITest.swift @@ -573,7 +573,10 @@ class CustomerSheetUITest: XCTestCase { XCTAssertTrue(app.staticTexts["Done"].waitForExistence(timeout: 1)) // Sanity check "Done" button is there // Remove the 4242 saved PM - XCTAssertNotNil(scroll(collectionView: app.collectionViews.firstMatch, toFindButtonWithId: "CircularButton.Edit")?.tap()) + // circularEditButton shows up in the view hierarchy, but it's not actually on the screen or tappable so we scroll a little + let startCoordinate = app.collectionViews.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.99)) + startCoordinate.press(forDuration: 0.1, thenDragTo: app.collectionViews.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.99))) + XCTAssertTrue(app.buttons.matching(identifier: "CircularButton.Edit").element(boundBy: 1).waitForExistenceAndTap()) app.buttons["Remove"].waitForExistenceAndTap() XCTAssertTrue(app.alerts.buttons["Remove"].waitForExistenceAndTap()) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift index 1ef45cdfffc..edbfe015790 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetUITest.swift @@ -330,7 +330,10 @@ class PaymentSheetStandardUITests: PaymentSheetUITestCase { XCTAssertTrue(editButton.waitForExistence(timeout: 60.0)) editButton.tap() - app.buttons["CircularButton.Edit"].waitForExistenceAndTap() + // circularEditButton shows up in the view hierarchy, but it's not actually on the screen or tappable so we scroll a little + let startCoordinate = app.collectionViews.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 0.99)) + startCoordinate.press(forDuration: 0.1, thenDragTo: app.collectionViews.firstMatch.coordinate(withNormalizedOffset: CGVector(dx: 0.1, dy: 0.99))) + XCTAssertTrue(app.buttons.matching(identifier: "CircularButton.Edit").firstMatch.waitForExistenceAndTap()) let removeButton = app.buttons["Remove"] XCTAssertTrue(removeButton.waitForExistence(timeout: 60.0)) @@ -340,6 +343,7 @@ class PaymentSheetStandardUITests: PaymentSheetUITestCase { XCTAssertTrue(confirmRemoval.waitForExistence(timeout: 60.0)) confirmRemoval.tap() + XCTAssertTrue(app.staticTexts["Select your payment method"].waitForExistence(timeout: 3.0)) XCTAssertEqual(app.cells.count, 3) // Should be "Add", "Apple Pay", "Link" // Give time for analyticsLog to receive mc_custom_paymentoption_removed From 7022a1189126343247ce8250499da9618da579b3 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Fri, 6 Dec 2024 14:42:31 -0800 Subject: [PATCH 21/26] sleep for 1 second to allow label animation to finish testRemovalOfSavedPaymentMethods_verticalMode --- .../PaymentSheetUITest/PaymentSheetVerticalUITest.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift index d576bc0c30e..a1c734b59a7 100644 --- a/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift +++ b/Example/PaymentSheet Example/PaymentSheetUITest/PaymentSheetVerticalUITest.swift @@ -232,7 +232,8 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase { app.buttons["Done"].waitForExistenceAndTap() // Tap out of FlowController app.tapCoordinate(at: .init(x: 200, y: 100)) - + // Sleep to allow animation to finish + sleep(1) // The next card should be selected now XCTAssertEqual(app.buttons["Payment method"].label, "•••• 1001, card") From 79f2a94b0bceac6981603af39c4f864b410b16ab Mon Sep 17 00:00:00 2001 From: joyceqin-stripe Date: Fri, 6 Dec 2024 15:27:11 -0800 Subject: [PATCH 22/26] Revert space change --- .../SavedPaymentMethodRowButton.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift index 4aea13b386c..c8dc6068aa8 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/Vertical Saved Payment Method Screen/SavedPaymentMethodRowButton.swift @@ -81,6 +81,7 @@ final class SavedPaymentMethodRowButton: UIView { private let appearance: PaymentSheet.Appearance // MARK: Private views + private lazy var chevronButton: RowButton.RightAccessoryButton = { let chevronButton = RowButton.RightAccessoryButton(accessoryType: .update, appearance: appearance, didTap: handleUpdateButtonTapped) chevronButton.isHidden = true From b592b55f0373258f2dcdc427950fe62f186af550 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 9 Dec 2024 10:50:17 -0800 Subject: [PATCH 23/26] fallback when opted in to set as default not local default but first in saved pm list --- .../ElementsCustomer.swift | 13 +++++++----- .../CustomerSessionAdapter.swift | 11 +++++----- .../CustomerSheet/CustomerSheet.swift | 7 ++++--- .../EmbeddedPaymentElement+Internal.swift | 20 ++++++++++++------- .../PaymentSheet/PaymentSheetLoader.swift | 7 ++++--- .../SavedPaymentOptionsViewController.swift | 13 ++++++++---- .../PaymentSheetVerticalViewController.swift | 20 +++++++++++++------ 7 files changed, 58 insertions(+), 33 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift index dc74d4c1c9e..76ab7b66b6b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/Internal/API Bindings/v1-elements-sessions/ElementsCustomer.swift @@ -36,14 +36,17 @@ struct ElementsCustomer: Equatable, Hashable { } // Optional - // to test default payment methods reading from back end, hard-code a valid default payment method - // later, when API calls to get and update default payment method are available, that will no longer be needed let defaultPaymentMethod = response["default_payment_method"] as? String return ElementsCustomer(paymentMethods: paymentMethods, defaultPaymentMethod: defaultPaymentMethod, customerSession: customerSession) } - static func getDefaultPaymentMethod(from customer: ElementsCustomer?) -> STPPaymentMethod? { - guard let customer = customer else { return nil } - return customer.paymentMethods.first { $0.stripeId == customer.defaultPaymentMethod } + func getDefaultOrFirstPaymentMethod() -> STPPaymentMethod? { + // if customer has a default payment method from the elements session, return the default payment method + let defaultSavedPaymentMethod = paymentMethods.first { $0.stripeId == defaultPaymentMethod } + if let defaultSavedPaymentMethod = defaultSavedPaymentMethod { + return defaultSavedPaymentMethod + } + // otherwise, return the first payment method from the customer's list of saved payment methods + return paymentMethods.first } } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift index 129adf47d06..fd29c12eb5e 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSessionAdapter/CustomerSessionAdapter.swift @@ -108,13 +108,14 @@ extension CustomerSessionAdapter { } func fetchSelectedPaymentOption(for customerId: String, customer: ElementsCustomer? = nil) -> CustomerPaymentOption? { - guard configuration.allowsSetAsDefaultPM, - let customer = customer, - let defaultPaymentMethod = customer.defaultPaymentMethod else { - return CustomerPaymentOption.defaultPaymentMethod(for: customerId) + // if opted in to the "set as default" feature, try to get default payment method from elements session + if configuration.allowsSetAsDefaultPM { + guard let customer = customer, + let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() else { return nil } + return CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } - return CustomerPaymentOption.stripeId(defaultPaymentMethod) + return CustomerPaymentOption.defaultPaymentMethod(for: customerId) } func detachPaymentMethod(paymentMethodId: String) async throws { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift index 501edeac3a8..79e088e701c 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/CustomerSheet/CustomerSheet.swift @@ -329,9 +329,10 @@ extension CustomerSheet { var selectedPaymentOption: CustomerPaymentOption? - // get default payment method from elements session - if configuration.allowsSetAsDefaultPM, - let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { + // if opted in to the "set as default" feature, try to get default payment method from elements session + if configuration.allowsSetAsDefaultPM { + guard let customer = elementsSession.customer, + let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() else { return nil } selectedPaymentOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 0cf7f56fe46..86f2fcd2c59 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -37,12 +37,6 @@ extension EmbeddedPaymentElement { isFlatCheckmarkStyle: configuration.appearance.embeddedPaymentElement.row.style == .flatWithCheckmark ) let initialSelection: EmbeddedPaymentMethodsView.Selection? = { - // get default payment method from elements session - if configuration.allowsSetAsDefaultPM, - let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: loadResult.elementsSession.customer) { - return .saved(paymentMethod: defaultPaymentMethod) - } - // Select the previous payment option switch previousPaymentOption { case .applePay: @@ -60,7 +54,19 @@ extension EmbeddedPaymentElement { } // If there's no previous customer input, default to the customer's default or the first saved payment method, if any - let customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) + var customerDefault: CustomerPaymentOption? + // if opted in to the "set as default" feature, try to get default payment method from elements session + if configuration.allowsSetAsDefaultPM { + if let defaultPaymentMethod = loadResult.elementsSession.customer?.getDefaultOrFirstPaymentMethod() { + customerDefault = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + customerDefault = nil + } + } + else { + customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) + } switch customerDefault { case .applePay: return .applePay diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift index fee9ddf93b2..c10a9cbaaff 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentSheetLoader.swift @@ -319,9 +319,10 @@ final class PaymentSheetLoader { // Move default PM to front if let customerID = configuration.customer?.id { var defaultPaymentMethodOption: CustomerPaymentOption? - // get default payment method from elements session - if configuration.allowsSetAsDefaultPM, - let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { + // if opted in to the "set as default" feature, try to get default payment method from elements session + if configuration.allowsSetAsDefaultPM { + guard let customer = elementsSession.customer, + let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() else { return [] } defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } else { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index e13d2df524f..16c83a88799 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -446,10 +446,15 @@ class SavedPaymentOptionsViewController: UIViewController { static func makeViewModels(savedPaymentMethods: [STPPaymentMethod], customerID: String?, showApplePay: Bool, showLink: Bool, allowsSetAsDefaultPM: Bool, customer: ElementsCustomer?) -> (defaultSelectedIndex: Int, viewModels: [Selection]) { // Get the default var defaultPaymentMethodOption: CustomerPaymentOption? - // get default payment method from elements session - if allowsSetAsDefaultPM, - let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: customer) { - defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + // if opted in to the "set as default" feature, try to get default payment method from elements session + if allowsSetAsDefaultPM { + if let customer = customer, + let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() { + defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + defaultPaymentMethodOption = nil + } } else { defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index a92f127b31f..250eafc26ea 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -288,11 +288,6 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo if let selection { return selection } - // get default payment method from elements session - if configuration.allowsSetAsDefaultPM, - let defaultPaymentMethod = ElementsCustomer.getDefaultPaymentMethod(from: elementsSession.customer) { - return .saved(paymentMethod: defaultPaymentMethod) - } switch previousPaymentOption { case .applePay: @@ -323,7 +318,20 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo } } // Default to the customer's default or the first saved payment method, if any - let customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) + var customerDefault: CustomerPaymentOption? + // if opted in to the "set as default" feature, try to get default payment method from elements session + if configuration.allowsSetAsDefaultPM { + if let customer = elementsSession.customer, + let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() { + customerDefault = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) + } + else { + customerDefault = nil + } + } + else { + customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) + } switch customerDefault { case .applePay: return isFlowController ? .applePay : nil // Only default to Apple Pay in flow controller mode From 74c9db8239c2ff596b5782e1ec3fc11539350bd4 Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 9 Dec 2024 12:05:10 -0800 Subject: [PATCH 24/26] added tests --- .../CustomerSheet/CustomerSheetTests.swift | 26 +++++++++- .../PaymentSheet/STPElementsSessionTest.swift | 50 +++++++++++++++++++ .../STPFixtures+PaymentSheet.swift | 26 +++++++++- 3 files changed, 99 insertions(+), 3 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift index a533b7309e4..5613298520f 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift @@ -7,7 +7,7 @@ import Foundation @_spi(STP) @testable import StripeCore @_spi(STP) @testable import StripePayments -@_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @testable import StripePaymentSheet +@_spi(CustomerSessionBetaAccess) @_spi(CardBrandFilteringBeta) @_spi(AllowsSetAsDefaultPM) @testable import StripePaymentSheet import OHHTTPStubs import OHHTTPStubsSwift @@ -327,4 +327,28 @@ class CustomerSheetTests: APIStubbedTestCase { } wait(for: [loadPaymentMethodInfo], timeout: 5.0) } + + func testLoadPaymentMethodInfo_CustomerSession_NoDefaultPMHasSavedPaymentMethod() throws { + let stubbedAPIClient = stubbedAPIClient() + StubbedBackend.stubSessions(fileMock: .elementsSessions_customerSessionsCustomerSheetWithSavedPM_200) + var configuration = CustomerSheet.Configuration() + configuration.apiClient = stubbedAPIClient + configuration.allowsSetAsDefaultPM = true + + let loadPaymentMethodInfo = expectation(description: "loadPaymentMethodInfo completed") + let customerSheet = CustomerSheet(configuration: configuration, + intentConfiguration: .init(setupIntentClientSecretProvider: { return "si_123" }), + customerSessionClientSecretProvider: { return .init(customerId: "cus_123", clientSecret: "cuss_123") }) + let csDataSource = customerSheet.createCustomerSheetDataSource()! + csDataSource.loadPaymentMethodInfo { result in + guard case .success((let paymentMethods, let selectedPaymentMethod, _)) = result else { + XCTFail() + return + } + XCTAssertFalse(paymentMethods.isEmpty) + XCTAssertNotNil(selectedPaymentMethod) + loadPaymentMethodInfo.fulfill() + } + wait(for: [loadPaymentMethodInfo], timeout: 5.0) + } } diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPElementsSessionTest.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPElementsSessionTest.swift index e269af7081c..d5fb658ab57 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPElementsSessionTest.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPElementsSessionTest.swift @@ -342,4 +342,54 @@ class STPElementsSessionTest: XCTestCase { XCTAssertTrue(allowsRemoval) XCTAssertFalse(elementsSession.paymentMethodRemoveLastForCustomerSheet) } + private let testCardJSON = [ + "id": "pm_123card", + "type": "card", + "card": [ + "last4": "4242", + "brand": "visa", + "fingerprint": "B8XXs2y2JsVBtB9f", + "networks": ["available": ["visa"]], + "exp_month": "01", + "exp_year": Calendar.current.component(.year, from: Date()) + 1 + ] + ] as [AnyHashable : Any] + private let testCardAmexJSON = [ + "id": "pm_123amexcard", + "type": "card", + "card": [ + "last4": "0005", + "brand": "amex", + ], + ] as [AnyHashable : Any] + func testElementsCustomerDefaultPaymentMethod() { + let elementsSession = STPElementsSession._testDefaultCardValue(defaultPaymentMethod: "pm_123card", paymentMethods: [testCardAmexJSON, testCardJSON]) + let customer = elementsSession.customer + XCTAssertNotNil(customer) + let defaultPaymentMethodId = customer?.defaultPaymentMethod + XCTAssertNotNil(defaultPaymentMethodId) + let defaultPaymentMethod = customer?.getDefaultOrFirstPaymentMethod() + XCTAssertNotNil(defaultPaymentMethod) + XCTAssertEqual(defaultPaymentMethod?.stripeId, defaultPaymentMethodId) + XCTAssertEqual(defaultPaymentMethod?.stripeId, "pm_123card") + } + func testElementsCustomerNoDefaultPaymentMethodHasSavedPaymentMethods() { + let elementsSession = STPElementsSession._testDefaultCardValue(defaultPaymentMethod: nil, paymentMethods: [testCardAmexJSON, testCardJSON]) + let customer = elementsSession.customer + XCTAssertNotNil(customer) + let defaultPaymentMethodId = customer?.defaultPaymentMethod + XCTAssertNil(defaultPaymentMethodId) + let defaultPaymentMethod = customer?.getDefaultOrFirstPaymentMethod() + XCTAssertNotNil(defaultPaymentMethod) + XCTAssertEqual(defaultPaymentMethod?.stripeId, "pm_123amexcard") + } + func testElementsCustomerNoDefaultPaymentMethodNoSavedPaymentMethods() { + let elementsSession = STPElementsSession._testDefaultCardValue(defaultPaymentMethod: nil, paymentMethods: []) + let customer = elementsSession.customer + XCTAssertNotNil(customer) + let defaultPaymentMethodId = customer?.defaultPaymentMethod + XCTAssertNil(defaultPaymentMethodId) + let defaultPaymentMethod = customer?.getDefaultOrFirstPaymentMethod() + XCTAssertNil(defaultPaymentMethod) + } } diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift index 43ed581fbc5..4bf0f2067e0 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/STPFixtures+PaymentSheet.swift @@ -45,6 +45,19 @@ extension STPElementsSession { return _testValue(paymentMethodTypes: ["card"]) } + static func _testDefaultCardValue(defaultPaymentMethod: String?, paymentMethods: [[AnyHashable: Any]]? = nil) -> STPElementsSession { + return _testValue(paymentMethodTypes: ["card"], customerSessionData: [ + "mobile_payment_element": [ + "enabled": true, + "features": ["payment_method_save": "enabled", + "payment_method_remove": "enabled", + ], + ], + "customer_sheet": [ + "enabled": false, + ]], allowsSetAsDefaultPM: true, defaultPaymentMethod: defaultPaymentMethod, paymentMethods: paymentMethods) + } + static func _testValue( paymentMethodTypes: [String], externalPaymentMethodTypes: [String] = [], @@ -53,7 +66,10 @@ extension STPElementsSession { isLinkPassthroughModeEnabled: Bool? = nil, linkMode: LinkMode? = nil, linkFundingSources: Set = [], - disableLinkSignup: Bool? = nil + disableLinkSignup: Bool? = nil, + allowsSetAsDefaultPM: Bool = false, + defaultPaymentMethod: String? = nil, + paymentMethods: [[AnyHashable: Any]]? = nil ) -> STPElementsSession { var json = STPTestUtils.jsonNamed("ElementsSession")! json[jsonDict: "payment_method_preference"]?["ordered_payment_method_types"] = paymentMethodTypes @@ -74,8 +90,14 @@ extension STPElementsSession { "api_key_expiry": 12345, "customer": "cus_123", "components": customerSessionData, - ], + ] ] + if allowsSetAsDefaultPM, let defaultPaymentMethod { + json[jsonDict: "customer"]?["default_payment_method"] = defaultPaymentMethod + } + if let paymentMethods { + json[jsonDict: "customer"]?["payment_methods"] = paymentMethods + } } if let cardBrandChoiceData { From 2e8b49ee419ab57f0fa95eef15ef68f2af0a630a Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 9 Dec 2024 14:39:58 -0800 Subject: [PATCH 25/26] no saved pms customersessionadapter test --- .../CustomerSheet/CustomerSheetTests.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift index 5613298520f..8059ad4999b 100644 --- a/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift +++ b/StripePaymentSheet/StripePaymentSheetTests/PaymentSheet/CustomerSheet/CustomerSheetTests.swift @@ -351,4 +351,28 @@ class CustomerSheetTests: APIStubbedTestCase { } wait(for: [loadPaymentMethodInfo], timeout: 5.0) } + + func testLoadPaymentMethodInfo_CustomerSession_NoDefaultPMNoSavedPaymentMethod() throws { + let stubbedAPIClient = stubbedAPIClient() + StubbedBackend.stubSessions(fileMock: .elementsSessions_customerSessionsCustomerSheet_200) + var configuration = CustomerSheet.Configuration() + configuration.apiClient = stubbedAPIClient + configuration.allowsSetAsDefaultPM = true + + let loadPaymentMethodInfo = expectation(description: "loadPaymentMethodInfo completed") + let customerSheet = CustomerSheet(configuration: configuration, + intentConfiguration: .init(setupIntentClientSecretProvider: { return "si_123" }), + customerSessionClientSecretProvider: { return .init(customerId: "cus_123", clientSecret: "cuss_123") }) + let csDataSource = customerSheet.createCustomerSheetDataSource()! + csDataSource.loadPaymentMethodInfo { result in + guard case .success((let paymentMethods, let selectedPaymentMethod, _)) = result else { + XCTFail() + return + } + XCTAssertTrue(paymentMethods.isEmpty) + XCTAssertNil(selectedPaymentMethod) + loadPaymentMethodInfo.fulfill() + } + wait(for: [loadPaymentMethodInfo], timeout: 5.0) + } } From a2a37e6d50cd62ab98d0c0d54435c2ce4eb93c8f Mon Sep 17 00:00:00 2001 From: Joyce Qin Date: Mon, 9 Dec 2024 14:59:26 -0800 Subject: [PATCH 26/26] removed explicit nil assignments when already implied --- .../Embedded/EmbeddedPaymentElement+Internal.swift | 3 --- .../SavedPaymentOptionsViewController.swift | 3 --- .../ViewControllers/PaymentSheetVerticalViewController.swift | 3 --- 3 files changed, 9 deletions(-) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift index 86f2fcd2c59..9dec233bc49 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Embedded/EmbeddedPaymentElement+Internal.swift @@ -60,9 +60,6 @@ extension EmbeddedPaymentElement { if let defaultPaymentMethod = loadResult.elementsSession.customer?.getDefaultOrFirstPaymentMethod() { customerDefault = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } - else { - customerDefault = nil - } } else { customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift index 16c83a88799..07f58ae49c0 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Saved Payment Method Screen/SavedPaymentOptionsViewController.swift @@ -452,9 +452,6 @@ class SavedPaymentOptionsViewController: UIViewController { let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() { defaultPaymentMethodOption = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } - else { - defaultPaymentMethodOption = nil - } } else { defaultPaymentMethodOption = CustomerPaymentOption.defaultPaymentMethod(for: customerID) diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index 250eafc26ea..92d62f6f488 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -325,9 +325,6 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo let defaultPaymentMethod = customer.getDefaultOrFirstPaymentMethod() { customerDefault = CustomerPaymentOption.stripeId(defaultPaymentMethod.stripeId) } - else { - customerDefault = nil - } } else { customerDefault = CustomerPaymentOption.defaultPaymentMethod(for: configuration.customer?.id)