Skip to content

Commit

Permalink
Merge branch 'master' into joyceqin/MOBILESDK-2697
Browse files Browse the repository at this point in the history
  • Loading branch information
joyceqin-stripe authored Jan 29, 2025
2 parents a9d9db1 + c3239fd commit 3bca1bc
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Foundation
@_spi(STP) import StripeCore
@_spi(STP) import StripePayments

struct PaymentMethodIncentive {
struct PaymentMethodIncentive: Equatable {

let identifier: String
let displayText: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
renderLinkedBank(linkedBank)
}
}
private let theme: ElementsAppearance
var presentingViewControllerDelegate: PresentingViewControllerDelegate?
var incentive: PaymentMethodIncentive?
private let incentive: PaymentMethodIncentive?

var delegate: ElementDelegate?
var view: UIView {
Expand Down Expand Up @@ -164,6 +169,19 @@ final class InstantDebitsPaymentMethodElement: ContainerElement {

return nameValid && emailValid && phoneValid && addressValid
}

var displayableIncentive: PaymentMethodIncentive? {
// We can show the incentive if we haven't linked a bank yet, meaning
// that we have no indication that the session is ineligible.
let canShowIncentive = linkedBank?.incentiveEligible ?? true
return canShowIncentive ? incentive : nil
}

var showIncentiveInHeader: Bool {
// Only show the incentive if the user hasn't linked a bank account yet. If they have,
// the incentive will be shown in the bank form instead.
linkedBank == nil
}

init(
configuration: PaymentSheetFormFactoryConfig,
Expand Down Expand Up @@ -195,8 +213,7 @@ final class InstantDebitsPaymentMethodElement: ContainerElement {
self.linkedBankInfoSectionElement.view.isHidden = true
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
Expand Down Expand Up @@ -224,17 +241,30 @@ 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 renderLinkedBank(_ linkedBank: InstantDebitsLinkedBank?) {
if let linkedBank, let last4ofBankAccount = linkedBank.last4, let bankName = linkedBank.bankName {
linkedBankInfoView.setBankName(text: bankName)
linkedBankInfoView.setLastFourOfBank(text: "••••\(last4ofBankAccount)")
linkedBankInfoView.setIncentiveEligible(linkedBank.incentiveEligible)
}

formElement.toggleElements(
linkedBankElements,
hidden: linkedBank == nil,
animated: true
)

if let promoDisclaimerElement {
let hideDisclaimer = incentive == nil || linkedBank?.incentiveEligible == false
formElement.toggleElements(
linkedBankElements,
hidden: false,
[promoDisclaimerElement],
hidden: hideDisclaimer,
animated: true
)
}
self.delegate?.didUpdate(element: self)
}

func getLinkedBank() -> InstantDebitsLinkedBank? {
Expand All @@ -248,11 +278,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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ final class FormHeaderView: UIView {
private let paymentMethodType: PaymentSheet.PaymentMethodType
private let shouldUseNewCardHeader: Bool // true if the customer has a saved payment method that is type card
private let appearance: PaymentSheet.Appearance
private var incentive: PaymentMethodIncentive?

init(
paymentMethodType: PaymentSheet.PaymentMethodType,
Expand All @@ -74,6 +75,7 @@ final class FormHeaderView: UIView {
self.paymentMethodType = paymentMethodType
self.shouldUseNewCardHeader = shouldUseNewCardHeader
self.appearance = appearance
self.incentive = incentive
self.promoBadgeView = Self.makePromoBadge(for: incentive, with: appearance)
super.init(frame: .zero)
addAndPinSubview(stackView)
Expand All @@ -90,6 +92,30 @@ final class FormHeaderView: UIView {
fatalError("init(coder:) has not been implemented")
}

func setIncentive(_ incentive: PaymentMethodIncentive?) {
guard incentive != self.incentive else {
return
}

if let promoBadgeView {
stackView.removeArrangedSubview(promoBadgeView)
promoBadgeView.removeFromSuperview()

stackView.removeArrangedSubview(spacerView)
spacerView.removeFromSuperview()
}

self.incentive = incentive

if let incentive {
promoBadgeView = Self.makePromoBadge(for: incentive, with: appearance)
if let promoBadgeView {
stackView.addArrangedSubview(promoBadgeView)
stackView.addArrangedSubview(spacerView)
}
}
}

private static func makePromoBadge(
for incentive: PaymentMethodIncentive?,
with appearance: PaymentSheet.Appearance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class RowButton: UIView {
}
}()
promoBadge.translatesAutoresizingMaskIntoConstraints = false
promoBadge.isUserInteractionEnabled = false
addSubview(promoBadge)
NSLayoutConstraint.activate([
promoBadge.centerYAnchor.constraint(equalTo: centerYAnchor),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,17 @@ class VerticalPaymentMethodListViewController: UIViewController {
private(set) var currentSelection: VerticalPaymentMethodListSelection?
let stackView = UIStackView()
let appearance: PaymentSheet.Appearance
private(set) var incentive: PaymentMethodIncentive?
weak var delegate: VerticalPaymentMethodListViewControllerDelegate?

// Properties moved from initializer captures
private var overrideHeaderView: UIView?
private var savedPaymentMethod: STPPaymentMethod?
private var initialSelection: VerticalPaymentMethodListSelection?
private var savedPaymentMethodAccessoryType: RowButton.RightAccessoryButton.AccessoryType?
private var shouldShowApplePay: Bool
private var shouldShowLink: Bool
private var paymentMethodTypes: [PaymentSheet.PaymentMethodType]

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
Expand All @@ -43,11 +53,30 @@ class VerticalPaymentMethodListViewController: UIViewController {
incentive: PaymentMethodIncentive?,
delegate: VerticalPaymentMethodListViewControllerDelegate
) {
self.delegate = delegate
self.appearance = appearance
self.incentive = incentive
self.delegate = delegate
self.overrideHeaderView = overrideHeaderView
self.savedPaymentMethod = savedPaymentMethod
self.initialSelection = initialSelection
self.savedPaymentMethodAccessoryType = savedPaymentMethodAccessoryType
self.shouldShowApplePay = shouldShowApplePay
self.shouldShowLink = shouldShowLink
self.paymentMethodTypes = paymentMethodTypes

super.init(nibName: nil, bundle: nil)

self.renderContent()
}

private func refreshContent() {
stackView.arrangedSubviews.forEach { subview in
subview.removeFromSuperview()
}

renderContent()
}

private func renderContent() {
// 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)
Expand Down Expand Up @@ -119,7 +148,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) == false
) { [weak self] in
self?.didTap(rowButton: $0, selection: selection)
}
Expand Down Expand Up @@ -170,6 +199,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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ extension PaymentMethodFormViewController: ElementDelegate {
analyticsHelper.logFormInteracted(paymentMethodTypeIdentifier: paymentMethodType.identifier)
delegate?.didUpdate(self)
animateHeightChange()

if let instantDebitsFormElement = form as? InstantDebitsPaymentMethodElement {
let incentive = instantDebitsFormElement.displayableIncentive

if let formHeaderView = headerView as? FormHeaderView {
// We already display a promo badge in the bank form, so we don't want
// to display another one in the header.
let headerIncentive = instantDebitsFormElement.showIncentiveInHeader ? incentive : nil
formHeaderView.setIncentive(headerIncentive)
}
}
}
}

Expand Down Expand Up @@ -272,7 +283,7 @@ extension PaymentMethodFormViewController {
intentId: intentId,
linkMode: linkMode,
billingDetails: billingDetails,
eligibleForIncentive: instantDebitsFormElement?.incentive != nil
eligibleForIncentive: instantDebitsFormElement?.displayableIncentive != nil
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,17 @@ extension PaymentSheetVerticalViewController: VerticalPaymentMethodListViewContr
}
}()
let headerView: UIView = {
let incentive = elementsSession.incentive?.takeIfAppliesTo(paymentMethodType)
let incentive = paymentMethodListViewController?.incentive?.takeIfAppliesTo(paymentMethodType)
let currentForm = formCache[paymentMethodType]

let displayedIncentive = if let instantDebitsForm = currentForm as? InstantDebitsPaymentMethodElement {
// If we have shown this form before and the incentive has been cleared, make sure we don't show it again
// when re-rendering the form.
instantDebitsForm.showIncentiveInHeader ? incentive : nil
} else {
incentive
}

if shouldDisplayFormOnly, let wallet = makeWalletHeaderView() {
// Special case: if there is only one payment method type and it's not a card and wallet options are available
// Display the wallet, then the FormHeaderView below it
Expand All @@ -756,7 +766,7 @@ extension PaymentSheetVerticalViewController: VerticalPaymentMethodListViewContr
paymentMethodType: paymentMethodType,
shouldUseNewCardHeader: savedPaymentMethods.first?.type == .card,
appearance: configuration.appearance,
incentive: incentive
incentive: displayedIncentive
),
])
containerView.axis = .vertical
Expand All @@ -771,7 +781,7 @@ extension PaymentSheetVerticalViewController: VerticalPaymentMethodListViewContr
// Special case: use "New Card" instead of "Card" if the displayed saved PM is a card
shouldUseNewCardHeader: savedPaymentMethods.first?.type == .card,
appearance: configuration.appearance,
incentive: incentive
incentive: displayedIncentive
)
}
}()
Expand Down Expand Up @@ -881,6 +891,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?) {
Expand Down

0 comments on commit 3bca1bc

Please sign in to comment.