Skip to content

Commit 49b996d

Browse files
authored
Merge pull request #5231 from woocommerce/issue/dry-receipt-printing
Refactor receipt printing
2 parents b0d603e + 29327c7 commit 49b996d

File tree

9 files changed

+188
-30
lines changed

9 files changed

+188
-30
lines changed

Hardware/Hardware/CardReader/CardPresentTransactionDetails.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@ public struct CardPresentTransactionDetails: Codable, Equatable {
2828

2929
/// (Only applicable to EMV payments) The authorization data from the card issuer.
3030
public let emvAuthData: String?
31+
32+
public init(last4: String,
33+
expMonth: Int,
34+
expYear: Int,
35+
cardholderName: String?,
36+
brand: CardBrand,
37+
fingerprint: String,
38+
generatedCard: String?,
39+
receipt: ReceiptDetails?,
40+
emvAuthData: String?) {
41+
self.last4 = last4
42+
self.expMonth = expMonth
43+
self.expYear = expYear
44+
self.cardholderName = cardholderName
45+
self.brand = brand
46+
self.fingerprint = fingerprint
47+
self.generatedCard = generatedCard
48+
self.receipt = receipt
49+
self.emvAuthData = emvAuthData
50+
}
3151
}
3252

3353
extension CardPresentTransactionDetails {

Hardware/Hardware/Printer/CardPresentReceiptParameters.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/// Encapsulates the information necessary to print a receipt for a
22
/// card present payment
3-
public struct CardPresentReceiptParameters: Codable {
3+
public struct CardPresentReceiptParameters: Codable, Equatable {
44
/// The total amount
55
public let amount: UInt
66

WooCommerce/Classes/ViewModels/CardPresentPayments/PaymentCaptureOrchestrator.swift

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,6 @@ final class PaymentCaptureOrchestrator {
9595
ServiceLocator.stores.dispatch(action)
9696
}
9797

98-
func printReceipt(for order: Order, params: CardPresentReceiptParameters) {
99-
let action = ReceiptAction.print(order: order, parameters: params) { (result) in
100-
switch result {
101-
case .success:
102-
ServiceLocator.analytics.track(.receiptPrintSuccess)
103-
case .cancel:
104-
ServiceLocator.analytics.track(.receiptPrintCanceled)
105-
case .failure(let error):
106-
ServiceLocator.analytics.track(.receiptPrintFailed, withError: error)
107-
}
108-
}
109-
110-
ServiceLocator.stores.dispatch(action)
111-
}
112-
11398
func emailReceipt(for order: Order, params: CardPresentReceiptParameters, onContent: @escaping (String) -> Void) {
11499
let action = ReceiptAction.generateContent(order: order, parameters: params) { emailContent in
115100
onContent(emailContent)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import Foundation
2+
import Yosemite
3+
4+
struct ReceiptActionCoordinator {
5+
static func printReceipt(for order: Order, params: CardPresentReceiptParameters) {
6+
ServiceLocator.analytics.track(.receiptPrintTapped)
7+
8+
let action = ReceiptAction.print(order: order, parameters: params) { (result) in
9+
switch result {
10+
case .success:
11+
ServiceLocator.analytics.track(.receiptPrintSuccess)
12+
case .cancel:
13+
ServiceLocator.analytics.track(.receiptPrintCanceled)
14+
case .failure(let error):
15+
ServiceLocator.analytics.track(.receiptPrintFailed, withError: error)
16+
DDLogError("⛔️ Failed to print receipt: \(error.localizedDescription)")
17+
}
18+
}
19+
20+
ServiceLocator.stores.dispatch(action)
21+
}
22+
}

WooCommerce/Classes/ViewModels/CardPresentPayments/ReceiptViewModel.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,6 @@ final class ReceiptViewModel {
2929

3030
/// Prints the receipt
3131
func printReceipt() {
32-
ServiceLocator.analytics.track(.receiptPrintTapped)
33-
let action = ReceiptAction.print(order: order, parameters: receipt) { (result) in
34-
switch result {
35-
case .success:
36-
ServiceLocator.analytics.track(.receiptPrintSuccess)
37-
case .cancel:
38-
ServiceLocator.analytics.track(.receiptPrintCanceled)
39-
case .failure(let error):
40-
ServiceLocator.analytics.track(.receiptPrintFailed, withError: error)
41-
}
42-
}
43-
ServiceLocator.stores.dispatch(action)
32+
ReceiptActionCoordinator.printReceipt(for: order, params: receipt)
4433
}
4534
}

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,8 +565,7 @@ extension OrderDetailsViewModel {
565565
}
566566

567567
func printReceipt(params: CardPresentReceiptParameters) {
568-
ServiceLocator.analytics.track(.receiptPrintTapped)
569-
paymentOrchestrator.printReceipt(for: order, params: params)
568+
ReceiptActionCoordinator.printReceipt(for: order, params: params)
570569
}
571570

572571
func emailReceipt(params: CardPresentReceiptParameters, onContent: @escaping (String) -> Void) {

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,8 @@
372372
02F6800325807C9B00C3BAD2 /* ShippingLabelPaperSizeOptionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F6800225807C9B00C3BAD2 /* ShippingLabelPaperSizeOptionListView.swift */; };
373373
02F6800925807CD300C3BAD2 /* GridStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F6800825807CD300C3BAD2 /* GridStackView.swift */; };
374374
02FE89C7231FAA4100E85EF8 /* MainTabBarControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FE89C6231FAA4100E85EF8 /* MainTabBarControllerTests.swift */; };
375+
03AA165E2719B7EF005CCB7B /* ReceiptActionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AA165D2719B7EF005CCB7B /* ReceiptActionCoordinator.swift */; };
376+
03AA16602719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03AA165F2719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift */; };
375377
247CE89C2583402A00F9D9D1 /* Embassy in Frameworks */ = {isa = PBXBuildFile; productRef = 247CE89B2583402A00F9D9D1 /* Embassy */; };
376378
247CE8A6258340E600F9D9D1 /* ScreenshotImages.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 247CE8A5258340E600F9D9D1 /* ScreenshotImages.xcassets */; };
377379
24C5AC7625A53021008FD769 /* Embassy in Frameworks */ = {isa = PBXBuildFile; productRef = 247CE89B2583402A00F9D9D1 /* Embassy */; };
@@ -1823,6 +1825,8 @@
18231825
02F6800225807C9B00C3BAD2 /* ShippingLabelPaperSizeOptionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelPaperSizeOptionListView.swift; sourceTree = "<group>"; };
18241826
02F6800825807CD300C3BAD2 /* GridStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridStackView.swift; sourceTree = "<group>"; };
18251827
02FE89C6231FAA4100E85EF8 /* MainTabBarControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarControllerTests.swift; sourceTree = "<group>"; };
1828+
03AA165D2719B7EF005CCB7B /* ReceiptActionCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptActionCoordinator.swift; sourceTree = "<group>"; };
1829+
03AA165F2719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptActionCoordinatorTests.swift; sourceTree = "<group>"; };
18261830
247CE8A5258340E600F9D9D1 /* ScreenshotImages.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ScreenshotImages.xcassets; sourceTree = "<group>"; };
18271831
24C579D124F476300076E1B4 /* Woo-Alpha.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Woo-Alpha.entitlements"; sourceTree = "<group>"; };
18281832
24F98C4F2502AEE200F49B68 /* EventLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventLogging.swift; sourceTree = "<group>"; };
@@ -6339,6 +6343,7 @@
63396343
D810F8F72639EDE900437C67 /* CardPresentPaymentsModalViewControllerTests.swift */,
63406344
31E906A226CC91A70099A985 /* CardReaderConnectionControllerTests.swift */,
63416345
26100B1F2722FCAD00473045 /* MockCardPresentPaymentsOnboardingUseCase.swift */,
6346+
03AA165F2719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift */,
63426347
);
63436348
path = CardPresentPayments;
63446349
sourceTree = "<group>";
@@ -6436,6 +6441,7 @@
64366441
31AD0B1026E9575F000B6391 /* CardPresentModalConnectingFailed.swift */,
64376442
D8EE9697264D3CCB0033B2F9 /* ReceiptViewModel.swift */,
64386443
D8752EF6265E60F4008ACC80 /* PaymentCaptureCelebration.swift */,
6444+
03AA165D2719B7EF005CCB7B /* ReceiptActionCoordinator.swift */,
64396445
);
64406446
path = CardPresentPayments;
64416447
sourceTree = "<group>";
@@ -7802,6 +7808,7 @@
78027808
E12AF69926BA8ADC00C371C1 /* CardPresentPaymentsOnboardingUseCase.swift in Sources */,
78037809
45C11B7C2508E156006C2089 /* SelectedSiteSettings.swift in Sources */,
78047810
B5A56BF3219F46470065A902 /* UIButton+Animations.swift in Sources */,
7811+
03AA165E2719B7EF005CCB7B /* ReceiptActionCoordinator.swift in Sources */,
78057812
B54FBE552111F70700390F57 /* ResultsController+UIKit.swift in Sources */,
78067813
CE2409F1215D12D30091F887 /* WooNavigationController.swift in Sources */,
78077814
0294F8AB25E8A12C005B537A /* WooTabNavigationController.swift in Sources */,
@@ -8192,6 +8199,7 @@
81928199
26FE09E124DB8FA000B9BDF5 /* SurveyCoordinatorControllerTests.swift in Sources */,
81938200
0277AEAB256CAA5300F45C4A /* MockShippingLabelAddress.swift in Sources */,
81948201
D83F593D225B4B5000626E75 /* ManualTrackingViewControllerTests.swift in Sources */,
8202+
03AA16602719B83D005CCB7B /* ReceiptActionCoordinatorTests.swift in Sources */,
81958203
CEEC9B6621E7C5200055EEF0 /* AppRatingManagerTests.swift in Sources */,
81968204
263EB409242C58EA00F3A15F /* ProductFormActionsFactoryTests.swift in Sources */,
81978205
02BA23C022EE9DAF009539E7 /* AsyncDictionaryTests.swift in Sources */,
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import XCTest
2+
import TestKit
3+
import Yosemite
4+
import Hardware
5+
6+
@testable import WooCommerce
7+
8+
class ReceiptActionCoordinatorTests: XCTestCase {
9+
func test_printReceipt_logs_receiptPrintTapped_analyticEvent() {
10+
// Given
11+
ServiceLocator.setAnalytics(WooAnalytics(analyticsProvider: MockAnalyticsProvider()))
12+
let order = MockOrders().makeOrder()
13+
let params = CardPresentReceiptParameters.makeParams()
14+
15+
// When
16+
ReceiptActionCoordinator.printReceipt(for: order, params: params)
17+
18+
// Then
19+
let analytics = ServiceLocator.analytics.analyticsProvider as! MockAnalyticsProvider
20+
let receivedEvents = analytics.receivedEvents
21+
22+
XCTAssert(receivedEvents.contains(WooAnalyticsStat.receiptPrintTapped.rawValue))
23+
}
24+
25+
func test_printReceipt_sends_print_receiptAction() throws {
26+
// Given
27+
let order = MockOrders().makeOrder()
28+
let params = CardPresentReceiptParameters.makeParams()
29+
30+
let storesManager = MockStoresManager(sessionManager: .makeForTesting(authenticated: true))
31+
storesManager.reset()
32+
33+
ServiceLocator.setStores(storesManager)
34+
35+
assertEmpty(storesManager.receivedActions)
36+
37+
// When
38+
ReceiptActionCoordinator.printReceipt(for: order, params: params)
39+
40+
//Then
41+
XCTAssertEqual(storesManager.receivedActions.count, 1)
42+
43+
let action = try XCTUnwrap(storesManager.receivedActions.first as? ReceiptAction)
44+
switch action {
45+
case .print(let actionOrder, let actionParams, completion: _):
46+
XCTAssertEqual(actionOrder, order)
47+
XCTAssertEqual(actionParams, params)
48+
default:
49+
XCTFail("Print Receipt failed to dispatch .print action")
50+
}
51+
}
52+
53+
func test_printReceipt_success_logs_receiptPrintSuccess_analyticEvent() throws {
54+
try assertAnalyticLogged(.receiptPrintSuccess, for: .success)
55+
}
56+
57+
func test_printReceipt_cancel_logs_receiptPrintCanceled_analyticEvent() throws {
58+
try assertAnalyticLogged(.receiptPrintCanceled, for: .cancel)
59+
}
60+
61+
func test_printReceipt_fail_logs_receiptPrintFailed_analyticEvent() throws {
62+
let error = NSError(domain: "errordomain", code: 123, userInfo: nil)
63+
try assertAnalyticLogged(.receiptPrintFailed, for: .failure(error))
64+
}
65+
}
66+
67+
extension ReceiptActionCoordinatorTests {
68+
func assertAnalyticLogged(_ analytic: WooAnalyticsStat, for printingResult: PrintingResult) throws {
69+
// Given
70+
let order = MockOrders().makeOrder()
71+
let params = CardPresentReceiptParameters.makeParams()
72+
73+
let storesManager = MockStoresManager(sessionManager: .makeForTesting(authenticated: true))
74+
ServiceLocator.setStores(storesManager)
75+
ServiceLocator.setAnalytics(WooAnalytics(analyticsProvider: MockAnalyticsProvider()))
76+
77+
// When
78+
ReceiptActionCoordinator.printReceipt(for: order, params: params)
79+
80+
//Then
81+
let action = try XCTUnwrap(storesManager.receivedActions.first as? ReceiptAction)
82+
switch action {
83+
case .print(order: _, parameters: _, let completion):
84+
completion(printingResult)
85+
86+
let analytics = ServiceLocator.analytics.analyticsProvider as! MockAnalyticsProvider
87+
let receivedEvents = analytics.receivedEvents
88+
89+
XCTAssert(receivedEvents.contains(analytic.rawValue))
90+
default:
91+
XCTFail("Print Receipt failed to dispatch .print action")
92+
}
93+
}
94+
}
95+
96+
extension CardPresentReceiptParameters {
97+
static func makeParams(amount: UInt = 123,
98+
formattedAmount: String = "0.00",
99+
currency: String = "USD",
100+
date: Date = Date(timeIntervalSince1970: TimeInterval(1630000000)), //Thu Aug 26 2021 17:46:40 GMT+0000
101+
storeName: String? = "My store",
102+
cardDetails: CardPresentTransactionDetails = CardPresentTransactionDetails.makeDetails(),
103+
orderID: Int64? = 12345) -> CardPresentReceiptParameters {
104+
CardPresentReceiptParameters(amount: amount,
105+
formattedAmount: formattedAmount,
106+
currency: currency,
107+
date: date,
108+
storeName: storeName,
109+
cardDetails: cardDetails,
110+
orderID: orderID)
111+
}
112+
}
113+
114+
extension CardPresentTransactionDetails {
115+
static func makeDetails(last4: String = "0000",
116+
expMonth: Int = 1,
117+
expYear: Int = 31,
118+
cardholderName: String? = nil,
119+
brand: CardBrand = .unknown,
120+
fingerprint: String = "y29834",
121+
generatedCard: String? = "1230",
122+
receipt: ReceiptDetails? = nil,
123+
emvAuthData: String? = nil) -> CardPresentTransactionDetails {
124+
CardPresentTransactionDetails(last4: last4,
125+
expMonth: expMonth,
126+
expYear: expYear,
127+
cardholderName: cardholderName,
128+
brand: brand,
129+
fingerprint: fingerprint,
130+
generatedCard: generatedCard,
131+
receipt: receipt,
132+
emvAuthData: emvAuthData)
133+
}
134+
}

Yosemite/Yosemite/Model/Model.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ public typealias PaymentParameters = Hardware.PaymentIntentParameters
130130
public typealias PaymentIntent = Hardware.PaymentIntent
131131
public typealias PrintingResult = Hardware.PrintingResult
132132
public typealias CardPresentReceiptParameters = Hardware.CardPresentReceiptParameters
133+
public typealias CardPresentTransactionDetails = Hardware.CardPresentTransactionDetails
133134
public typealias WCPayAccount = Networking.WCPayAccount
134135
public typealias WCPayAccountStatusEnum = Networking.WCPayAccountStatusEnum
135136
public typealias WCPayCustomer = Networking.WCPayCustomer

0 commit comments

Comments
 (0)