Skip to content

Commit e1be2a5

Browse files
committed
Add card_reader_model property to collect payment events.
1 parent 51482fe commit e1be2a5

File tree

9 files changed

+72
-19
lines changed

9 files changed

+72
-19
lines changed

Hardware/Hardware/CardReader/CardReaderType.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public enum CardReaderType {
1414
extension CardReaderType {
1515
/// A human-readable model name for the reader.
1616
///
17-
var model: String {
17+
public var model: String {
1818
/// This should match the Android SDK deviceName, to simplify Analytics use
1919
/// https://stripe.dev/stripe-terminal-android/external/external/com.stripe.stripeterminal.external.models/-device-type/index.html
2020
/// pbUcTB-r9-p2#comment-2164

WooCommerce/Classes/Analytics/WooAnalyticsEvent.swift

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,7 @@ extension WooAnalyticsEvent {
573573

574574
enum Keys {
575575
static let batteryLevel = "battery_level"
576+
static let cardReaderModel = "card_reader_model"
576577
static let countryCode = "country"
577578
static let gatewayID = "plugin_slug"
578579
static let errorDescription = "error_description"
@@ -609,9 +610,11 @@ extension WooAnalyticsEvent {
609610
/// - forGatewayID: the plugin (e.g. "woocommerce-payments" or "woocommerce-gateway-stripe") to be included in the event properties in Tracks.
610611
/// - batteryLevel: the battery level (if available) to be included in the event properties in Tracks, e.g. 0.75 = 75%.
611612
/// - countryCode: the country code of the store.
613+
/// - cardReaderModel: the model type of the card reader.
612614
///
613-
static func cardReaderConnectionSuccess(forGatewayID: String?, batteryLevel: Float?, countryCode: String) -> WooAnalyticsEvent {
615+
static func cardReaderConnectionSuccess(forGatewayID: String?, batteryLevel: Float?, countryCode: String, cardReaderModel: String) -> WooAnalyticsEvent {
614616
var properties = [
617+
Keys.cardReaderModel: cardReaderModel,
615618
Keys.countryCode: countryCode,
616619
Keys.gatewayID: gatewayID(forGatewayID: forGatewayID)
617620
]
@@ -769,14 +772,15 @@ extension WooAnalyticsEvent {
769772
/// - Parameters:
770773
/// - forGatewayID: the plugin (e.g. "woocommerce-payments" or "woocommerce-gateway-stripe") to be included in the event properties in Tracks.
771774
/// - countryCode: the country code of the store.
775+
/// - cardReaderModel: the model type of the card reader.
772776
///
773-
static func collectPaymentTapped(forGatewayID: String?, countryCode: String) -> WooAnalyticsEvent {
777+
static func collectPaymentTapped(forGatewayID: String?, countryCode: String, cardReaderModel: String) -> WooAnalyticsEvent {
774778
WooAnalyticsEvent(statName: .collectPaymentTapped,
775779
properties: [
780+
Keys.cardReaderModel: cardReaderModel,
776781
Keys.countryCode: countryCode,
777782
Keys.gatewayID: gatewayID(forGatewayID: forGatewayID)
778-
]
779-
)
783+
])
780784
}
781785

782786
/// Tracked when the payment collection fails
@@ -785,8 +789,9 @@ extension WooAnalyticsEvent {
785789
/// - forGatewayID: the plugin (e.g. "woocommerce-payments" or "woocommerce-gateway-stripe") to be included in the event properties in Tracks.
786790
/// - error: the error to be included in the event properties.
787791
/// - countryCode: the country code of the store.
792+
/// - cardReaderModel: the model type of the card reader, if available.
788793
///
789-
static func collectPaymentFailed(forGatewayID: String?, error: Error, countryCode: String) -> WooAnalyticsEvent {
794+
static func collectPaymentFailed(forGatewayID: String?, error: Error, countryCode: String, cardReaderModel: String?) -> WooAnalyticsEvent {
790795
let paymentMethod: PaymentMethod? = {
791796
guard case let CardReaderServiceError.paymentCaptureWithPaymentMethod(_, paymentMethod) = error else {
792797
return nil
@@ -813,6 +818,7 @@ extension WooAnalyticsEvent {
813818
}
814819
}()
815820
let properties: [String: WooAnalyticsEventPropertyType] = [
821+
Keys.cardReaderModel: cardReaderModel,
816822
Keys.countryCode: countryCode,
817823
Keys.gatewayID: gatewayID(forGatewayID: forGatewayID),
818824
Keys.paymentMethodType: paymentMethod?.analyticsValue,
@@ -827,10 +833,12 @@ extension WooAnalyticsEvent {
827833
/// - Parameters:
828834
/// - forGatewayID: the plugin (e.g. "woocommerce-payments" or "woocommerce-gateway-stripe") to be included in the event properties in Tracks.
829835
/// - countryCode: the country code of the store.
836+
/// - cardReaderModel: the model type of the card reader.
830837
///
831-
static func collectPaymentCanceled(forGatewayID: String?, countryCode: String) -> WooAnalyticsEvent {
838+
static func collectPaymentCanceled(forGatewayID: String?, countryCode: String, cardReaderModel: String) -> WooAnalyticsEvent {
832839
WooAnalyticsEvent(statName: .collectPaymentCanceled,
833840
properties: [
841+
Keys.cardReaderModel: cardReaderModel,
834842
Keys.countryCode: countryCode,
835843
Keys.gatewayID: gatewayID(forGatewayID: forGatewayID)
836844
]
@@ -843,10 +851,15 @@ extension WooAnalyticsEvent {
843851
/// - forGatewayID: the plugin (e.g. "woocommerce-payments" or "woocommerce-gateway-stripe") to be included in the event properties in Tracks.
844852
/// - countryCode: the country code of the store.
845853
/// - paymentMethod: the payment method of the captured payment.
854+
/// - cardReaderModel: the model type of the card reader.
846855
///
847-
static func collectPaymentSuccess(forGatewayID: String?, countryCode: String, paymentMethod: PaymentMethod) -> WooAnalyticsEvent {
856+
static func collectPaymentSuccess(forGatewayID: String?,
857+
countryCode: String,
858+
paymentMethod: PaymentMethod,
859+
cardReaderModel: String) -> WooAnalyticsEvent {
848860
WooAnalyticsEvent(statName: .collectPaymentSuccess,
849861
properties: [
862+
Keys.cardReaderModel: cardReaderModel,
850863
Keys.countryCode: countryCode,
851864
Keys.gatewayID: gatewayID(forGatewayID: forGatewayID),
852865
Keys.paymentMethodType: paymentMethod.analyticsValue

WooCommerce/Classes/ViewModels/CardPresentPayments/CardPresentModalReaderIsReady.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ final class CardPresentModalReaderIsReady: CardPresentPaymentsModalViewModel {
4343

4444
private let paymentGatewayAccountID: String?
4545
private let countryCode: String
46+
private let cardReaderModel: String
4647
private let analytics: Analytics
4748

48-
init(name: String, amount: String, paymentGatewayAccountID: String?, countryCode: String, analytics: Analytics = ServiceLocator.analytics) {
49+
init(name: String,
50+
amount: String,
51+
paymentGatewayAccountID: String?,
52+
countryCode: String,
53+
cardReaderModel: String,
54+
analytics: Analytics = ServiceLocator.analytics) {
4955
self.name = name
5056
self.amount = amount
5157
self.paymentGatewayAccountID = paymentGatewayAccountID
5258
self.countryCode = countryCode
59+
self.cardReaderModel = cardReaderModel
5360
self.analytics = analytics
5461
}
5562

@@ -60,7 +67,8 @@ final class CardPresentModalReaderIsReady: CardPresentPaymentsModalViewModel {
6067
func didTapSecondaryButton(in viewController: UIViewController?) {
6168
analytics.track(event: WooAnalyticsEvent.InPersonPayments
6269
.collectPaymentCanceled(forGatewayID: paymentGatewayAccountID,
63-
countryCode: countryCode))
70+
countryCode: countryCode,
71+
cardReaderModel: cardReaderModel))
6472

6573
let action = CardPresentPaymentAction.cancelPayment(onCompletion: nil)
6674

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ final class OrderDetailsPaymentAlerts {
2626

2727
private let paymentGatewayAccountID: String?
2828
private let countryCode: String
29+
private let cardReaderModel: String
2930

30-
init(presentingController: UIViewController, paymentGatewayAccountID: String?, countryCode: String) {
31+
init(presentingController: UIViewController, paymentGatewayAccountID: String?, countryCode: String, cardReaderModel: String) {
3132
self.presentingController = presentingController
3233
self.paymentGatewayAccountID = paymentGatewayAccountID
3334
self.countryCode = countryCode
35+
self.cardReaderModel = cardReaderModel
3436
}
3537

3638
func presentViewModel(viewModel: CardPresentPaymentsModalViewModel) {
@@ -94,7 +96,11 @@ final class OrderDetailsPaymentAlerts {
9496

9597
private extension OrderDetailsPaymentAlerts {
9698
func readerIsReady() -> CardPresentPaymentsModalViewModel {
97-
CardPresentModalReaderIsReady(name: name, amount: amount, paymentGatewayAccountID: paymentGatewayAccountID, countryCode: countryCode)
99+
CardPresentModalReaderIsReady(name: name,
100+
amount: amount,
101+
paymentGatewayAccountID: paymentGatewayAccountID,
102+
countryCode: countryCode,
103+
cardReaderModel: cardReaderModel)
98104
}
99105

100106
func tapOrInsert(onCancel: @escaping () -> Void) -> CardPresentPaymentsModalViewModel {

WooCommerce/Classes/ViewRelated/CardPresentPayments/CardReaderConnectionController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,8 @@ private extension CardReaderConnectionController {
577577
event: WooAnalyticsEvent.InPersonPayments
578578
.cardReaderConnectionSuccess(forGatewayID: self.gatewayID,
579579
batteryLevel: reader.batteryLevel,
580-
countryCode: self.configuration.countryCode)
580+
countryCode: self.configuration.countryCode,
581+
cardReaderModel: reader.readerType.model)
581582
)
582583
// If we were installing a software update, introduce a small delay so the user can
583584
// actually see a success message showing the installation was complete

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsConnectedViewController.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ final class CardReaderSettingsConnectedViewController: UIViewController, CardRea
2323
private lazy var paymentAlerts: OrderDetailsPaymentAlerts = {
2424
OrderDetailsPaymentAlerts(presentingController: self,
2525
paymentGatewayAccountID: viewModel?.dataSource.cardPresentPaymentGatewayID(),
26-
countryCode: CardPresentConfigurationLoader().configuration.countryCode)
26+
countryCode: CardPresentConfigurationLoader().configuration.countryCode,
27+
cardReaderModel: viewModel?.connectedReaderModel ?? "")
2728
}()
2829

2930
private let settingsAlerts = CardReaderSettingsAlerts()

WooCommerce/Classes/ViewRelated/Dashboard/Settings/CardReadersV2/CardReaderSettingsConnectedViewModel.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ final class CardReaderSettingsConnectedViewModel: CardReaderSettingsPresentedVie
2828
var connectedReaderID: String?
2929
var connectedReaderBatteryLevel: String?
3030
var connectedReaderSoftwareVersion: String?
31+
var connectedReaderModel: String? {
32+
connectedReaders.first?.readerType.model
33+
}
3134

3235
/// The connected gateway ID (plugin slug) - useful for the view controller's tracks events
3336
var connectedGatewayID: String?

WooCommerce/Classes/ViewRelated/Orders/Collect Payments/CollectOrderPaymentUseCase.swift

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
5353
///
5454
private var readerSubscription: AnyCancellable?
5555

56+
private var connectedReader: CardReader?
57+
5658
/// Closure to inform when the full flow has been completed, after receipt management.
5759
/// Needed to be saved as an instance variable because it needs to be referenced from the `MailComposer` delegate.
5860
///
@@ -62,7 +64,8 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
6264
///
6365
private lazy var alerts = OrderDetailsPaymentAlerts(presentingController: rootViewController,
6466
paymentGatewayAccountID: paymentGatewayAccount.gatewayID,
65-
countryCode: configurationLoader.configuration.countryCode)
67+
countryCode: configurationLoader.configuration.countryCode,
68+
cardReaderModel: connectedReader?.readerType.model ?? "")
6669

6770
/// IPP payments collector.
6871
///
@@ -110,6 +113,7 @@ final class CollectOrderPaymentUseCase: NSObject, CollectOrderPaymentProtocol {
110113
/// - Parameter onCompleted: Closure Invoked after the flow has been totally completed, Currently after merchant has handled the receipt.
111114
func collectPayment(backButtonTitle: String, onCollect: @escaping (Result<Void, Error>) -> (), onCompleted: @escaping () -> ()) {
112115
configureBackend()
116+
observeConnectedReadersForAnalytics()
113117
connectReader { [weak self] in
114118
self?.attemptPayment(onCompletion: { [weak self] result in
115119
// Inform about the collect payment state
@@ -172,7 +176,8 @@ private extension CollectOrderPaymentUseCase {
172176
func attemptPayment(onCompletion: @escaping (Result<CardPresentCapturedPaymentData, Error>) -> ()) {
173177
// Track tapped event
174178
analytics.track(event: WooAnalyticsEvent.InPersonPayments.collectPaymentTapped(forGatewayID: paymentGatewayAccount.gatewayID,
175-
countryCode: configurationLoader.configuration.countryCode))
179+
countryCode: configurationLoader.configuration.countryCode,
180+
cardReaderModel: connectedReader?.readerType.model ?? ""))
176181

177182
// Show reader ready alert
178183
alerts.readerIsReady(title: Localization.collectPaymentTitle(username: order.billingAddress?.firstName), amount: formattedAmount)
@@ -215,7 +220,8 @@ private extension CollectOrderPaymentUseCase {
215220
analytics.track(event: WooAnalyticsEvent.InPersonPayments
216221
.collectPaymentSuccess(forGatewayID: paymentGatewayAccount.gatewayID,
217222
countryCode: configurationLoader.configuration.countryCode,
218-
paymentMethod: capturedPaymentData.paymentMethod))
223+
paymentMethod: capturedPaymentData.paymentMethod,
224+
cardReaderModel: connectedReader?.readerType.model ?? ""))
219225

220226
// Success Callback
221227
onCompletion(.success(capturedPaymentData))
@@ -227,7 +233,8 @@ private extension CollectOrderPaymentUseCase {
227233
// Record error
228234
analytics.track(event: WooAnalyticsEvent.InPersonPayments.collectPaymentFailed(forGatewayID: paymentGatewayAccount.gatewayID,
229235
error: error,
230-
countryCode: configurationLoader.configuration.countryCode))
236+
countryCode: configurationLoader.configuration.countryCode,
237+
cardReaderModel: connectedReader?.readerType.model))
231238
DDLogError("Failed to collect payment: \(error.localizedDescription)")
232239

233240
// Inform about the error
@@ -257,7 +264,8 @@ private extension CollectOrderPaymentUseCase {
257264
paymentOrchestrator.cancelPayment { [weak self, analytics] _ in
258265
guard let self = self else { return }
259266
analytics.track(event: WooAnalyticsEvent.InPersonPayments.collectPaymentCanceled(forGatewayID: self.paymentGatewayAccount.gatewayID,
260-
countryCode: self.configurationLoader.configuration.countryCode))
267+
countryCode: self.configurationLoader.configuration.countryCode,
268+
cardReaderModel: self.connectedReader?.readerType.model ?? ""))
261269
}
262270
}
263271

@@ -309,6 +317,16 @@ private extension CollectOrderPaymentUseCase {
309317
}
310318
}
311319

320+
// MARK: Connected Card Readers
321+
private extension CollectOrderPaymentUseCase {
322+
func observeConnectedReadersForAnalytics() {
323+
let action = CardPresentPaymentAction.observeConnectedReaders() { [weak self] readers in
324+
self?.connectedReader = readers.first
325+
}
326+
stores.dispatch(action)
327+
}
328+
}
329+
312330
// MARK: MailComposer Delegate
313331
extension CollectOrderPaymentUseCase: MFMailComposeViewControllerDelegate {
314332
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {

WooCommerce/WooCommerceTests/ViewModels/CardPresentPayments/CardPresentModalReaderIsReadyTests.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ final class CardPresentModalReaderIsReadyTests: XCTestCase {
1717
amount: Expectations.amount,
1818
paymentGatewayAccountID: Expectations.paymentGatewayAccountID,
1919
countryCode: Expectations.countryCode,
20+
cardReaderModel: Expectations.cardReaderModel,
2021
analytics: analytics)
2122
}
2223

@@ -92,6 +93,7 @@ final class CardPresentModalReaderIsReadyTests: XCTestCase {
9293
XCTAssertEqual(analyticsProvider.receivedEvents.first, "card_present_collect_payment_canceled")
9394

9495
let firstPropertiesBatch = try XCTUnwrap(analyticsProvider.receivedProperties.first)
96+
XCTAssertEqual(firstPropertiesBatch["card_reader_model"] as? String, Expectations.cardReaderModel)
9597
XCTAssertEqual(firstPropertiesBatch["country"] as? String, Expectations.countryCode)
9698
XCTAssertEqual(firstPropertiesBatch["plugin_slug"] as? String, Expectations.paymentGatewayAccountID)
9799
}
@@ -103,6 +105,7 @@ private extension CardPresentModalReaderIsReadyTests {
103105
static let name = "name"
104106
static let amount = "amount"
105107
static let image = UIImage.cardPresentImage
108+
static let cardReaderModel = "WISEPAD_3"
106109
static let countryCode = "CA"
107110
static let paymentGatewayAccountID = "woocommerce-payments"
108111
}

0 commit comments

Comments
 (0)