Skip to content

Commit a1de8f9

Browse files
authored
Shipping Label: Integrate refund endpoint (#15618)
2 parents 9c9599c + 6cf213b commit a1de8f9

File tree

9 files changed

+293
-0
lines changed

9 files changed

+293
-0
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,8 @@
10061006
DE02ABB32B563E61008E0AC4 /* blaze-payment-info.json in Resources */ = {isa = PBXBuildFile; fileRef = DE02ABB22B563E61008E0AC4 /* blaze-payment-info.json */; };
10071007
DE02ABB52B563E96008E0AC4 /* BlazePaymentInfoMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE02ABB42B563E96008E0AC4 /* BlazePaymentInfoMapper.swift */; };
10081008
DE02ABB72B563F3A008E0AC4 /* BlazePaymentInfoMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE02ABB62B563F3A008E0AC4 /* BlazePaymentInfoMapperTests.swift */; };
1009+
DE1D3E7D2DD1FDF100565CF9 /* wooshipping-address-validation-error.json in Resources */ = {isa = PBXBuildFile; fileRef = DE1D3E7C2DD1FDF100565CF9 /* wooshipping-address-validation-error.json */; };
1010+
DE1D3E7F2DD200E900565CF9 /* wooshipping-label-refund-error.json in Resources */ = {isa = PBXBuildFile; fileRef = DE1D3E7E2DD200E900565CF9 /* wooshipping-label-refund-error.json */; };
10091011
DE2004552BF5BD1A00660A72 /* ProductStock.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2004542BF5BD1A00660A72 /* ProductStock.swift */; };
10101012
DE2004572BF5C22600660A72 /* ProductStockListMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE2004562BF5C22600660A72 /* ProductStockListMapper.swift */; };
10111013
DE2004592BF6142200660A72 /* product-stock.json in Resources */ = {isa = PBXBuildFile; fileRef = DE2004582BF6142200660A72 /* product-stock.json */; };
@@ -2251,6 +2253,9 @@
22512253
DE02ABB22B563E61008E0AC4 /* blaze-payment-info.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "blaze-payment-info.json"; sourceTree = "<group>"; };
22522254
DE02ABB42B563E96008E0AC4 /* BlazePaymentInfoMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazePaymentInfoMapper.swift; sourceTree = "<group>"; };
22532255
DE02ABB62B563F3A008E0AC4 /* BlazePaymentInfoMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlazePaymentInfoMapperTests.swift; sourceTree = "<group>"; };
2256+
DE1D3E782DD1ED3100565CF9 /* shipping-label-refund-status-error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "shipping-label-refund-status-error.json"; sourceTree = "<group>"; };
2257+
DE1D3E7C2DD1FDF100565CF9 /* wooshipping-address-validation-error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wooshipping-address-validation-error.json"; sourceTree = "<group>"; };
2258+
DE1D3E7E2DD200E900565CF9 /* wooshipping-label-refund-error.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wooshipping-label-refund-error.json"; sourceTree = "<group>"; };
22542259
DE2004542BF5BD1A00660A72 /* ProductStock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStock.swift; sourceTree = "<group>"; };
22552260
DE2004562BF5C22600660A72 /* ProductStockListMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProductStockListMapper.swift; sourceTree = "<group>"; };
22562261
DE2004582BF6142200660A72 /* product-stock.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "product-stock.json"; sourceTree = "<group>"; };
@@ -3309,6 +3314,7 @@
33093314
DE66C5682977D62700DAA978 /* plugins-without-data.json */,
33103315
DE66C5662977CEB800DAA978 /* shipping-label-status-success-without-data.json */,
33113316
DE66C5642977CC4300DAA978 /* shipping-label-purchase-success-without-data.json */,
3317+
DE1D3E7E2DD200E900565CF9 /* wooshipping-label-refund-error.json */,
33123318
DE66C5622977CBC700DAA978 /* shipping-label-eligibility-failure-without-data.json */,
33133319
DE66C5602977CB5F00DAA978 /* shipping-label-eligibility-success-without-data.json */,
33143320
DE66C55E2977C67D00DAA978 /* shipping-label-account-settings-without-data.json */,
@@ -3580,6 +3586,7 @@
35803586
451A97DD260B59870059D135 /* shipping-label-packages-success.json */,
35813587
02E7FFCE25621C7900C53030 /* shipping-label-print.json */,
35823588
028FA471257E110700F88A48 /* shipping-label-refund-error.json */,
3589+
DE1D3E782DD1ED3100565CF9 /* shipping-label-refund-status-error.json */,
35833590
028FA472257E110700F88A48 /* shipping-label-refund-success.json */,
35843591
CC9A253B26442C71005DE56E /* shipping-label-eligibility-success.json */,
35853592
CC9A254526442CA7005DE56E /* shipping-label-eligibility-failure.json */,
@@ -3718,6 +3725,7 @@
37183725
CED9BCC82D3FDFCC00C063B8 /* wooshipping-update-origin-success.json */,
37193726
EE1042B22D65DF9B005AB07F /* wooshipping-verify-destination-error.json */,
37203727
EE1042B32D65DF9B005AB07F /* wooshipping-verify-destination-success.json */,
3728+
DE1D3E7C2DD1FDF100565CF9 /* wooshipping-address-validation-error.json */,
37213729
);
37223730
path = Responses;
37233731
sourceTree = "<group>";
@@ -4614,6 +4622,7 @@
46144622
0205021C27C86B9700FB1C6B /* inbox-note-without-isRead.json in Resources */,
46154623
24F98C622502EFF600F49B68 /* feature-flags-load-all.json in Resources */,
46164624
DE50296128C609A300551736 /* jetpack-connected-user.json in Resources */,
4625+
DE1D3E7F2DD200E900565CF9 /* wooshipping-label-refund-error.json in Resources */,
46174626
B58D10C82114D21D00107ED4 /* generic_error.json in Resources */,
46184627
077F39D826A58EB600ABEADC /* systemStatus.json in Resources */,
46194628
31A451CD27863A2E00FE81AA /* stripe-account-restricted.json in Resources */,
@@ -4727,6 +4736,7 @@
47274736
DEF13C6029668C420024A02B /* order-without-data.json in Resources */,
47284737
022902D422E2436400059692 /* stats_module_disabled_error.json in Resources */,
47294738
4513382827A96DE700AE5E78 /* inbox-note.json in Resources */,
4739+
DE1D3E7D2DD1FDF100565CF9 /* wooshipping-address-validation-error.json in Resources */,
47304740
EEA1E2092AC3E08600A37ADD /* generate-product-success-no-dimensions-info.json in Resources */,
47314741
CE606D8B2BE38B04001CB424 /* shipping-methods.json in Resources */,
47324742
31A451D227863A2E00FE81AA /* stripe-account-restricted-pending.json in Resources */,

Networking/Networking/Remote/WooShippingRemote.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ public protocol WooShippingRemoteProtocol {
6464
orderID: Int64,
6565
shipmentToUpdate: WooShippingUpdateShipment,
6666
completion: @escaping (Result<WooShippingShipments, Error>) -> Void)
67+
68+
func refundShippingLabel(siteID: Int64,
69+
orderID: Int64,
70+
shippingLabelID: Int64,
71+
completion: @escaping (Result<ShippingLabelRefund, Error>) -> Void)
6772
}
6873

6974
/// Shipping Labels Remote Endpoints for the WooShipping Plugin.
@@ -469,6 +474,26 @@ public final class WooShippingRemote: Remote, WooShippingRemoteProtocol {
469474
completion(.failure(error))
470475
}
471476
}
477+
478+
/// Requests a refund for a shipping label.
479+
/// - Parameters:
480+
/// - siteID: Remote ID of the site that owns the shipping label.
481+
/// - orderID: Remote ID of the order that owns the shipping labels.
482+
/// - shippingLabelID: Remote ID of the shipping label.
483+
/// - completion: Closure to be executed upon completion.
484+
public func refundShippingLabel(siteID: Int64,
485+
orderID: Int64,
486+
shippingLabelID: Int64,
487+
completion: @escaping (Result<ShippingLabelRefund, Error>) -> Void) {
488+
let path = Path.refundLabel(orderID: orderID, labelID: shippingLabelID)
489+
let request = JetpackRequest(wooApiVersion: .wooShipping,
490+
method: .post,
491+
siteID: siteID,
492+
path: path,
493+
availableAsRESTRequest: true)
494+
let mapper = ShippingLabelRefundMapper()
495+
enqueue(request, mapper: mapper, completion: completion)
496+
}
472497
}
473498

474499
// MARK: Constants
@@ -495,6 +520,9 @@ private extension WooShippingRemote {
495520
static func updateShipment(orderID: Int64) -> String {
496521
"shipments/\(orderID)"
497522
}
523+
static func refundLabel(orderID: Int64, labelID: Int64) -> String {
524+
"label/refund/\(orderID)/\(labelID)"
525+
}
498526
}
499527

500528
enum ParameterKey {

Networking/NetworkingTests/Remote/WooShippingRemoteTests.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,47 @@ final class WooShippingRemoteTests: XCTestCase {
714714
// Then
715715
XCTAssertNotNil(result.failure)
716716
}
717+
718+
// MARK: Refund shipping label
719+
720+
func test_refundShippingLabel_parses_success_response() throws {
721+
// Given
722+
let labelID: Int64 = 332
723+
let remote = WooShippingRemote(network: network)
724+
network.simulateResponse(requestUrlSuffix: "label/refund/\(sampleOrderID)/\(labelID)", filename: "wooshipping-label-refund-success")
725+
726+
// When
727+
let result: Result<ShippingLabelRefund, Error> = waitFor { promise in
728+
remote.refundShippingLabel(siteID: self.sampleSiteID,
729+
orderID: self.sampleOrderID,
730+
shippingLabelID: labelID) { result in
731+
promise(result)
732+
}
733+
}
734+
735+
// Then
736+
let refund = try XCTUnwrap(result.get())
737+
XCTAssertEqual(refund, ShippingLabelRefund(dateRequested: Date(timeIntervalSince1970: 1723147248.000), status: .pending))
738+
}
739+
740+
func test_refundShippingLabel_returns_error_on_failure() throws {
741+
// Given
742+
let labelID: Int64 = 332
743+
let remote = WooShippingRemote(network: network)
744+
network.simulateResponse(requestUrlSuffix: "label/refund/\(sampleOrderID)/\(labelID)", filename: "wooshipping-label-refund-error")
745+
746+
// When
747+
let result: Result<ShippingLabelRefund, Error> = waitFor { promise in
748+
remote.refundShippingLabel(siteID: self.sampleSiteID,
749+
orderID: self.sampleOrderID,
750+
shippingLabelID: labelID) { result in
751+
promise(result)
752+
}
753+
}
754+
755+
// Then
756+
XCTAssertNotNil(result.failure)
757+
}
717758
}
718759

719760
private extension WooShippingRemoteTests {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"code": "status_error",
3+
"message": "Successful refund, but there was an error getting label status: Error message from get_status",
4+
"data": {
5+
"message": "error message from get_status"
6+
}
7+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"data": {
3+
"success": false,
4+
"refund": {
5+
"status": "pending",
6+
"request_date": 1723147248000,
7+
"is_manual": false
8+
},
9+
"label": {
10+
"label_id": 1149,
11+
"tracking": "CM199912222US",
12+
"refundable_amount": 58.81,
13+
"created": 1603716274809,
14+
"carrier_id": "usps",
15+
"service_name": "USPS - Priority Mail International",
16+
"status": "PURCHASED",
17+
"commercial_invoice_url": "https://woocommerce.com",
18+
"package_name": "box",
19+
"product_names": [
20+
"Password protected!"
21+
],
22+
"product_ids": [
23+
3013
24+
],
25+
"receipt_item_id": 24448314,
26+
"created_date": 1603716279000,
27+
"main_receipt_id": 19879722,
28+
"rate": 58.81,
29+
"currency": "USD",
30+
"expiry_date": 1619268278000,
31+
"label_cached": 1603716284000,
32+
"refund": {
33+
"request_date": 1723147248000,
34+
"status": "pending",
35+
"refund_date": 1
36+
}
37+
}
38+
}
39+
}
40+

Yosemite/Yosemite/Actions/WooShippingAction.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,10 @@ public enum WooShippingAction: Action {
9898
orderID: Int64,
9999
shipmentToUpdate: WooShippingUpdateShipment,
100100
completion: (Result<WooShippingShipments, Error>) -> Void)
101+
102+
/// Requests a refund for a shipping label
103+
///
104+
case refundShippingLabel(shippingLabel: ShippingLabel,
105+
completion: (Result<ShippingLabel, Error>) -> Void)
106+
101107
}

Yosemite/Yosemite/Stores/WooShippingStore.swift

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public final class WooShippingStore: Store {
8585
orderID: orderID,
8686
shipmentToUpdate: shipmentToUpdate,
8787
completion: completion)
88+
case let .refundShippingLabel(shippingLabel, completion):
89+
refundShippingLabel(shippingLabel: shippingLabel, completion: completion)
8890
}
8991
}
9092
}
@@ -211,6 +213,24 @@ private extension WooShippingStore {
211213
completion: @escaping (Result<[WooShippingOriginAddress], Error>) -> Void) {
212214
remote.loadOriginAddresses(siteID: siteID, completion: completion)
213215
}
216+
217+
func refundShippingLabel(shippingLabel: ShippingLabel,
218+
completion: @escaping (Result<ShippingLabel, Error>) -> Void) {
219+
remote.refundShippingLabel(siteID: shippingLabel.siteID,
220+
orderID: shippingLabel.orderID,
221+
shippingLabelID: shippingLabel.shippingLabelID) { [weak self] result in
222+
guard let self else { return }
223+
switch result {
224+
case .success(let refund):
225+
upsertShippingLabelRefundInBackground(shippingLabel: shippingLabel,
226+
refund: refund) { updatedLabel in
227+
completion(.success(updatedLabel))
228+
}
229+
case .failure(let error):
230+
completion(.failure(error))
231+
}
232+
}
233+
}
214234
}
215235

216236
// MARK: Helpers
@@ -534,6 +554,36 @@ private extension WooShippingStore {
534554
storageSavedPackage.package = predefinedPackage
535555
}
536556

557+
/// Updates the specified shipping label with the given refund *in a background thread*.
558+
/// `onCompletion` will be called on the main thread!
559+
func upsertShippingLabelRefundInBackground(shippingLabel: ShippingLabel,
560+
refund: ShippingLabelRefund,
561+
onCompletion: @escaping (_ updatedLabel: ShippingLabel) -> Void) {
562+
storageManager.performAndSave ({ storage -> ShippingLabel in
563+
let storageShippingLabel = storage.loadShippingLabel(siteID: shippingLabel.siteID,
564+
orderID: shippingLabel.orderID,
565+
shippingLabelID: shippingLabel.shippingLabelID)
566+
guard let storageShippingLabel else {
567+
DDLogWarn("⚠️ No shipping label found in storage when updating refund")
568+
return shippingLabel.copy(refund: refund)
569+
}
570+
571+
let storageRefund = storageShippingLabel.refund ?? storage.insertNewObject(ofType: Storage.ShippingLabelRefund.self)
572+
storageRefund.update(with: refund)
573+
storageShippingLabel.refund = storageRefund
574+
return storageShippingLabel.toReadOnly()
575+
576+
}, completion: { result in
577+
switch result {
578+
case .success(let label):
579+
onCompletion(label)
580+
case .failure(let error):
581+
DDLogError("⛔️ Error upserting shipping label refund: \(error)")
582+
onCompletion(shippingLabel.copy(refund: refund))
583+
}
584+
}, on: .main)
585+
}
586+
537587
/// Updates order's `dateModified` locally
538588
/// Used as temp workaround to reflect that the order instance was updated
539589
private func setLastModifiedDateForOrder(siteID: Int64, orderID: Int64) {

Yosemite/YosemiteTests/Mocks/Networking/Remote/MockWooShippingRemote.swift

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ final class MockWooShippingRemote {
1212
let siteID: Int64
1313
}
1414

15+
private struct RefundResultKey: Hashable {
16+
let siteID: Int64
17+
let orderID: Int64
18+
let shippingLabelID: Int64
19+
}
20+
1521
/// The results to return based on the given arguments in `createPackage`
1622
private var createPackageResults = [ResultKey: Result<WooShippingCreatePackageResponse, Error>]()
1723

@@ -57,6 +63,9 @@ final class MockWooShippingRemote {
5763
/// The results to return based on the given arguments in `updateShipment`
5864
private var updateShipment = [ResultKey: Result<WooShippingShipments, Error>]()
5965

66+
/// The results to return based on the given arguments in `refundShippingLabel`
67+
private var refundShippingLabel = [RefundResultKey: Result<ShippingLabelRefund, Error>]()
68+
6069
/// Set the value passed to the `completion` block if `createPackage` is called.
6170
func whenCreatePackage(siteID: Int64,
6271
thenReturn result: Result<WooShippingCreatePackageResponse, Error>) {
@@ -161,6 +170,15 @@ final class MockWooShippingRemote {
161170
let key = ResultKey(siteID: siteID)
162171
updateShipment[key] = result
163172
}
173+
174+
/// Set the value passed to the `completion` block if `refundShippingLabel` is called.
175+
func whenRefundingShippingLabel(siteID: Int64,
176+
orderID: Int64,
177+
shippingLabelID: Int64,
178+
thenReturn result: Result<ShippingLabelRefund, Error>) {
179+
let key = RefundResultKey(siteID: siteID, orderID: orderID, shippingLabelID: shippingLabelID)
180+
refundShippingLabel[key] = result
181+
}
164182
}
165183

166184
// MARK: - WooShippingRemoteProtocol
@@ -401,4 +419,19 @@ extension MockWooShippingRemote: WooShippingRemoteProtocol {
401419
}
402420
}
403421
}
422+
423+
func refundShippingLabel(siteID: Int64,
424+
orderID: Int64,
425+
shippingLabelID: Int64,
426+
completion: @escaping (Result<ShippingLabelRefund, Error>) -> Void) {
427+
DispatchQueue.main.async { [weak self] in
428+
guard let self else { return }
429+
let key = RefundResultKey(siteID: siteID, orderID: orderID, shippingLabelID: shippingLabelID)
430+
if let result = self.refundShippingLabel[key] {
431+
completion(result)
432+
} else {
433+
XCTFail("\(String(describing: self)) Could not find Result for \(key)")
434+
}
435+
}
436+
}
404437
}

0 commit comments

Comments
 (0)