Skip to content

Commit

Permalink
Show promo badge in bank form element (#4363)
Browse files Browse the repository at this point in the history
## Summary
<!-- Simple summary of what was changed. -->

This pull request starts showing the promo badge in the bank form
element, both for eligible and ineligible sessions.

The changes also affect non-incentivized sessions: The design of the
account info view has changed, and I’m aligning the last4 of bank
account and SEPA with the last4 of card, making them include a space.

## Motivation
<!-- Why are you making this change? If it's for fixing a bug, if
possible, please include a code snippet or example project that
demonstrates the issue. -->

[CONSUMERBANK-572](https://jira.corp.stripe.com/browse/CONSUMERBANK-572)

## Testing
<!-- How was the code tested? Be as specific as possible. -->

Added `BankAccountInfoViewSnapshotTests`.

## Changelog
<!-- Is this a notable change that affects users? If so, add a line to
`CHANGELOG.md` and prefix the line with one of the following:
    - [Added] for new features.
    - [Changed] for changes in existing functionality.
    - [Deprecated] for soon-to-be removed features.
    - [Removed] for now removed features.
    - [Fixed] for any bug fixes.
    - [Security] in case of vulnerabilities.
-->
  • Loading branch information
tillh-stripe authored Dec 20, 2024
1 parent a42bcff commit 6cb4756
Show file tree
Hide file tree
Showing 37 changed files with 249 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ class CustomerSheetUITest: XCTestCase {

presentCSAndAddSepaFrom(buttonLabel: "None")

app.staticTexts["••••3000"].waitForExistenceAndTap(timeout: timeout)
app.staticTexts["•••• 3000"].waitForExistenceAndTap(timeout: timeout)

let editButton = app.staticTexts["Edit"]
XCTAssertTrue(editButton.waitForExistence(timeout: timeout))
Expand All @@ -273,7 +273,7 @@ class CustomerSheetUITest: XCTestCase {
app.buttons["Reload"].tap()
XCTAssertTrue(app.staticTexts["None"].waitForExistenceAndTap(timeout: 5))
XCTAssertTrue(app.staticTexts["Manage your payment methods"].waitForExistence(timeout: 5))
XCTAssertFalse(app.staticTexts["••••3000"].waitForExistence(timeout: 5))
XCTAssertFalse(app.staticTexts["•••• 3000"].waitForExistence(timeout: 5))
}

func testPrevPM_AddPM_canceled() throws {
Expand Down Expand Up @@ -350,7 +350,7 @@ class CustomerSheetUITest: XCTestCase {
XCTAssertTrue(confirmButton.waitForExistence(timeout: timeout))
confirmButton.tap()

dismissAlertView(alertBody: "Success: ••••6789, selected", alertTitle: "Complete", buttonToTap: "OK")
dismissAlertView(alertBody: "Success: •••• 6789, selected", alertTitle: "Complete", buttonToTap: "OK")
}

func testCustomerSheet_addUSBankAccount_MicroDeposit() throws {
Expand Down Expand Up @@ -783,7 +783,7 @@ class CustomerSheetUITest: XCTestCase {
let confirmButton = app.buttons["Confirm"]
XCTAssertTrue(confirmButton.waitForExistence(timeout: timeout))
confirmButton.tap()
dismissAlertView(alertBody: "Success: ••••3000, selected", alertTitle: "Complete", buttonToTap: "OK")
dismissAlertView(alertBody: "Success: •••• 3000, selected", alertTitle: "Complete", buttonToTap: "OK")
}

func removeFirstPaymentMethodInList(alertBody: String = "Visa •••• 4242", alertTitle: String = "Remove card?") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -340,10 +340,10 @@ class EmbeddedUITests: PaymentSheetUITestCase {

loadPlayground(app, settings)
app.buttons["Present embedded payment element"].waitForExistenceAndTap()
ensureSPMSelection("••••6789", insteadOf: "•••• 4242")
ensureSPMSelection("•••• 6789", insteadOf: "•••• 4242")

let card4242Button = app.buttons["•••• 4242"]
let bank6789Button = app.buttons["••••6789"]
let bank6789Button = app.buttons["•••• 6789"]

// Switch from 6789 (Bank account) to 4242
app.buttons["View more"].waitForExistenceAndTap()
Expand Down Expand Up @@ -401,9 +401,9 @@ class EmbeddedUITests: PaymentSheetUITestCase {

loadPlayground(app, settings)
app.buttons["Present embedded payment element"].waitForExistenceAndTap()
ensureSPMSelection("••••6789", insteadOf: "•••• 4242")
ensureSPMSelection("•••• 6789", insteadOf: "•••• 4242")

let bank6789Button = app.buttons["••••6789"]
let bank6789Button = app.buttons["•••• 6789"]
let applePayButton = app.buttons["Apple Pay"]

// Ensure card bank acct. is selected, and apple pay is not.
Expand Down Expand Up @@ -491,7 +491,7 @@ class EmbeddedUITests: PaymentSheetUITestCase {

// Verify we show the bank account in the saved PM row
XCTAssertTrue(app.buttons["Edit"].waitForExistence(timeout: 10))
XCTAssertFalse(app.buttons["••••6789"].isSelected)
XCTAssertFalse(app.buttons["•••• 6789"].isSelected)
XCTAssertTrue(app.buttons["Cash App Pay"].isSelected)
XCTAssertTrue(app.staticTexts["Cash App Pay"].waitForExistence(timeout: 10))
}
Expand All @@ -508,10 +508,10 @@ class EmbeddedUITests: PaymentSheetUITestCase {
loadPlayground(app, settings)

app.buttons["Present embedded payment element"].waitForExistenceAndTap()
ensureSPMSelection("••••6789", insteadOf: "•••• 4242")
ensureSPMSelection("•••• 6789", insteadOf: "•••• 4242")

XCTAssertTrue(app.staticTexts["••••6789"].waitForExistence(timeout: 10))
XCTAssertTrue(app.buttons["••••6789"].isSelected)
XCTAssertTrue(app.staticTexts["•••• 6789"].waitForExistence(timeout: 10))
XCTAssertTrue(app.buttons["•••• 6789"].isSelected)
XCTAssertTrue(app.buttons["Checkout"].waitForExistenceAndTap())
XCTAssertTrue(app.staticTexts["Success!"].waitForExistence(timeout: 10))

Expand Down Expand Up @@ -639,7 +639,7 @@ class EmbeddedUITests: PaymentSheetUITestCase {
XCTAssertTrue(app.staticTexts["Add US bank account"].waitForExistence(timeout: 10))
app.buttons["Continue"].waitForExistenceAndTap()
XCTAssertTrue(app.staticTexts["Payment method"].waitForExistence(timeout: 10))
XCTAssertEqual(app.staticTexts["Payment method"].label, "••••6789")
XCTAssertEqual(app.staticTexts["Payment method"].label, "•••• 6789")
XCTAssertTrue(app.buttons["US bank account"].isSelected)
XCTAssertTrue(app.buttons["Checkout"].isEnabled)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ class PaymentSheetStandardLPMUITwoTests: PaymentSheetStandardLPMUICase {
// Reload w/ same customer
reload(app, settings: settings)
// This time, expect SEPA to be pre-selected as the default
XCTAssert(paymentMethodButton.label.hasPrefix("••••3201, sepa_debit"))
XCTAssert(paymentMethodButton.label.hasPrefix("•••• 3201, sepa_debit"))

// Tapping confirm without presenting flowcontroller should show the mandate
app.buttons["Confirm"].tap()
Expand All @@ -575,7 +575,7 @@ class PaymentSheetStandardLPMUITwoTests: PaymentSheetStandardLPMUICase {
// Reload w/ same customer
reload(app, settings: settings)
// If you present the flowcontroller and see the mandate...
XCTAssert(paymentMethodButton.label.hasPrefix("••••3201, sepa_debit"))
XCTAssert(paymentMethodButton.label.hasPrefix("•••• 3201, sepa_debit"))
paymentMethodButton.waitForExistenceAndTap()

XCTAssertTrue(app.otherElements.matching(identifier: "mandatetextview").element.exists)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2699,7 +2699,7 @@ extension PaymentSheetUITestCase {
// Reload and pay with the now-saved US bank account
reload(app, settings: settings)
app.buttons["Present PaymentSheet"].tap()
XCTAssertTrue(app.buttons["••••6789"].waitForExistenceAndTap())
XCTAssertTrue(app.buttons["•••• 6789"].waitForExistenceAndTap())

// Make sure bottom notice mandate is visible
XCTAssertTrue(app.textViews["By continuing, you agree to authorize payments pursuant to these terms."].exists)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ class PaymentSheetVerticalUITests: PaymentSheetUITestCase {
// Reload
reload(app, settings: settings)
XCTAssertTrue(paymentMethodButton.waitForExistence(timeout: 10))
XCTAssertEqual(paymentMethodButton.label, "••••3000, sepa_debit, John Doe, [email protected], 123 Main, San Francisco, CA, 94016, US")
XCTAssertEqual(paymentMethodButton.label, "•••• 3000, sepa_debit, John Doe, [email protected], 123 Main, San Francisco, CA, 94016, US")
paymentMethodButton.tap()

// Switch to the saved card...
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,7 @@
CB225E962CEF80DC00054262 /* PaymentMethodTypeCollectionViewCellSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB225E952CEF80DC00054262 /* PaymentMethodTypeCollectionViewCellSnapshotTests.swift */; };
CB46EF492CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB46EF482CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift */; };
CB46EF4B2CED1BDA00E9A7F2 /* PromoBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB46EF4A2CED1BDA00E9A7F2 /* PromoBadgeView.swift */; };
CBF7BE542D11BF5300A4C172 /* BankAccountInfoViewSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBF7BE532D11BF5300A4C172 /* BankAccountInfoViewSnapshotTests.swift */; };
CD19725E26DBDB9960D828CB /* BottomSheetPresentationAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F09CF961C943E36D76860F /* BottomSheetPresentationAnimator.swift */; };
CF2AD2C7F761C46AE559E563 /* SavedPaymentOptionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B3ECDF6CF9AABD573F86CA2 /* SavedPaymentOptionsViewController.swift */; };
D0B9FBCB359A7D774B98D19E /* LinkCookieKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1928BE9DFF116368B1A19DC /* LinkCookieKey.swift */; };
Expand Down Expand Up @@ -772,6 +773,7 @@
CB46EF482CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentMethodIncentive.swift; sourceTree = "<group>"; };
CB46EF4A2CED1BDA00E9A7F2 /* PromoBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromoBadgeView.swift; sourceTree = "<group>"; };
CBCFE3D39D670C3C77C59722 /* cs-CZ */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "cs-CZ"; path = "cs-CZ.lproj/Localizable.strings"; sourceTree = "<group>"; };
CBF7BE532D11BF5300A4C172 /* BankAccountInfoViewSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BankAccountInfoViewSnapshotTests.swift; sourceTree = "<group>"; };
CC3498CF4AEAA8F169616CDF /* STPCardBrandChoice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STPCardBrandChoice.swift; sourceTree = "<group>"; };
CCA2B5817236F64A212A8C61 /* IntentConfirmParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentConfirmParams.swift; sourceTree = "<group>"; };
CD0150C1C20FD33EA024096A /* Appearance+FontScaling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Appearance+FontScaling.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1657,6 +1659,7 @@
3F70E5F0FE6218715432D55A /* AddPaymentMethodViewControllerSnapshotTests.swift */,
25AB69CF78A2B13839EB32EC /* AddressViewController */,
BE92D55DA4B4D449734B2917 /* BacsDDMandateViewSnapshotTests.swift */,
CBF7BE532D11BF5300A4C172 /* BankAccountInfoViewSnapshotTests.swift */,
0C53358C028E528F0FC626A2 /* CustomerSheet */,
989C2E3E03E42DA64A2FAE0D /* CustomerSheetSnapshotTests.swift */,
31699A822BE183D40048677F /* DownloadManagerTest.swift */,
Expand Down Expand Up @@ -2006,6 +2009,7 @@
316B33122B5F171C0008D2E5 /* UserDefaults+StripePaymentSheetTest.swift in Sources */,
B63DC67A2CC06AD10011C27E /* EmbeddedPaymentElementSnapshotTests.swift in Sources */,
D77514C28C9A031908E99CA1 /* PaymentMethodMessagingViewFunctionalTest.swift in Sources */,
CBF7BE542D11BF5300A4C172 /* BankAccountInfoViewSnapshotTests.swift in Sources */,
D14478CFCABDF7455DA7472A /* PaymentMethodMessagingViewSnapshotTests.swift in Sources */,
3D3607748436E625FF6CF921 /* PaymentSheet+APITest.swift in Sources */,
68F13446778AF2CAA631ACDE /* PaymentSheet+DeferredAPITest.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ re-entering the security code (CVV/CVC). */
/* Title shown above a section containing payment methods that a customer can choose to pay with e.g. card, bank account, etc. */
"New payment method" = "New payment method";

/* Label for when the user is not eligible for a promo. */
"No %@ promo" = "No %@ promo";

/* Text of a label for confirming an email address. E.g., 'Not [email protected]?' */
"Not %@?" = "Not %@?";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ extension STPPaymentMethod {
case .card:
return "•••• \(card?.last4 ?? "")"
case .SEPADebit:
return "••••\(sepaDebit?.last4 ?? "")"
return "•••• \(sepaDebit?.last4 ?? "")"
case .USBankAccount:
return "••••\(usBankAccount?.last4 ?? "")"
return "•••• \(usBankAccount?.last4 ?? "")"
default:
return type.displayName
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ final class IntentConfirmParams {

var paymentSheetLabel: String {
if let last4 = (financialConnectionsLinkedBank?.last4 ?? instantDebitsLinkedBank?.last4) {
return "••••\(last4)"
return "•••• \(last4)"
} else {
return paymentMethodParams.paymentSheetLabel
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,7 @@ extension PaymentMethodTypeCollectionView {
return paymentMethodLogo
}()
private lazy var promoBadge: PromoBadgeView = {
let font = appearance.scaledFont(for: appearance.font.base.medium, style: .footnote, maximumPointSize: 20)
return PromoBadgeView(appearance: appearance, tinyMode: true)
PromoBadgeView(appearance: appearance, tinyMode: true)
}()
private lazy var shadowRoundedRectangle: ShadowedRoundedRectangle = {
return ShadowedRoundedRectangle(appearance: appearance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ extension PaymentSheetFormFactory {
savingAccount: isSaving,
merchantName: merchantName,
initialLinkedBank: previousCustomerInput?.financialConnectionsLinkedBank,
theme: theme
appearance: configuration.appearance
)
}

Expand Down Expand Up @@ -689,7 +689,7 @@ extension PaymentSheetFormFactory {
addressElement: addressElement,
incentive: incentive,
isPaymentIntent: isPaymentIntent,
theme: theme
appearance: configuration.appearance
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,37 @@ class BankAccountInfoView: UIView {
struct Constants {
static let spacing: CGFloat = 12
}

private let appearance: PaymentSheet.Appearance
private let incentive: PaymentMethodIncentive?

private let theme: ElementsAppearance

private var theme: ElementsAppearance {
appearance.asElementsTheme
}
private lazy var accountInfoStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .leading
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(bankNameLabel)
stackView.addArrangedSubview(bankAccountNumberLabel)
return stackView
}()
private lazy var contentStackView: UIStackView = {
let stackView = UIStackView()
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = Constants.spacing
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(accountInfoStackView)
if let promoBadgeView {
stackView.addArrangedSubview(promoBadgeView)
}
return stackView
}()
lazy var bankNameLabel: UILabel = {
let label = UILabel()
label.font = theme.fonts.subheadline
label.font = theme.fonts.subheadline.medium
label.textColor = theme.colors.bodyText
label.numberOfLines = 1
label.adjustsFontSizeToFitWidth = false
Expand All @@ -33,8 +58,8 @@ class BankAccountInfoView: UIView {
}()
lazy var bankAccountNumberLabel: UILabel = {
let label = UILabel()
label.font = theme.fonts.subheadline
label.textColor = theme.colors.bodyText
label.font = theme.fonts.caption
label.textColor = theme.colors.secondaryText
label.numberOfLines = 0
return label
}()
Expand All @@ -46,6 +71,13 @@ class BankAccountInfoView: UIView {
imageView.tintColor = .systemGray2
return imageView
}()

private lazy var promoBadgeView: PromoBadgeView? = {
guard let incentive else {
return nil
}
return PromoBadgeView(appearance: appearance, tinyMode: false, text: incentive.displayText)
}()

lazy var xIcon: UIImageView = {
let xIcon = UIImageView(image: Image.icon_x_standalone.makeImage(template: true))
Expand All @@ -69,8 +101,13 @@ class BankAccountInfoView: UIView {
}
}

init(frame: CGRect, theme: ElementsAppearance = .default) {
self.theme = theme
init(
frame: CGRect,
appearance: PaymentSheet.Appearance = .default,
incentive: PaymentMethodIncentive? = nil
) {
self.appearance = appearance
self.incentive = incentive
super.init(frame: frame)
addViewComponents()
addTouchCallbackForX()
Expand All @@ -82,31 +119,23 @@ class BankAccountInfoView: UIView {

func addViewComponents() {
bankIconImageView.translatesAutoresizingMaskIntoConstraints = false
bankNameLabel.translatesAutoresizingMaskIntoConstraints = false
bankAccountNumberLabel.translatesAutoresizingMaskIntoConstraints = false
xIcon.translatesAutoresizingMaskIntoConstraints = false
xIconTappableArea.translatesAutoresizingMaskIntoConstraints = false

addSubview(bankIconImageView)
addSubview(bankNameLabel)
addSubview(bankAccountNumberLabel)
addSubview(contentStackView)
xIconTappableArea.addSubview(xIcon)
addSubview(xIconTappableArea)

NSLayoutConstraint.activate([
bankIconImageView.centerYAnchor.constraint(equalTo: centerYAnchor),
bankIconImageView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.spacing),

bankNameLabel.leadingAnchor.constraint(equalTo: bankIconImageView.trailingAnchor, constant: Constants.spacing),
bankNameLabel.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.5),
bankNameLabel.topAnchor.constraint(equalTo: topAnchor, constant: Constants.spacing),
bankNameLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.spacing),

bankAccountNumberLabel.leadingAnchor.constraint(equalTo: bankNameLabel.trailingAnchor, constant: Constants.spacing),
bankAccountNumberLabel.topAnchor.constraint(equalTo: topAnchor, constant: Constants.spacing),
bankAccountNumberLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.spacing),

xIconTappableArea.leadingAnchor.constraint(greaterThanOrEqualTo: bankAccountNumberLabel.trailingAnchor, constant: Constants.spacing),

contentStackView.leadingAnchor.constraint(equalTo: bankIconImageView.trailingAnchor, constant: Constants.spacing),
contentStackView.topAnchor.constraint(equalTo: topAnchor, constant: Constants.spacing),
contentStackView.trailingAnchor.constraint(lessThanOrEqualTo: xIconTappableArea.leadingAnchor),
contentStackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -Constants.spacing),

xIconTappableArea.trailingAnchor.constraint(equalTo: trailingAnchor),
xIconTappableArea.widthAnchor.constraint(equalToConstant: 44.0),
xIconTappableArea.topAnchor.constraint(equalTo: topAnchor),
Expand Down Expand Up @@ -137,11 +166,16 @@ class BankAccountInfoView: UIView {
func setLastFourOfBank(text: String) {
self.bankAccountNumberLabel.text = text
}

func setIncentiveEligible(_ eligible: Bool) {
promoBadgeView?.setEligible(eligible)
}

func updateUI() {
bankNameLabel.textColor = theme.colors.textFieldText.disabled(!isUserInteractionEnabled)
bankAccountNumberLabel.textColor = theme.colors.textFieldText.disabled(!isUserInteractionEnabled)
bankIconImageView.alpha = isUserInteractionEnabled ? 1.0 : 0.5
promoBadgeView?.alpha = isUserInteractionEnabled ? 1.0 : 0.5
xIcon.alpha = isUserInteractionEnabled ? 1.0 : 0.5
}
}
Expand Down
Loading

0 comments on commit 6cb4756

Please sign in to comment.