Skip to content

Commit 7b5f2dc

Browse files
authored
[Woo POS][Historical Orders] Order Details: Email Receipt action (Wireframe UI) (1/1) (#16110)
2 parents 1365091 + 4ae1b3c commit 7b5f2dc

30 files changed

+715
-418
lines changed

Modules/Sources/Networking/Remote/ReceiptRemote.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,31 @@ public final class ReceiptRemote: Remote {
5353
/// - Parameters:
5454
/// - siteID: Site which hosts the Order.
5555
/// - orderID: ID of the order that the receipt is associated to.
56-
public func sendPOSReceipt(siteID: Int64, orderID: Int64) async throws {
56+
public func sendPOSReceipt(siteID: Int64, orderID: Int64, emailAddress: String) async throws {
5757
let sendEmailPath = "\(Constants.ordersPath)/\(orderID)/\(Constants.actionsPath)/send_email"
5858
let sendEmailRequest = JetpackRequest(wooApiVersion: .mark3,
5959
method: .post,
6060
siteID: siteID,
6161
path: sendEmailPath,
6262
parameters: [
63-
ParameterKeys.templateID: POSConstants.receiptTemplateID
63+
ParameterKeys.templateID: POSConstants.receiptTemplateID,
64+
ParameterKeys.email: emailAddress,
65+
ParameterKeys.forceEmailUpdate: true
6466
],
6567
availableAsRESTRequest: true)
6668
try await enqueue(sendEmailRequest)
6769
}
6870
}
6971

70-
extension ReceiptRemote: POSReceiptsRemoteProtocol { }
72+
extension ReceiptRemote: POSReceiptsRemoteProtocol {}
7173

7274
private extension ReceiptRemote {
7375
enum ParameterKeys {
7476
static let expirationDays: String = "expiration_days"
7577
static let forceRegenerate: String = "force_new"
7678
static let templateID: String = "template_id"
79+
static let forceEmailUpdate: String = "force_email_update"
80+
static let email: String = "email"
7781
}
7882

7983
enum Constants {

Modules/Sources/NetworkingCore/Remote/OrdersRemote.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,24 @@ extension OrdersRemote: POSOrdersRemoteProtocol {
451451
}
452452
}
453453

454+
public func updatePOSOrderEmail(siteID: Int64, orderID: Int64, emailAddress: String) async throws {
455+
let parameters: [String: Any] = [
456+
"billing": [
457+
"email": emailAddress
458+
]
459+
]
460+
461+
let path = "\(Constants.ordersPath)/\(orderID)"
462+
let request = JetpackRequest(wooApiVersion: .mark3,
463+
method: .post,
464+
siteID: siteID,
465+
path: path,
466+
parameters: parameters,
467+
availableAsRESTRequest: true)
468+
469+
try await enqueue(request)
470+
}
471+
454472
public func loadPOSOrders(siteID: Int64, pageNumber: Int, pageSize: Int) async throws -> PagedItems<Order> {
455473
let parameters: [String: Any] = [
456474
ParameterKeys.page: String(pageNumber),

Modules/Sources/NetworkingCore/Remote/POSOrdersRemoteProtocol.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33
public protocol POSReceiptsRemoteProtocol {
44
func sendReceipt(siteID: Int64, orderID: Int64) async throws
5-
func sendPOSReceipt(siteID: Int64, orderID: Int64) async throws
5+
func sendPOSReceipt(siteID: Int64, orderID: Int64, emailAddress: String) async throws
66
}
77

88
public protocol POSOrdersRemoteProtocol {
@@ -11,6 +11,10 @@ public protocol POSOrdersRemoteProtocol {
1111
cashPaymentChangeDueAmount: String?,
1212
fields: [OrdersRemote.UpdateOrderField]) async throws -> Order
1313

14+
func updatePOSOrderEmail(siteID: Int64,
15+
orderID: Int64,
16+
emailAddress: String) async throws
17+
1418
func createPOSOrder(siteID: Int64,
1519
order: Order,
1620
fields: [OrdersRemote.CreateOrderField]) async throws -> Order

Modules/Sources/Yosemite/Tools/POS/POSOrderService.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public protocol POSOrderServiceProtocol {
99
/// - cart: Cart with different types of items and quantities.
1010
/// - Returns: Order from the remote sync.
1111
func syncOrder(cart: POSCart, currency: CurrencyCode) async throws -> Order
12-
func updatePOSOrder(order: Order, recipientEmail: String) async throws
12+
func updatePOSOrder(orderID: Int64, recipientEmail: String) async throws
1313
func markOrderAsCompletedWithCashPayment(order: Order, changeDueAmount: String?) async throws
1414
}
1515

@@ -46,20 +46,9 @@ public final class POSOrderService: POSOrderServiceProtocol {
4646
return try await ordersRemote.createPOSOrder(siteID: siteID, order: order, fields: [.items, .status, .currency, .couponLines])
4747
}
4848

49-
public func updatePOSOrder(order: Order, recipientEmail: String) async throws {
50-
guard order.billingAddress?.email == nil || order.billingAddress?.email == "" else {
51-
throw POSOrderServiceError.emailAlreadySet
52-
}
53-
let updatedBillingAddress = order.billingAddress?.copy(email: recipientEmail)
54-
let updatedOrder = order.copy(billingAddress: updatedBillingAddress)
55-
49+
public func updatePOSOrder(orderID: Int64, recipientEmail: String) async throws {
5650
do {
57-
let _ = try await ordersRemote.updatePOSOrder(
58-
siteID: siteID,
59-
order: updatedOrder,
60-
cashPaymentChangeDueAmount: nil,
61-
fields: [.billingAddress]
62-
)
51+
try await ordersRemote.updatePOSOrderEmail(siteID: siteID, orderID: orderID, emailAddress: recipientEmail)
6352
} catch {
6453
throw POSOrderServiceError.updateOrderFailed
6554
}
@@ -105,7 +94,6 @@ private extension Order {
10594

10695
private extension POSOrderService {
10796
enum POSOrderServiceError: Error {
108-
case emailAlreadySet
10997
case updateOrderFailed
11098
}
11199
}

Modules/Sources/Yosemite/Tools/POS/POSReceiptService.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22
import Networking
33

44
public protocol POSReceiptServiceProtocol {
5-
func sendReceipt(order: Order, recipientEmail: String, isEligibleForPOSReceipt: Bool) async throws
5+
func sendReceipt(orderID: Int64, recipientEmail: String, isEligibleForPOSReceipt: Bool) async throws
66
}
77

88
public final class POSReceiptService: POSReceiptServiceProtocol {
@@ -25,12 +25,12 @@ public final class POSReceiptService: POSReceiptServiceProtocol {
2525
self.receiptsRemote = receiptsRemote
2626
}
2727

28-
public func sendReceipt(order: Yosemite.Order, recipientEmail: String, isEligibleForPOSReceipt: Bool) async throws {
28+
public func sendReceipt(orderID: Int64, recipientEmail: String, isEligibleForPOSReceipt: Bool) async throws {
2929
do {
3030
if isEligibleForPOSReceipt {
31-
try await receiptsRemote.sendPOSReceipt(siteID: siteID, orderID: order.orderID)
31+
try await receiptsRemote.sendPOSReceipt(siteID: siteID, orderID: orderID, emailAddress: recipientEmail)
3232
} else {
33-
try await receiptsRemote.sendReceipt(siteID: siteID, orderID: order.orderID)
33+
try await receiptsRemote.sendReceipt(siteID: siteID, orderID: orderID)
3434
}
3535
} catch {
3636
throw POSReceiptServiceError.sendReceiptFailed(underlyingError: error as NSError)

Modules/Tests/NetworkingTests/Remote/ReceiptRemoteTests.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,29 +129,33 @@ final class ReceiptRemoteTests: XCTestCase {
129129
// Given
130130
let remote = ReceiptRemote(network: network)
131131
let posReceiptTemplateID = "customer_pos_completed_order"
132+
let testEmail = "[email protected]"
132133

133134
network.simulateResponse(
134135
requestUrlSuffix: "orders/\(sampleOrderID)/actions/send_email",
135136
filename: "orders-actions-send-email-success"
136137
)
137138

138139
// When
139-
try await remote.sendPOSReceipt(siteID: sampleSiteID, orderID: sampleOrderID)
140+
try await remote.sendPOSReceipt(siteID: sampleSiteID, orderID: sampleOrderID, emailAddress: testEmail)
140141

141142
// Then the send email request was made with correct parameters.
142143
let sendEmailRequest = try XCTUnwrap(network.requestsForResponseData.last as? JetpackRequest)
143144
XCTAssertEqual(sendEmailRequest.method, .post)
144145
XCTAssertEqual(sendEmailRequest.path, "orders/\(sampleOrderID)/actions/send_email")
145146
XCTAssertEqual(sendEmailRequest.parameters["template_id"] as? String, posReceiptTemplateID)
147+
XCTAssertEqual(sendEmailRequest.parameters["email"] as? String, testEmail)
148+
XCTAssertEqual(sendEmailRequest.parameters["force_email_update"] as? Bool, true)
146149
}
147150

148151
func test_sendPOSReceipt_when_no_reponse_exist_throws_error() async {
149152
// Given
150153
let remote = ReceiptRemote(network: network)
154+
let testEmail = "[email protected]"
151155

152156
await assertThrowsError({
153157
// When
154-
try await remote.sendPOSReceipt(siteID: sampleSiteID, orderID: sampleOrderID)
158+
try await remote.sendPOSReceipt(siteID: sampleSiteID, orderID: sampleOrderID, emailAddress: testEmail)
155159
}, errorAssert: { error in
156160
// Then
157161
return error is NetworkError

Modules/Tests/YosemiteTests/Mocks/MockPOSOrdersRemote.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ final class MockPOSOrdersRemote: POSOrdersRemoteProtocol {
2222
}
2323
}
2424

25+
var updatePOSOrderEmailCalled: Bool = false
26+
var spyUpdatePOSOrderEmailSiteID: Int64?
27+
var spyUpdatePOSOrderEmailOrderID: Int64?
28+
var spyUpdatePOSOrderEmailAddress: String?
29+
var updatePOSOrderEmailResult: Result<Void, Error> = .success(())
30+
31+
func updatePOSOrderEmail(siteID: Int64, orderID: Int64, emailAddress: String) async throws {
32+
updatePOSOrderEmailCalled = true
33+
spyUpdatePOSOrderEmailSiteID = siteID
34+
spyUpdatePOSOrderEmailOrderID = orderID
35+
spyUpdatePOSOrderEmailAddress = emailAddress
36+
switch updatePOSOrderEmailResult {
37+
case .success:
38+
return
39+
case .failure(let error):
40+
throw error
41+
}
42+
}
43+
2544
var createPOSOrderCalled: Bool = false
2645
var spyCreatePOSOrder: Order?
2746
var spyCreatePOSOrderFields: [OrdersRemote.CreateOrderField]?

Modules/Tests/YosemiteTests/Mocks/MockPOSReceiptsRemote.swift

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ final class MockPOSReceiptsRemote: POSReceiptsRemoteProtocol {
55
var sendPOSReceiptCalled = false
66
var spySiteID: Int64?
77
var spyOrderID: Int64?
8+
var spyEmail: String?
89
var shouldThrowError: Error?
910

1011
func sendReceipt(siteID: Int64, orderID: Int64) async throws {
@@ -17,7 +18,14 @@ final class MockPOSReceiptsRemote: POSReceiptsRemoteProtocol {
1718
}
1819
}
1920

20-
func sendPOSReceipt(siteID: Int64, orderID: Int64) async throws {
21+
func sendPOSReceipt(siteID: Int64, orderID: Int64, emailAddress: String) async throws {
2122
sendPOSReceiptCalled = true
23+
spySiteID = siteID
24+
spyOrderID = orderID
25+
spyEmail = emailAddress
26+
27+
if let shouldThrowError {
28+
throw shouldThrowError
29+
}
2230
}
2331
}

Modules/Tests/YosemiteTests/Tools/POS/POSOrderServiceTests.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,35 @@ struct POSOrderServiceTests {
166166
return true
167167
})
168168
}
169+
170+
@Test func updatePOSOrder_calls_remote_updatePOSOrderEmail_with_correct_parameters() async throws {
171+
// Given
172+
let siteID: Int64 = 123
173+
let orderID: Int64 = 456
174+
let recipientEmail = "[email protected]"
175+
176+
// When
177+
try await sut.updatePOSOrder(orderID: orderID, recipientEmail: recipientEmail)
178+
179+
// Then
180+
#expect(mockOrdersRemote.updatePOSOrderEmailCalled == true)
181+
#expect(mockOrdersRemote.spyUpdatePOSOrderEmailSiteID == siteID)
182+
#expect(mockOrdersRemote.spyUpdatePOSOrderEmailOrderID == orderID)
183+
#expect(mockOrdersRemote.spyUpdatePOSOrderEmailAddress == recipientEmail)
184+
}
185+
186+
@Test func updatePOSOrder_throws_error_when_remote_call_fails() async throws {
187+
// Given
188+
mockOrdersRemote.updatePOSOrderEmailResult = .failure(NSError(domain: "", code: 0))
189+
190+
// When/Then
191+
await #expect(performing: {
192+
try await sut.updatePOSOrder(orderID: 456, recipientEmail: "[email protected]")
193+
}, throws: { _ in
194+
// The actual error `POSOrderServiceError.updateOrderFailed` is private, thus we cannot check against the exact error.
195+
return true
196+
})
197+
}
169198
}
170199

171200
private func makePOSCartItem(

Modules/Tests/YosemiteTests/Tools/POS/POSReceiptServiceTests.swift

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct POSReceiptServiceTests {
2020
let email = "[email protected]"
2121

2222
// When
23-
try await sut.sendReceipt(order: order, recipientEmail: email, isEligibleForPOSReceipt: false)
23+
try await sut.sendReceipt(orderID: order.orderID, recipientEmail: email, isEligibleForPOSReceipt: false)
2424

2525
// Then
2626
#expect(receiptsRemote.sendReceiptCalled)
@@ -36,7 +36,7 @@ struct POSReceiptServiceTests {
3636

3737
// When/Then
3838
do {
39-
try await sut.sendReceipt(order: order, recipientEmail: "[email protected]", isEligibleForPOSReceipt: false)
39+
try await sut.sendReceipt(orderID: order.orderID, recipientEmail: "[email protected]", isEligibleForPOSReceipt: false)
4040
XCTFail("Expected error to be thrown")
4141
} catch {
4242
guard case POSReceiptService.POSReceiptServiceError.sendReceiptFailed = error else {
@@ -48,10 +48,17 @@ struct POSReceiptServiceTests {
4848

4949
@Test
5050
func sendReceipt_calls_remote_when_isEligibleForPOSReceipt_is_true() async throws {
51+
// Given
52+
let email = "[email protected]"
53+
let orderID: Int64 = 789
54+
5155
// When
52-
try await sut.sendReceipt(order: Order.fake(), recipientEmail: "[email protected]", isEligibleForPOSReceipt: true)
56+
try await sut.sendReceipt(orderID: orderID, recipientEmail: email, isEligibleForPOSReceipt: true)
5357

5458
// Then
5559
#expect(receiptsRemote.sendPOSReceiptCalled)
60+
#expect(receiptsRemote.spySiteID == 123)
61+
#expect(receiptsRemote.spyOrderID == orderID)
62+
#expect(receiptsRemote.spyEmail == email)
5663
}
5764
}

0 commit comments

Comments
 (0)