Skip to content

Commit 8a454f2

Browse files
authored
Merge pull request #5375 from woocommerce/issue/5342-quick-order-customer-note-on-receipt
Quick order customer note on receipts
2 parents 04a683c + b3280ef commit 8a454f2

File tree

4 files changed

+99
-23
lines changed

4 files changed

+99
-23
lines changed

Hardware/Hardware/Printer/AirPrintReceipt/ReceiptRenderer.swift

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@ import UIKit
33
/// Renders a receipt in an AirPrint enabled printer.
44
///
55
public final class ReceiptRenderer: UIPrintPageRenderer {
6-
private let lines: [ReceiptLineItem]
7-
private let parameters: CardPresentReceiptParameters
8-
private let cartTotals: [ReceiptTotalLine]
6+
private let content: ReceiptContent
97

108
private let dateFormatter: DateFormatter = {
119
let formatter = DateFormatter()
@@ -17,9 +15,7 @@ public final class ReceiptRenderer: UIPrintPageRenderer {
1715
}()
1816

1917
public init(content: ReceiptContent) {
20-
self.lines = content.lineItems
21-
self.parameters = content.parameters
22-
self.cartTotals = content.cartTotals
18+
self.content = content
2319

2420
super.init()
2521

@@ -49,7 +45,7 @@ public extension ReceiptRenderer {
4945
background-color:#F5F5F5;
5046
width:100%;
5147
color: #707070;
52-
margin: \(Constants.margin / 2)pt 0 0 0;
48+
margin: \(Constants.margin / 2)pt 0;
5349
padding: \(Constants.margin / 2)pt;
5450
}
5551
table td:last-child { width: 30%; text-align: right; }
@@ -77,19 +73,20 @@ public extension ReceiptRenderer {
7773
<h1>\(receiptTitle)</h1>
7874
<h3>\(Localization.amountPaidSectionTitle.uppercased())</h3>
7975
<p>
80-
\(parameters.formattedAmount) \(parameters.currency.uppercased())
76+
\(content.parameters.formattedAmount) \(content.parameters.currency.uppercased())
8177
</p>
8278
<h3>\(Localization.datePaidSectionTitle.uppercased())</h3>
8379
<p>
84-
\(dateFormatter.string(from: parameters.date))
80+
\(dateFormatter.string(from: content.parameters.date))
8581
</p>
8682
<h3>\(Localization.paymentMethodSectionTitle.uppercased())</h3>
8783
<p>
88-
<span class="card-icon \(parameters.cardDetails.brand.iconName)-icon"></span> - \(parameters.cardDetails.last4)
84+
<span class="card-icon \(content.parameters.cardDetails.brand.iconName)-icon"></span> - \(content.parameters.cardDetails.last4)
8985
</p>
9086
</header>
9187
<h3>\(summarySectionTitle.uppercased())</h3>
9288
\(summaryTable())
89+
\(orderNoteSection())
9390
<footer>
9491
<p>
9592
\(requiredItems())
@@ -112,8 +109,8 @@ private extension ReceiptRenderer {
112109

113110
private func summaryTable() -> String {
114111
var summaryContent = "<table>"
115-
for line in lines {
116-
summaryContent += "<tr><td>\(line.title) × \(line.quantity)</td><td>\(line.amount) \(parameters.currency.uppercased())</td></tr>"
112+
for line in content.lineItems {
113+
summaryContent += "<tr><td>\(line.title) × \(line.quantity)</td><td>\(line.amount) \(content.parameters.currency.uppercased())</td></tr>"
117114
}
118115
summaryContent += totalRows()
119116
summaryContent += "</table>"
@@ -123,7 +120,7 @@ private extension ReceiptRenderer {
123120

124121
private func totalRows() -> String {
125122
var rows = ""
126-
for total in cartTotals {
123+
for total in content.cartTotals {
127124
rows += summaryRow(title: total.description, amount: total.amount)
128125
}
129126
return rows
@@ -136,14 +133,24 @@ private extension ReceiptRenderer {
136133
\(title)
137134
</td>
138135
<td>
139-
\(amount) \(parameters.currency.uppercased())
136+
\(amount) \(content.parameters.currency.uppercased())
140137
</td>
141138
</tr>
142139
"""
143140
}
144141

142+
private func orderNoteSection() -> String {
143+
guard let orderNote = content.orderNote else {
144+
return ""
145+
}
146+
return """
147+
<h3>\(Localization.orderNoteSectionTitle.uppercased())</h3>
148+
<p>\(orderNote)</p>
149+
"""
150+
}
151+
145152
private func requiredItems() -> String {
146-
guard let emv = parameters.cardDetails.receipt else {
153+
guard let emv = content.parameters.cardDetails.receipt else {
147154
return "<br/>"
148155
}
149156

@@ -163,15 +170,15 @@ private extension ReceiptRenderer {
163170
}
164171

165172
private var receiptTitle: String {
166-
guard let storeName = parameters.storeName else {
173+
guard let storeName = content.parameters.storeName else {
167174
return Localization.receiptTitle
168175
}
169176

170177
return .localizedStringWithFormat(Localization.receiptFromFormat, storeName)
171178
}
172179

173180
private var summarySectionTitle: String {
174-
guard let orderID = parameters.orderID else {
181+
guard let orderID = content.parameters.orderID else {
175182
return Localization.summarySectionTitle
176183
}
177184
return String(format: Localization.summarySectionTitleWithOrderFormat, String(orderID))
@@ -218,6 +225,10 @@ private extension ReceiptRenderer {
218225
comment: "Title of 'Summary' section in the receipt when the order number is unknown"
219226
)
220227

228+
static let orderNoteSectionTitle = NSLocalizedString(
229+
"Notes",
230+
comment: "Title of order note section in the receipt, commonly used for Quick Orders.")
231+
221232
static let summarySectionTitleWithOrderFormat = NSLocalizedString(
222233
"Summary: Order #%1$@",
223234
comment: "Title of 'Summary' section in the receipt. %1$@ is the order number, e.g. 4920"

Hardware/Hardware/Printer/ReceiptContent.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@ public struct ReceiptContent: Codable {
44
public let lineItems: [ReceiptLineItem]
55
public let parameters: CardPresentReceiptParameters
66
public let cartTotals: [ReceiptTotalLine]
7+
public let orderNote: String?
78

8-
public init(parameters: CardPresentReceiptParameters, lineItems: [ReceiptLineItem], cartTotals: [ReceiptTotalLine]) {
9+
public init(parameters: CardPresentReceiptParameters,
10+
lineItems: [ReceiptLineItem],
11+
cartTotals: [ReceiptTotalLine],
12+
orderNote: String?) {
913
self.lineItems = lineItems
1014
self.parameters = parameters
1115
self.cartTotals = cartTotals
16+
self.orderNote = orderNote
1217
}
1318
}
1419

@@ -17,6 +22,7 @@ extension ReceiptContent {
1722
case lineItems = "line_items"
1823
case parameters = "parameters"
1924
case cartTotals = "cart_totals"
25+
case orderNote = "order_note"
2026
}
2127
}
2228

Yosemite/Yosemite/Stores/ReceiptStore.swift

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public class ReceiptStore: Store {
5959

6060
private extension ReceiptStore {
6161
func print(order: Order, parameters: CardPresentReceiptParameters, completion: @escaping (PrintingResult) -> Void) {
62-
let content = generateReceiptContent(order: order, parameters: parameters)
62+
let content = generateReceiptContent(order: order, parameters: parameters, removingHtml: true)
6363
receiptPrinterService.printReceipt(content: content, completion: completion)
6464
}
6565

@@ -69,11 +69,27 @@ private extension ReceiptStore {
6969
onContent(renderer.htmlContent())
7070
}
7171

72-
func generateReceiptContent(order: Order, parameters: CardPresentReceiptParameters) -> ReceiptContent {
72+
func generateReceiptContent(order: Order, parameters: CardPresentReceiptParameters, removingHtml: Bool = false) -> ReceiptContent {
7373
let lineItems = generateLineItems(order: order)
7474
let cartTotals = generateCartTotals(order: order, parameters: parameters)
75+
let note = receiptOrderNote(order: order, removingHtml: removingHtml)
76+
77+
return ReceiptContent(parameters: parameters,
78+
lineItems: lineItems,
79+
cartTotals: cartTotals,
80+
orderNote: note)
81+
}
7582

76-
return ReceiptContent(parameters: parameters, lineItems: lineItems, cartTotals: cartTotals)
83+
private func receiptOrderNote(order: Order, removingHtml: Bool) -> String? {
84+
guard let orderNote = order.customerNote else {
85+
return nil
86+
}
87+
if removingHtml {
88+
// TODO: move this logic to the WooCommerce target, and then use String.removedHTMLTags extension function
89+
return orderNote.replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil)
90+
} else {
91+
return orderNote
92+
}
7793
}
7894

7995
func generateLineItems(order: Order) -> [ReceiptLineItem] {

Yosemite/YosemiteTests/Stores/ReceiptStoreTests.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,11 +352,54 @@ final class ReceiptStoreTests: XCTestCase {
352352
}
353353
XCTAssertEqual("30.00", lineItemsTotalLine?.amount)
354354
}
355+
356+
func test_generateContent_callsGenerate_passingUnmodified_customerNote() throws {
357+
let mockParameters = try XCTUnwrap(MockPaymentIntent.mock().receiptParameters())
358+
let expectedNote = "<a href=\"https://example.com\">This note has a link</a>"
359+
let mockOrder = makeOrder(customerNote: expectedNote)
360+
let expectation = expectation(description: #function)
361+
362+
let receiptStore = ReceiptStore(dispatcher: dispatcher,
363+
storageManager: storageManager,
364+
network: network,
365+
receiptPrinterService: receiptPrinterService,
366+
fileStorage: MockInMemoryStorage())
367+
368+
let action = ReceiptAction.generateContent(
369+
order: mockOrder,
370+
parameters: mockParameters,
371+
onContent: { content in
372+
XCTAssert(content.contains(expectedNote))
373+
expectation.fulfill()
374+
})
375+
376+
receiptStore.onAction(action)
377+
378+
wait(for: [expectation], timeout: Constants.expectationTimeout)
379+
}
380+
381+
func test_print_callsPrint_passingHTMLStripped_customerNote() throws {
382+
let mockParameters = try XCTUnwrap(MockPaymentIntent.mock().receiptParameters())
383+
let mockOrder = makeOrder(customerNote: "<a href=\"https://example.com\">This note has a link</a>")
384+
385+
let receiptStore = ReceiptStore(dispatcher: dispatcher,
386+
storageManager: storageManager,
387+
network: network,
388+
receiptPrinterService: receiptPrinterService,
389+
fileStorage: MockInMemoryStorage())
390+
391+
let action = ReceiptAction.print(order: mockOrder, parameters: mockParameters, completion: { _ in })
392+
393+
receiptStore.onAction(action)
394+
395+
XCTAssertEqual(receiptPrinterService.contentProvided?.orderNote, "This note has a link")
396+
}
355397
}
356398

357399

358400
private extension ReceiptStoreTests {
359-
func makeOrder(discountTotal: String = "",
401+
func makeOrder(customerNote: String? = nil,
402+
discountTotal: String = "",
360403
discountTax: String = "",
361404
shippingTotal: String = "",
362405
shippingTax: String = "",
@@ -371,7 +414,7 @@ private extension ReceiptStoreTests {
371414
number: "",
372415
status: .custom(""),
373416
currency: "usd",
374-
customerNote: nil,
417+
customerNote: customerNote,
375418
dateCreated: Date(),
376419
dateModified: Date(),
377420
datePaid: nil,

0 commit comments

Comments
 (0)