Skip to content

Commit

Permalink
Add promo badge in horizontal and vertical mode
Browse files Browse the repository at this point in the history
  • Loading branch information
tillh-stripe committed Nov 20, 2024
1 parent 064831e commit 91b5ab1
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,8 @@
C28450436BDA52BE9BE3BDC3 /* PaymentSheetConfigurationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E83494558F0C93C5B05A1DFB /* PaymentSheetConfigurationTests.swift */; };
C346B534D57A952D4415ADFD /* Intent+Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04C8047FD8994D3FAA3D1A7A /* Intent+Link.swift */; };
C5E3750BBCA700CF364F7578 /* PaymentSheetFormFactory+OXXO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20379AE078D68A0AC83A6C5 /* PaymentSheetFormFactory+OXXO.swift */; };
CB46EF492CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB46EF482CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift */; };
CB46EF4B2CED1BDA00E9A7F2 /* PromoBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB46EF4A2CED1BDA00E9A7F2 /* PromoBadgeView.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 @@ -751,6 +753,8 @@
C90A2636C2A577AF36FB793B /* PaymentSheetLoaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentSheetLoaderTest.swift; sourceTree = "<group>"; };
C94104A367EAF6C8785C17A1 /* FormSpecProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormSpecProvider.swift; sourceTree = "<group>"; };
C9726902C985C99F69E6880C /* CustomerSheet+PaymentMethodAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CustomerSheet+PaymentMethodAvailability.swift"; sourceTree = "<group>"; };
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>"; };
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>"; };
Expand Down Expand Up @@ -1025,6 +1029,7 @@
22552CD237A259249CD0C592 /* Intent.swift */,
CCA2B5817236F64A212A8C61 /* IntentConfirmParams.swift */,
9A0D887C5AC6EFFAFE1AFD77 /* PaymentMethodType.swift */,
CB46EF482CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift */,
2A19DBD87D0EBC7FA3DFB2A7 /* PaymentOption+Images.swift */,
6B680A2FF197F612D065F16C /* PaymentSheet.swift */,
5CD1A451B238C1D1ADAA72EC /* PaymentSheet+API.swift */,
Expand Down Expand Up @@ -1347,6 +1352,7 @@
B626EE922BF2872200B05B05 /* PaymentMethodTypeImageView.swift */,
383EA30FC9C862DF2217F96D /* PaymentSheetUIKitAdditions.swift */,
9356711AB2961A5F729F3EAA /* PayWithLinkButton.swift */,
CB46EF4A2CED1BDA00E9A7F2 /* PromoBadgeView.swift */,
AF8355E00EC53A8B0C864167 /* RotatingCardBrandsView.swift */,
3556971CA13C767092BE7A34 /* ShadowedRoundedRectangleView.swift */,
6BB97FB5D5730FE4CAB9298D /* SheetNavigationBar.swift */,
Expand Down Expand Up @@ -2045,6 +2051,7 @@
F70BCDEECB5863244085F12F /* BoolReference.swift in Sources */,
8B1D7A7CE7D50382E9FA77E3 /* Images.swift in Sources */,
D3CC2489468E3288FD34C160 /* IntentStatusPoller.swift in Sources */,
CB46EF492CED1A2E00E9A7F2 /* PaymentMethodIncentive.swift in Sources */,
96C307CDEE7028B12D9CB69B /* PaymentSheetLinkAccount.swift in Sources */,
623C2D9F87929D6DA9C09E23 /* STPCameraView.swift in Sources */,
599337DB99E9E7017EF47BCE /* STPCardScanner.swift in Sources */,
Expand Down Expand Up @@ -2230,6 +2237,7 @@
AF0D609C28A8B0ECD11FD539 /* UpdateCardViewController.swift in Sources */,
D203D701AF400680AF0F82F8 /* AUBECSMandate.swift in Sources */,
D792BA37B04E5A3AD30E37CF /* AffirmCopyLabel.swift in Sources */,
CB46EF4B2CED1BDA00E9A7F2 /* PromoBadgeView.swift in Sources */,
9BFC22175CF85F58B8B8792A /* AfterpayPriceBreakdownView.swift in Sources */,
EA712D67C03385B9AD80288C /* Appearance+FontScaling.swift in Sources */,
B667BF0B2BF2B7C60050EFD8 /* RowButton.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ class CustomerAddPaymentMethodViewController: UIViewController {
}()
private lazy var paymentMethodTypesView: PaymentMethodTypeCollectionView = {
let view = PaymentMethodTypeCollectionView(
paymentMethodTypes: paymentMethodTypes, appearance: configuration.appearance, delegate: self)
paymentMethodTypes: paymentMethodTypes,
appearance: configuration.appearance,
incentive: nil,
delegate: self
)
return view
}()
private lazy var paymentMethodDetailsContainerView: DynamicHeightContainerView = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class AddPaymentMethodViewController: UIViewController {
private var paymentMethodFormElement: PaymentMethodElement {
paymentMethodFormViewController.form
}

private var incentive: PaymentMethodIncentive? {
elementsSession.linkSettings?.linkConsumerIncentive.flatMap { PaymentMethodIncentive(from: $0) }
}

// MARK: - Views
private lazy var paymentMethodFormViewController: PaymentMethodFormViewController = {
Expand All @@ -72,6 +76,7 @@ class AddPaymentMethodViewController: UIViewController {
paymentMethodTypes: paymentMethodTypes,
initialPaymentMethodType: previousCustomerInput?.paymentMethodType,
appearance: configuration.appearance,
incentive: incentive,
delegate: self
)
return view
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,20 @@ class PaymentMethodTypeCollectionView: UICollectionView {
let paymentMethodTypes: [PaymentSheet.PaymentMethodType]
let appearance: PaymentSheet.Appearance
weak var _delegate: PaymentMethodTypeCollectionViewDelegate?

private var incentive: PaymentMethodIncentive?

init(
paymentMethodTypes: [PaymentSheet.PaymentMethodType],
initialPaymentMethodType: PaymentSheet.PaymentMethodType? = nil,
appearance: PaymentSheet.Appearance,
incentive: PaymentMethodIncentive?,
delegate: PaymentMethodTypeCollectionViewDelegate
) {
stpAssert(!paymentMethodTypes.isEmpty, "At least one payment method type must be provided.")

self.paymentMethodTypes = paymentMethodTypes
self.incentive = incentive
self._delegate = delegate
let selectedItemIndex: Int = {
if let initialPaymentMethodType = initialPaymentMethodType {
Expand Down Expand Up @@ -82,6 +86,11 @@ class PaymentMethodTypeCollectionView: UICollectionView {
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func setIncentive(_ incentive: PaymentMethodIncentive?) {
self.incentive = incentive
reloadData()
}

override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: PaymentMethodTypeCollectionView.cellHeight)
Expand Down Expand Up @@ -113,7 +122,9 @@ extension PaymentMethodTypeCollectionView: UICollectionViewDataSource, UICollect
stpAssertionFailure()
return UICollectionViewCell()
}
cell.paymentMethodType = paymentMethodTypes[indexPath.item]
let paymentMethodType = paymentMethodTypes[indexPath.item]
cell.paymentMethodType = paymentMethodType
cell.promoBadgeText = incentive?.takeIfAppliesTo(paymentMethodType)?.displayText
cell.appearance = appearance
return cell
}
Expand Down Expand Up @@ -160,6 +171,12 @@ extension PaymentMethodTypeCollectionView {
update()
}
}

var promoBadgeText: String? = nil {
didSet {
update()
}
}

var appearance: PaymentSheet.Appearance = PaymentSheet.Appearance.default {
didSet {
Expand All @@ -182,6 +199,10 @@ extension PaymentMethodTypeCollectionView {
paymentMethodLogo.contentMode = .scaleAspectFit
return paymentMethodLogo
}()
private lazy var promoBadge: PromoBadgeView = {
let font = appearance.scaledFont(for: appearance.font.base.medium, style: .footnote, maximumPointSize: 20)
return PromoBadgeView(font: font, tinyMode: true)
}()
private lazy var shadowRoundedRectangle: ShadowedRoundedRectangle = {
return ShadowedRoundedRectangle(appearance: appearance)
}()
Expand Down Expand Up @@ -210,7 +231,7 @@ extension PaymentMethodTypeCollectionView {
override init(frame: CGRect) {
super.init(frame: frame)

[paymentMethodLogo, label].forEach {
[paymentMethodLogo, label, promoBadge].forEach {
shadowRoundedRectangle.addSubview($0)
$0.translatesAutoresizingMaskIntoConstraints = false
}
Expand All @@ -233,6 +254,10 @@ extension PaymentMethodTypeCollectionView {
equalTo: shadowRoundedRectangle.bottomAnchor, constant: -8),
label.leadingAnchor.constraint(equalTo: paymentMethodLogo.leadingAnchor),
label.trailingAnchor.constraint(equalTo: shadowRoundedRectangle.trailingAnchor, constant: -12), // should be -const of paymentMethodLogo leftAnchor

promoBadge.topAnchor.constraint(equalTo: paymentMethodLogo.topAnchor),
promoBadge.trailingAnchor.constraint(equalTo: shadowRoundedRectangle.trailingAnchor, constant: -12),
promoBadge.bottomAnchor.constraint(equalTo: paymentMethodLogo.bottomAnchor),
])

contentView.layer.cornerRadius = appearance.cornerRadius
Expand Down Expand Up @@ -274,9 +299,11 @@ extension PaymentMethodTypeCollectionView {
case .shouldDisableUserInteraction:
self.label.alpha = 0.6
self.paymentMethodLogo.alpha = 0.6
self.promoBadge.alpha = 0.6
case .shouldEnableUserInteraction:
self.label.alpha = 1
self.paymentMethodLogo.alpha = 1
self.promoBadge.alpha = 1
default:
break
}
Expand Down Expand Up @@ -308,6 +335,11 @@ extension PaymentMethodTypeCollectionView {
if paymentMethodTypeOfCurrentImage != self.paymentMethodType || image.size != CGSize(width: 1, height: 1) {
updateImage(image)
}

promoBadge.isHidden = promoBadgeText == nil
if let promoBadgeText {
promoBadge.setText("Get \(promoBadgeText)")
}

shadowRoundedRectangle.isSelected = isSelected
// Set text color
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// PaymentMethodIncentive.swift
// StripePaymentSheet
//
// Created by Till Hellmund on 11/19/24.
//

import Foundation
@_spi(STP) import StripePayments

struct PaymentMethodIncentive {

let identifier: String
let displayText: String

init(
identifier: String,
text: String
) {
self.identifier = identifier
self.displayText = text
}

func takeIfAppliesTo(_ paymentMethodType: PaymentSheet.PaymentMethodType) -> PaymentMethodIncentive? {
switch paymentMethodType {
case .stripe, .external:
return nil
case .instantDebits, .linkCardBrand:
return identifier == "link_instant_debits" ? self : nil
}
}
}

extension PaymentMethodIncentive {

init?(from incentive: LinkConsumerIncentive) {
guard let displayText = incentive.incentiveDisplayText else {
return nil
}

self.identifier = incentive.incentiveParams.paymentMethod
self.displayText = displayText
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ extension RowButton {
return label
}

static func makeForPaymentMethodType(paymentMethodType: PaymentSheet.PaymentMethodType, subtitle: String? = nil, savedPaymentMethodType: STPPaymentMethodType?, appearance: PaymentSheet.Appearance, shouldAnimateOnPress: Bool, isEmbedded: Bool = false, didTap: @escaping DidTapClosure) -> RowButton {
static func makeForPaymentMethodType(paymentMethodType: PaymentSheet.PaymentMethodType, subtitle: String? = nil, rightAccessoryView: UIView? = nil, savedPaymentMethodType: STPPaymentMethodType?, appearance: PaymentSheet.Appearance, shouldAnimateOnPress: Bool, isEmbedded: Bool = false, didTap: @escaping DidTapClosure) -> RowButton {
let imageView = PaymentMethodTypeImageView(paymentMethodType: paymentMethodType, backgroundColor: appearance.colors.componentBackground)
imageView.contentMode = .scaleAspectFit
// Special case "New card" vs "Card" title
Expand All @@ -277,7 +277,7 @@ extension RowButton {
}
return paymentMethodType.displayName
}()
return RowButton(appearance: appearance, imageView: imageView, text: text, subtext: subtitle, shouldAnimateOnPress: shouldAnimateOnPress, isEmbedded: isEmbedded, didTap: didTap)
return RowButton(appearance: appearance, imageView: imageView, text: text, subtext: subtitle, rightAccessoryView: rightAccessoryView, shouldAnimateOnPress: shouldAnimateOnPress, isEmbedded: isEmbedded, didTap: didTap)
}

static func makeForApplePay(appearance: PaymentSheet.Appearance, isEmbedded: Bool = false, didTap: @escaping DidTapClosure) -> RowButton {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class VerticalPaymentMethodListViewController: UIViewController {
let stackView = UIStackView()
let appearance: PaymentSheet.Appearance
weak var delegate: VerticalPaymentMethodListViewControllerDelegate?

private var incentive: PaymentMethodIncentive?

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
Expand All @@ -40,10 +42,12 @@ class VerticalPaymentMethodListViewController: UIViewController {
appearance: PaymentSheet.Appearance,
currency: String?,
amount: Int?,
incentive: PaymentMethodIncentive?,
delegate: VerticalPaymentMethodListViewControllerDelegate
) {
self.delegate = delegate
self.appearance = appearance
self.incentive = incentive
self.delegate = delegate
super.init(nibName: nil, bundle: nil)

Expand Down Expand Up @@ -111,9 +115,21 @@ class VerticalPaymentMethodListViewController: UIViewController {
let paymentMethodTypes = paymentMethodTypes
for paymentMethodType in paymentMethodTypes {
let selection = VerticalPaymentMethodListSelection.new(paymentMethodType: paymentMethodType)
let rightAccessoryView = incentive?.takeIfAppliesTo(paymentMethodType).flatMap { incentive in
PromoBadgeView(
font: appearance.scaledFont(
for: appearance.font.base.medium,
style: .subheadline,
maximumPointSize: 25
),
tinyMode: false,
text: "Get \(incentive.displayText)"
)
}
let rowButton = RowButton.makeForPaymentMethodType(
paymentMethodType: paymentMethodType,
subtitle: Self.subtitleText(for: paymentMethodType),
rightAccessoryView: rightAccessoryView,
savedPaymentMethodType: savedPaymentMethod?.type,
appearance: appearance,
// Enable press animation if tapping this transitions the screen to a form instead of becoming selected
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,7 @@ class PaymentSheetVerticalViewController: UIViewController, FlowControllerViewCo
appearance: configuration.appearance,
currency: loadResult.intent.currency,
amount: loadResult.intent.amount,
incentive: loadResult.elementsSession.linkSettings?.linkConsumerIncentive.flatMap { PaymentMethodIncentive(from: $0) },
delegate: self
)
}
Expand Down
Loading

0 comments on commit 91b5ab1

Please sign in to comment.