diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/AddPaymentMethodViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/AddPaymentMethodViewController.swift index 70f0732450f..f3af2d26347 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/AddPaymentMethodViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/AddPaymentMethodViewController.swift @@ -194,6 +194,11 @@ extension AddPaymentMethodViewController: PaymentMethodTypeCollectionViewDelegat extension AddPaymentMethodViewController: PaymentMethodFormViewControllerDelegate { func didUpdate(_ viewController: PaymentMethodFormViewController) { delegate?.didUpdate(self) + + if let instantDebitsFormElement = viewController.form as? InstantDebitsPaymentMethodElement { + let incentive = instantDebitsFormElement.displayableIncentive + paymentMethodTypesView.setIncentive(incentive) + } } func updateErrorLabel(for error: Swift.Error?) { diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/PaymentMethodTypeCollectionView.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/PaymentMethodTypeCollectionView.swift index d0c87937daa..ec1c9062660 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/PaymentMethodTypeCollectionView.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/New Payment Method Screen/PaymentMethodTypeCollectionView.swift @@ -90,6 +90,18 @@ class PaymentMethodTypeCollectionView: UICollectionView { override var intrinsicContentSize: CGSize { return CGSize(width: UIView.noIntrinsicMetric, height: PaymentMethodTypeCollectionView.cellHeight) } + + func setIncentive(_ incentive: PaymentMethodIncentive?) { + guard self.incentive != incentive, let index = self.indexPathsForSelectedItems?.first else { + return + } + + self.incentive = incentive + + // Prevent the selected cell from being unselected following the reload + reloadItems(at: [index]) + selectItem(at: index, animated: false, scrollPosition: []) + } } // MARK: - UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentMethodIncentive.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentMethodIncentive.swift index 0a898636d67..58bbc1d98c5 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentMethodIncentive.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/PaymentMethodIncentive.swift @@ -8,7 +8,7 @@ import Foundation @_spi(STP) import StripePayments -struct PaymentMethodIncentive { +struct PaymentMethodIncentive: Equatable { let identifier: String let displayText: String diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift index 093d9027ba6..ec5ff0ddf6b 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/USBankAccount/InstantDebitsPaymentMethodElement.swift @@ -23,16 +23,21 @@ final class InstantDebitsPaymentMethodElement: ContainerElement { let emailElement: TextFieldElement? let phoneElement: PhoneNumberElement? let addressElement: AddressSectionElement? + private let promoDisclaimerElement: StaticElement? private var linkedBankElements: [Element] { return [linkedBankInfoSectionElement] } private let linkedBankInfoSectionElement: SectionElement private let linkedBankInfoView: BankAccountInfoView - private var linkedBank: InstantDebitsLinkedBank? + private var linkedBank: InstantDebitsLinkedBank? { + didSet { + updateLinkedBank(linkedBank) + } + } private let theme: ElementsAppearance var presentingViewControllerDelegate: PresentingViewControllerDelegate? - var incentive: PaymentMethodIncentive? + private let incentive: PaymentMethodIncentive? var delegate: ElementDelegate? var view: UIView { @@ -164,6 +169,11 @@ final class InstantDebitsPaymentMethodElement: ContainerElement { return nameValid && emailValid && phoneValid && addressValid } + + var displayableIncentive: PaymentMethodIncentive? { + let canShowIncentive = linkedBank?.incentiveEligible ?? true + return canShowIncentive ? incentive : nil + } init( configuration: PaymentSheetFormFactoryConfig, @@ -196,7 +206,7 @@ final class InstantDebitsPaymentMethodElement: ContainerElement { self.incentive = incentive self.theme = theme - let promoDisclaimerElement = incentive.flatMap { + self.promoDisclaimerElement = incentive.flatMap { let label = ElementsUI.makeNoticeTextField(theme: theme) label.attributedText = $0.promoDisclaimerText(with: theme, isPaymentIntent: isPaymentIntent) label.textContainerInset = .zero @@ -224,18 +234,33 @@ final class InstantDebitsPaymentMethodElement: ContainerElement { func setLinkedBank(_ linkedBank: InstantDebitsLinkedBank) { self.linkedBank = linkedBank - if let last4ofBankAccount = linkedBank.last4, let bankName = linkedBank.bankName { + self.delegate?.didUpdate(element: self) + } + + fileprivate func updateLinkedBank(_ linkedBank: InstantDebitsLinkedBank?) { + let hideLinkedBank = linkedBank == nil + + if let last4ofBankAccount = linkedBank?.last4, let bankName = linkedBank?.bankName { linkedBankInfoView.setBankName(text: bankName) linkedBankInfoView.setLastFourOfBank(text: "•••• \(last4ofBankAccount)") // TODO: Take the eligibility from the linked bank linkedBankInfoView.setIncentiveEligible(false) + } + + formElement.toggleElements( + linkedBankElements, + hidden: hideLinkedBank, + animated: true + ) + + if let promoDisclaimerElement { + let hidePromoBadge = incentive == nil || linkedBank?.incentiveEligible == false formElement.toggleElements( - linkedBankElements, - hidden: false, + [promoDisclaimerElement], + hidden: hidePromoBadge, animated: true ) } - self.delegate?.didUpdate(element: self) } func getLinkedBank() -> InstantDebitsLinkedBank? { @@ -249,11 +274,6 @@ extension InstantDebitsPaymentMethodElement: BankAccountInfoViewDelegate { func didTapXIcon() { let hideLinkedBankElement = { - self.formElement.toggleElements( - self.linkedBankElements, - hidden: true, - animated: true - ) self.linkedBank = nil self.delegate?.didUpdate(element: self) } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/VerticalPaymentMethodListViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/VerticalPaymentMethodListViewController.swift index 794d06a4dc5..cf627940251 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/VerticalPaymentMethodListViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/Vertical Main Screen/VerticalPaymentMethodListViewController.swift @@ -23,7 +23,10 @@ class VerticalPaymentMethodListViewController: UIViewController { private(set) var currentSelection: VerticalPaymentMethodListSelection? let stackView = UIStackView() let appearance: PaymentSheet.Appearance + private var incentive: PaymentMethodIncentive? weak var delegate: VerticalPaymentMethodListViewControllerDelegate? + + private var refreshContent: () -> Void = {} required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -43,16 +46,47 @@ class VerticalPaymentMethodListViewController: UIViewController { incentive: PaymentMethodIncentive?, delegate: VerticalPaymentMethodListViewControllerDelegate ) { - self.delegate = delegate self.appearance = appearance + self.incentive = incentive self.delegate = delegate super.init(nibName: nil, bundle: nil) - + + self.refreshContent = { [weak self] in + guard let self else { + return + } + + stackView.arrangedSubviews.forEach { subview in + subview.removeFromSuperview() + } + + renderContent( + overrideHeaderView: overrideHeaderView, + savedPaymentMethod: savedPaymentMethod, + initialSelection: initialSelection, + savedPaymentMethodAccessoryType: savedPaymentMethodAccessoryType, + shouldShowApplePay: shouldShowApplePay, + shouldShowLink: shouldShowLink, + paymentMethodTypes: paymentMethodTypes + ) + } + self.refreshContent() + } + + private func renderContent( + overrideHeaderView: UIView?, + savedPaymentMethod: STPPaymentMethod?, + initialSelection: VerticalPaymentMethodListSelection?, + savedPaymentMethodAccessoryType: RowButton.RightAccessoryButton.AccessoryType?, + shouldShowApplePay: Bool, + shouldShowLink: Bool, + paymentMethodTypes: [PaymentSheet.PaymentMethodType] + ) { // Add the header - either the passed in `header` or "Select payment method" let header = overrideHeaderView ?? PaymentSheetUI.makeHeaderLabel(title: .Localized.select_payment_method, appearance: appearance) stackView.addArrangedSubview(header) stackView.setCustomSpacing(24, after: header) - + // Create stack view views after super.init so that we can reference `self` var views = [UIView]() // Saved payment method: @@ -65,7 +99,7 @@ class VerticalPaymentMethodListViewController: UIViewController { return nil } }() - + let savedPaymentMethodButton = RowButton.makeForSavedPaymentMethod(paymentMethod: savedPaymentMethod, appearance: appearance, rightAccessoryView: accessoryButton) { [weak self] in self?.didTap(rowButton: $0, selection: selection) } @@ -80,7 +114,7 @@ class VerticalPaymentMethodListViewController: UIViewController { Self.makeSectionLabel(text: .Localized.new_payment_method, appearance: appearance), ] } - + // Build Apple Pay and Link rows let applePay: RowButton? = { guard shouldShowApplePay else { return nil } @@ -106,7 +140,7 @@ class VerticalPaymentMethodListViewController: UIViewController { } return rowButton }() - + // Payment methods var indexAfterCards: Int? let paymentMethodTypes = paymentMethodTypes @@ -119,7 +153,7 @@ class VerticalPaymentMethodListViewController: UIViewController { promoText: incentive?.takeIfAppliesTo(paymentMethodType)?.displayText, appearance: appearance, // Enable press animation if tapping this transitions the screen to a form instead of becoming selected - shouldAnimateOnPress: !delegate.shouldSelectPaymentMethod(selection) + shouldAnimateOnPress: delegate?.shouldSelectPaymentMethod(selection) == true ) { [weak self] in self?.didTap(rowButton: $0, selection: selection) } @@ -132,10 +166,10 @@ class VerticalPaymentMethodListViewController: UIViewController { currentSelection = selection } } - + // Insert Apple Pay/Link after card or, if cards aren't present, first views.insert(contentsOf: [applePay, link].compactMap({ $0 }), at: indexAfterCards ?? 0) - + for view in views { stackView.addArrangedSubview(view) } @@ -170,6 +204,15 @@ class VerticalPaymentMethodListViewController: UIViewController { @objc func didTapAccessoryButton() { delegate?.didTapSavedPaymentMethodAccessoryButton() } + + func setIncentive(_ incentive: PaymentMethodIncentive?) { + guard self.incentive != incentive else { + return + } + + self.incentive = incentive + self.refreshContent() + } static func makeSectionLabel(text: String, appearance: PaymentSheet.Appearance) -> UILabel { let label = UILabel() diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentMethodFormViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentMethodFormViewController.swift index ad57536914b..6b363efe568 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentMethodFormViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentMethodFormViewController.swift @@ -271,7 +271,7 @@ extension PaymentMethodFormViewController { intentId: intentId, linkMode: linkMode, billingDetails: billingDetails, - eligibleForIncentive: instantDebitsFormElement?.incentive != nil + eligibleForIncentive: instantDebitsFormElement?.displayableIncentive != nil ) } diff --git a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift index 92d62f6f488..6c62f0f0e99 100644 --- a/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift +++ b/StripePaymentSheet/StripePaymentSheet/Source/PaymentSheet/ViewControllers/PaymentSheetVerticalViewController.swift @@ -844,6 +844,11 @@ extension PaymentSheetVerticalViewController: PaymentMethodFormViewControllerDel if viewController.paymentOption != nil { analyticsHelper.logFormCompleted(paymentMethodTypeIdentifier: viewController.paymentMethodType.identifier) } + + if let instantDebitsFormElement = viewController.form as? InstantDebitsPaymentMethodElement { + let incentive = instantDebitsFormElement.displayableIncentive + paymentMethodListViewController?.setIncentive(incentive) + } } func updateErrorLabel(for error: Swift.Error?) {