Skip to content

Commit 44d4e53

Browse files
committed
8116 Show correct tap/insert/swipe reader message
Previously, we did not take account of the ReaderInputOptions returned to us by the Stripe Terminal API, instead we always displayed “Tap, insert, or swipe to pay”, even when those options were not supported, or the SDK was requesting a different method. This change passes the ReaderInputOptions to the View Model concerned with showing this message to the merchant, and converts the various supported options into specific localized strings, along with a (currently unused) default of “Present card to pay”. To see this in action, compare the messages shown when using a US card reader (M2 or chipper, which both support swipe reads) to a CA reader (which only supports tap or insert reads.)
1 parent 96760d0 commit 44d4e53

File tree

14 files changed

+142
-23
lines changed

14 files changed

+142
-23
lines changed

Hardware/Hardware.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
028C39E028255CFE0007BA25 /* Models+Copiable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = 028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */; };
1111
02B5147A28254ED300750B71 /* Codegen in Frameworks */ = {isa = PBXBuildFile; productRef = 02B5147928254ED300750B71 /* Codegen */; };
1212
030338102705F7D400764131 /* ReceiptTotalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0303380F2705F7D400764131 /* ReceiptTotalLine.swift */; };
13+
035DBA3929251ED6003E5125 /* CardReaderInputOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 035DBA3829251ED6003E5125 /* CardReaderInputOptions.swift */; };
1314
039D948B2760C0660044EF38 /* NoOpCardReaderService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 039D948A2760C0660044EF38 /* NoOpCardReaderService.swift */; };
1415
03B440AA2754DFC400759429 /* UnderlyingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03B440A92754DFC400759429 /* UnderlyingError.swift */; };
1516
03CF78D327C6710C00523706 /* interac.svg in Resources */ = {isa = PBXBuildFile; fileRef = 03CF78D227C6710B00523706 /* interac.svg */; };
@@ -153,6 +154,7 @@
153154
02351FF56149ADCD11338B19 /* Pods-SampleReceiptPrinter.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SampleReceiptPrinter.release.xcconfig"; path = "Target Support Files/Pods-SampleReceiptPrinter/Pods-SampleReceiptPrinter.release.xcconfig"; sourceTree = "<group>"; };
154155
028C39DF28255CFE0007BA25 /* Models+Copiable.generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Models+Copiable.generated.swift"; sourceTree = "<group>"; };
155156
0303380F2705F7D400764131 /* ReceiptTotalLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptTotalLine.swift; sourceTree = "<group>"; };
157+
035DBA3829251ED6003E5125 /* CardReaderInputOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CardReaderInputOptions.swift; sourceTree = "<group>"; };
156158
039D948A2760C0660044EF38 /* NoOpCardReaderService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoOpCardReaderService.swift; sourceTree = "<group>"; };
157159
03B440A92754DFC400759429 /* UnderlyingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnderlyingError.swift; sourceTree = "<group>"; };
158160
03CF78D227C6710B00523706 /* interac.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = interac.svg; sourceTree = "<group>"; };
@@ -439,6 +441,7 @@
439441
D88FDB3625DD21BE00CB0DBD /* StripeCardReader */,
440442
D88FDB2725DD21B000CB0DBD /* CardReader.swift */,
441443
D88FDB2425DD21B000CB0DBD /* CardReaderEvent.swift */,
444+
035DBA3829251ED6003E5125 /* CardReaderInputOptions.swift */,
442445
D88FDB2625DD21B000CB0DBD /* CardReaderService.swift */,
443446
D88FDB1F25DD21AF00CB0DBD /* CardReaderServiceDiscoveryStatus.swift */,
444447
D88FDB2125DD21AF00CB0DBD /* CardReaderServiceStatus.swift */,
@@ -826,6 +829,7 @@
826829
D845BE54262ED7CC00A3E40F /* PrinterService.swift in Sources */,
827830
D845BDDE262DAB8300A3E40F /* PaymentMethod+Stripe.swift in Sources */,
828831
D89B8F0C25DDC9D30001C726 /* ChargeStatus.swift in Sources */,
832+
035DBA3929251ED6003E5125 /* CardReaderInputOptions.swift in Sources */,
829833
E140F61C2668CDC900FDB5FF /* Logging.swift in Sources */,
830834
03CF78D727DF9BE600523706 /* RefundParameters.swift in Sources */,
831835
03B440AA2754DFC400759429 /* UnderlyingError.swift in Sources */,

Hardware/Hardware/CardReader/CardReaderEvent.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
public enum CardReaderEvent: Equatable {
33
/// The reader begins waiting for input.
44
/// The app should prompt the customer to present a payment method
5-
case waitingForInput(String)
5+
case waitingForInput(CardReaderInput)
66

77
/// Request that a prompt be displayed in the app.
88
/// For example, if the prompt is SwipeCard,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
3+
public struct CardReaderInput: OptionSet {
4+
public let rawValue: Int
5+
6+
public init(rawValue: Int) {
7+
self.rawValue = rawValue
8+
}
9+
10+
public static let none = CardReaderInput([])
11+
public static let swipe = CardReaderInput(rawValue: 1 << 0)
12+
public static let insert = CardReaderInput(rawValue: 1 << 1)
13+
public static let tap = CardReaderInput(rawValue: 1 << 2)
14+
}
15+
16+
17+
#if !targetEnvironment(macCatalyst)
18+
import StripeTerminal
19+
20+
extension CardReaderInput {
21+
init(stripeReaderInputOptions: ReaderInputOptions) {
22+
let value = Int(stripeReaderInputOptions.rawValue)
23+
self.init(rawValue: value)
24+
}
25+
}
26+
#endif

Hardware/Hardware/CardReader/StripeCardReader/CardReaderEvent+Stripe.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ import StripeTerminal
44
extension CardReaderEvent {
55
/// Factory method
66
/// - Parameter readerInputOptions: An instance of a StripeTerminal.ReaderInputOptions
7-
static func make(readerInputOptions: ReaderInputOptions) -> Self {
8-
.waitingForInput(Terminal.stringFromReaderInputOptions(readerInputOptions))
7+
static func make(stripeReaderInputOptions: ReaderInputOptions) -> Self {
8+
let inputOptions = CardReaderInput(stripeReaderInputOptions: stripeReaderInputOptions)
9+
return .waitingForInput(inputOptions)
910
}
1011

1112
/// Factory method

Hardware/Hardware/CardReader/StripeCardReader/StripeCardReaderService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,7 @@ extension StripeCardReaderService: BluetoothReaderDelegate {
646646
/// This method is called by the Stripe Terminal SDK when it wants client apps
647647
/// to request users to tap / insert / swipe a card.
648648
public func reader(_ reader: Reader, didRequestReaderInput inputOptions: ReaderInputOptions = []) {
649-
sendReaderEvent(CardReaderEvent.make(readerInputOptions: inputOptions))
649+
sendReaderEvent(CardReaderEvent.make(stripeReaderInputOptions: inputOptions))
650650
}
651651

652652
/// In this case the Stripe Terminal SDK wants us to present a string on screen

WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalTapCard.swift

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,26 @@ final class CardPresentModalTapCard: CardPresentPaymentsModalViewModel {
3838

3939
let accessibilityLabel: String?
4040

41-
init(name: String, amount: String, transactionType: CardPresentTransactionType, onCancel: @escaping () -> Void) {
41+
init(name: String,
42+
amount: String,
43+
transactionType: CardPresentTransactionType,
44+
inputMethods: CardReaderInput,
45+
onCancel: @escaping () -> Void) {
4246
self.name = name
4347
self.amount = amount
44-
self.bottomSubtitle = Localization.tapInsertOrSwipe(transactionType: transactionType)
48+
49+
if inputMethods == CardReaderInput([.swipe, .insert, .tap]) {
50+
self.bottomSubtitle = Localization.tapInsertOrSwipe(transactionType: transactionType)
51+
} else if inputMethods == CardReaderInput([.tap, .insert]) {
52+
self.bottomSubtitle = Localization.tapOrInsert(transactionType: transactionType)
53+
} else if inputMethods.contains(.tap) {
54+
self.bottomSubtitle = Localization.tap(transactionType: transactionType)
55+
} else if inputMethods.contains(.insert) {
56+
self.bottomSubtitle = Localization.insert(transactionType: transactionType)
57+
} else {
58+
self.bottomSubtitle = Localization.presentCard(transactionType: transactionType)
59+
}
60+
4561
self.accessibilityLabel = Localization.readerIsReady + Localization.tapInsertOrSwipe(transactionType: transactionType)
4662
self.onCancel = onCancel
4763
}
@@ -83,6 +99,66 @@ private extension CardPresentModalTapCard {
8399
}
84100
}
85101

102+
static func tapOrInsert(transactionType: CardPresentTransactionType) -> String {
103+
switch transactionType {
104+
case .collectPayment:
105+
return NSLocalizedString(
106+
"Tap or insert card to pay",
107+
comment: "Label asking users to present a card. Presented to users when a payment is going to be collected"
108+
)
109+
case .refund:
110+
return NSLocalizedString(
111+
"Tap or insert card to refund",
112+
comment: "Label asking users to present a card. Presented to users when an in-person refund is going to be executed"
113+
)
114+
}
115+
}
116+
117+
static func tap(transactionType: CardPresentTransactionType) -> String {
118+
switch transactionType {
119+
case .collectPayment:
120+
return NSLocalizedString(
121+
"Tap card to pay",
122+
comment: "Label asking users to present a card. Presented to users when a payment is going to be collected"
123+
)
124+
case .refund:
125+
return NSLocalizedString(
126+
"Tap card to refund",
127+
comment: "Label asking users to present a card. Presented to users when an in-person refund is going to be executed"
128+
)
129+
}
130+
}
131+
132+
static func insert(transactionType: CardPresentTransactionType) -> String {
133+
switch transactionType {
134+
case .collectPayment:
135+
return NSLocalizedString(
136+
"Insert card to pay",
137+
comment: "Label asking users to present a card. Presented to users when a payment is going to be collected"
138+
)
139+
case .refund:
140+
return NSLocalizedString(
141+
"Insert card to refund",
142+
comment: "Label asking users to present a card. Presented to users when an in-person refund is going to be executed"
143+
)
144+
}
145+
}
146+
147+
static func presentCard(transactionType: CardPresentTransactionType) -> String {
148+
switch transactionType {
149+
case .collectPayment:
150+
return NSLocalizedString(
151+
"Present card to pay",
152+
comment: "Label asking users to present a card. Presented to users when a payment is going to be collected"
153+
)
154+
case .refund:
155+
return NSLocalizedString(
156+
"Present card to refund",
157+
comment: "Label asking users to present a card. Presented to users when an in-person refund is going to be executed"
158+
)
159+
}
160+
}
161+
86162
static let cancel = NSLocalizedString(
87163
"Cancel",
88164
comment: "Button to cancel a payment"

WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentRefundOrchestrator.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ final class CardPresentRefundOrchestrator {
2727
func refund(amount: Decimal,
2828
charge: WCPayCharge,
2929
paymentGatewayAccount: PaymentGatewayAccount,
30-
onWaitingForInput: @escaping () -> Void,
30+
onWaitingForInput: @escaping (CardReaderInput) -> Void,
3131
onProcessingMessage: @escaping () -> Void,
3232
onDisplayMessage: @escaping (String) -> Void,
3333
onCompletion: @escaping (Result<Void, Error>) -> Void) {
@@ -40,8 +40,8 @@ final class CardPresentRefundOrchestrator {
4040
let refundAction = CardPresentPaymentAction.refundPayment(parameters: refundParameters,
4141
onCardReaderMessage: { event in
4242
switch event {
43-
case .waitingForInput:
44-
onWaitingForInput()
43+
case .waitingForInput(let inputMethods):
44+
onWaitingForInput(inputMethods)
4545
case .displayMessage(let message):
4646
onDisplayMessage(message)
4747
default:

WooCommerce/Classes/ViewModels/CardPresentPayments/PaymentCaptureOrchestrator.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ final class PaymentCaptureOrchestrator {
3939
paymentGatewayAccount: PaymentGatewayAccount,
4040
paymentMethodTypes: [String],
4141
stripeSmallestCurrencyUnitMultiplier: Decimal,
42-
onWaitingForInput: @escaping () -> Void,
42+
onWaitingForInput: @escaping (CardReaderInput) -> Void,
4343
onProcessingMessage: @escaping () -> Void,
4444
onDisplayMessage: @escaping (String) -> Void,
4545
onProcessingCompletion: @escaping (PaymentIntent) -> Void,
@@ -68,8 +68,8 @@ final class PaymentCaptureOrchestrator {
6868
parameters: parameters,
6969
onCardReaderMessage: { event in
7070
switch event {
71-
case .waitingForInput:
72-
onWaitingForInput()
71+
case .waitingForInput(let inputMethods):
72+
onWaitingForInput(inputMethods)
7373
case .displayMessage(let message):
7474
onDisplayMessage(message)
7575
case .cardRemovedAfterClientSidePaymentCapture:

WooCommerce/Classes/ViewModels/Order Details/OrderDetailsPaymentAlerts.swift

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import MessageUI
22
import UIKit
33
import WordPressUI
4+
import Yosemite
45
import enum Hardware.CardReaderServiceError
56
import enum Hardware.UnderlyingError
67

@@ -50,13 +51,16 @@ final class OrderDetailsPaymentAlerts: OrderDetailsPaymentAlertsProtocol {
5051
presentViewModel(viewModel: CardPresentModalPreparingReader(cancelAction: onCancel))
5152
}
5253

53-
func tapOrInsertCard(title: String, amount: String, onCancel: @escaping () -> Void) {
54+
func tapOrInsertCard(title: String,
55+
amount: String,
56+
inputMethods: CardReaderInput,
57+
onCancel: @escaping () -> Void) {
5458
self.name = title
5559
self.amount = amount
5660

5761
// Initial presentation of the modal view controller. We need to provide
5862
// a customer name and an amount.
59-
let viewModel = tapOrInsert(onCancel: onCancel)
63+
let viewModel = tapOrInsert(readerInputMethods: inputMethods, onCancel: onCancel)
6064
presentViewModel(viewModel: viewModel)
6165
}
6266

@@ -94,8 +98,12 @@ final class OrderDetailsPaymentAlerts: OrderDetailsPaymentAlertsProtocol {
9498
}
9599

96100
private extension OrderDetailsPaymentAlerts {
97-
func tapOrInsert(onCancel: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
98-
CardPresentModalTapCard(name: name, amount: amount, transactionType: transactionType, onCancel: onCancel)
101+
func tapOrInsert(readerInputMethods: CardReaderInput, onCancel: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {
102+
CardPresentModalTapCard(name: name,
103+
amount: amount,
104+
transactionType: transactionType,
105+
inputMethods: readerInputMethods,
106+
onCancel: onCancel)
99107
}
100108

101109
func displayMessage(message: String) -> CardPresentPaymentsModalViewModel {

WooCommerce/Classes/ViewModels/Order Details/OrderDetailsPaymentAlertsProtocol.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import UIKit
2+
import Yosemite
23

34
/// Protocol for `OrderDetailsPaymentAlerts` to enable unit testing.
45
protocol OrderDetailsPaymentAlertsProtocol {
56
func presentViewModel(viewModel: CardPresentPaymentsModalViewModel)
67

78
func preparingReader(onCancel: @escaping () -> Void)
89

9-
func tapOrInsertCard(title: String, amount: String, onCancel: @escaping () -> Void)
10+
func tapOrInsertCard(title: String, amount: String, inputMethods: CardReaderInput, onCancel: @escaping () -> Void)
1011

1112
func displayReaderMessage(message: String)
1213

0 commit comments

Comments
 (0)