Skip to content

Commit 0539631

Browse files
authored
Merge pull request #6111 from woocommerce/issue/5976-wcpaycharges-yosemite
[Mobile Payments] Add WCPayCharges Yosemite layer
2 parents 471d5d5 + 2924896 commit 0539631

9 files changed

+487
-1
lines changed

Yosemite/Yosemite.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@
9696
02FF056B23DED3670058E6E7 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 02FF056A23DED3670058E6E7 /* Media.xcassets */; };
9797
02FF056D23DEDCB90058E6E7 /* MockImageSourceWriter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FF056C23DEDCB90058E6E7 /* MockImageSourceWriter.swift */; };
9898
02FF056F23E04F320058E6E7 /* MockMediaExportService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FF056E23E04F320058E6E7 /* MockMediaExportService.swift */; };
99+
031C1EAA27B1702800298699 /* WCPayCharge+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031C1EA927B1702800298699 /* WCPayCharge+ReadOnlyConvertible.swift */; };
100+
031C1EAC27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031C1EAB27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift */; };
101+
031C1EAE27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031C1EAD27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift */; };
102+
031C1EB027B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031C1EAF27B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift */; };
99103
031FD8A026FC970400B315C7 /* RosettaTestingHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 031FD89F26FC970300B315C7 /* RosettaTestingHelper.swift */; };
100104
03FBDA222631521100ACE257 /* CouponAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FBDA212631521100ACE257 /* CouponAction.swift */; };
101105
03FBDA26263296A100ACE257 /* CouponStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FBDA25263296A100ACE257 /* CouponStore.swift */; };
@@ -490,6 +494,10 @@
490494
02FF056A23DED3670058E6E7 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; };
491495
02FF056C23DEDCB90058E6E7 /* MockImageSourceWriter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockImageSourceWriter.swift; sourceTree = "<group>"; };
492496
02FF056E23E04F320058E6E7 /* MockMediaExportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockMediaExportService.swift; sourceTree = "<group>"; };
497+
031C1EA927B1702800298699 /* WCPayCharge+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCPayCharge+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
498+
031C1EAB27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
499+
031C1EAD27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
500+
031C1EAF27B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WCPayCardPaymentDetails+ReadOnlyConvertible.swift"; sourceTree = "<group>"; };
493501
031FD89F26FC970300B315C7 /* RosettaTestingHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosettaTestingHelper.swift; sourceTree = "<group>"; };
494502
03FBDA212631521100ACE257 /* CouponAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponAction.swift; sourceTree = "<group>"; };
495503
03FBDA25263296A100ACE257 /* CouponStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CouponStore.swift; sourceTree = "<group>"; };
@@ -1215,6 +1223,10 @@
12151223
DEFD6D962644423100E51E0D /* SitePlugin+ReadOnlyConvertible.swift */,
12161224
077F39E126A5AFCA00ABEADC /* SystemPlugin+ReadOnlyConvertible.swift */,
12171225
BAB3737827964A9500837B4A /* OrderTaxLine+ReadOnlyConvertible.swift */,
1226+
031C1EA927B1702800298699 /* WCPayCharge+ReadOnlyConvertible.swift */,
1227+
031C1EAB27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift */,
1228+
031C1EAF27B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift */,
1229+
031C1EAD27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift */,
12181230
);
12191231
path = Storage;
12201232
sourceTree = "<group>";
@@ -1844,6 +1856,7 @@
18441856
CE3B7AD72225ECA90050FE4B /* OrderStatusStore.swift in Sources */,
18451857
B5631ECD2114DF8C008D3535 /* EntityListener.swift in Sources */,
18461858
D80F758A223F72AA002F4A3B /* ShipmentTrackingProviderGroup+ReadOnlyConvertible.swift in Sources */,
1859+
031C1EAE27B1877000298699 /* WCPayCardPresentPaymentDetails+ReadOnlyConvertible.swift in Sources */,
18471860
5758EB3024DC7791009ED8A6 /* InAppFeedbackCardVisibilityUseCase.swift in Sources */,
18481861
7471401321877A8B009A11CC /* NotificationStore.swift in Sources */,
18491862
74D42DBA221C978D00B4977D /* ShipmentTracking+ReadOnlyType.swift in Sources */,
@@ -1897,6 +1910,7 @@
18971910
7493750E224988DE007D85D1 /* ProductImage+ReadOnlyConvertible.swift in Sources */,
18981911
26E5A08225A66868000DF8F6 /* ProductAttributeTermStore.swift in Sources */,
18991912
02FF056323DE9C490058E6E7 /* MediaAction.swift in Sources */,
1913+
031C1EAC27B1873200298699 /* WCPayCardPresentReceiptDetails+ReadOnlyConvertible.swift in Sources */,
19001914
D80F758C223F74B6002F4A3B /* ShipmentTrackingProvider+ReadOnlyConvertible.swift in Sources */,
19011915
45739F372437680F00480C95 /* ProductSettings.swift in Sources */,
19021916
247CE88725833F1200F9D9D1 /* MockObjectGraph.swift in Sources */,
@@ -2012,11 +2026,13 @@
20122026
CE179D55235F4E1700C24EB3 /* RefundAction.swift in Sources */,
20132027
02BA23C422EEEB3B009539E7 /* AvailabilityAction.swift in Sources */,
20142028
CE5F9A7A22B2D455001755E8 /* Array+Helpers.swift in Sources */,
2029+
031C1EB027B1879C00298699 /* WCPayCardPaymentDetails+ReadOnlyConvertible.swift in Sources */,
20152030
D849A1452320E565006CB84F /* ProductReview+ReadOnlyType.swift in Sources */,
20162031
743057B3218B69D100441A76 /* Queue.swift in Sources */,
20172032
02C254F62563B47C00A04423 /* ShippingLabelAddress+ReadonlyConvertible.swift in Sources */,
20182033
247CE84A2583246800F9D9D1 /* MockOrderStatusActionHandler.swift in Sources */,
20192034
7492FADB217FAE4D00ED2C69 /* SiteSetting+ReadOnlyType.swift in Sources */,
2035+
031C1EAA27B1702800298699 /* WCPayCharge+ReadOnlyConvertible.swift in Sources */,
20202036
D8652E0D26303A8B00350F37 /* ReceiptAction.swift in Sources */,
20212037
0212AC62242C68B600C51F6C /* ResultsController+SortProducts.swift in Sources */,
20222038
741F34802195EA62005F5BD9 /* CommentAction.swift in Sources */,

Yosemite/Yosemite/Actions/CardPresentPaymentAction.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,8 @@ public enum CardPresentPaymentAction: Action {
7373

7474
/// Checks if a reader is connected
7575
case checkCardReaderConnected(onCompletion: (AnyPublisher<[CardReader], Never>) -> Void)
76+
77+
/// Fetches Charge details by charge ID
78+
///
79+
case fetchWCPayCharge(siteID: Int64, chargeID: String, onCompletion: (Result<WCPayCharge, Error>) -> Void)
7680
}

Yosemite/Yosemite/Model/Model.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,14 @@ public typealias WCPayAccount = Networking.WCPayAccount
144144
public typealias WCPayAccountStatusEnum = Networking.WCPayAccountStatusEnum
145145
public typealias WCPayCustomer = Networking.Customer
146146
public typealias WCPayPaymentMethodType = Networking.WCPayPaymentMethodType
147+
public typealias WCPayCharge = Networking.WCPayCharge
148+
public typealias WCPayCardBrand = Networking.WCPayCardBrand
149+
public typealias WCPayCardFunding = Networking.WCPayCardFunding
150+
public typealias WCPayCardPresentPaymentDetails = Networking.WCPayCardPresentPaymentDetails
151+
public typealias WCPayCardPaymentDetails = Networking.WCPayCardPaymentDetails
152+
public typealias WCPayCardPresentReceiptDetails = Networking.WCPayCardPresentReceiptDetails
153+
public typealias WCPayPaymentMethodDetails = Networking.WCPayPaymentMethodDetails
154+
public typealias WCPayChargeStatus = Networking.WCPayChargeStatus
147155

148156
// MARK: - Exported Storage Symbols
149157

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Foundation
2+
import Storage
3+
4+
extension Storage.WCPayCardPaymentDetails: ReadOnlyConvertible {
5+
public func update(with paymentDetails: Yosemite.WCPayCardPaymentDetails) {
6+
brand = paymentDetails.brand.rawValue
7+
last4 = paymentDetails.last4
8+
funding = paymentDetails.funding.rawValue
9+
}
10+
11+
public func toReadOnly() -> Yosemite.WCPayCardPaymentDetails {
12+
WCPayCardPaymentDetails(brand: WCPayCardBrand(rawValue: brand) ?? .unknown,
13+
last4: last4,
14+
funding: WCPayCardFunding(rawValue: funding) ?? .unknown)
15+
}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import Foundation
2+
import Storage
3+
4+
extension Storage.WCPayCardPresentPaymentDetails: ReadOnlyConvertible {
5+
public func update(with paymentDetails: Yosemite.WCPayCardPresentPaymentDetails) {
6+
brand = paymentDetails.brand.rawValue
7+
last4 = paymentDetails.last4
8+
funding = paymentDetails.funding.rawValue
9+
}
10+
11+
public func toReadOnly() -> Yosemite.WCPayCardPresentPaymentDetails {
12+
let receiptDetails = receipt?.toReadOnly() ?? WCPayCardPresentReceiptDetails(accountType: .unknown, applicationPreferredName: "", dedicatedFileName: "")
13+
return WCPayCardPresentPaymentDetails(brand: WCPayCardBrand(rawValue: brand) ?? .unknown,
14+
last4: last4,
15+
funding: WCPayCardFunding(rawValue: funding) ?? .unknown,
16+
receipt: receiptDetails)
17+
}
18+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Foundation
2+
import Storage
3+
4+
extension Storage.WCPayCardPresentReceiptDetails: ReadOnlyConvertible {
5+
public func update(with receiptDetails: Yosemite.WCPayCardPresentReceiptDetails) {
6+
accountType = receiptDetails.accountType.rawValue
7+
applicationPreferredName = receiptDetails.applicationPreferredName
8+
dedicatedFileName = receiptDetails.dedicatedFileName
9+
}
10+
11+
public func toReadOnly() -> Yosemite.WCPayCardPresentReceiptDetails {
12+
let yosemiteAccountType = WCPayCardFunding(rawValue: accountType) ?? .unknown
13+
return WCPayCardPresentReceiptDetails(accountType: yosemiteAccountType,
14+
applicationPreferredName: applicationPreferredName,
15+
dedicatedFileName: dedicatedFileName)
16+
}
17+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import Foundation
2+
import Storage
3+
4+
// MARK: - Storage.WCPayCharge: ReadOnlyConvertible
5+
//
6+
extension Storage.WCPayCharge: ReadOnlyConvertible {
7+
8+
/// Updates the `Storage.WCPayCharge` using the ReadOnly representation (`Networking.WCPayCharge`)
9+
///
10+
/// - Parameter wcPayCharge: ReadOnly representation of WCPayCharge
11+
///
12+
public func update(with wcPayCharge: Yosemite.WCPayCharge) {
13+
siteID = wcPayCharge.siteID
14+
chargeID = wcPayCharge.id
15+
amount = wcPayCharge.amount
16+
amountCaptured = wcPayCharge.amountCaptured
17+
amountRefunded = wcPayCharge.amountRefunded
18+
authorizationCode = wcPayCharge.authorizationCode
19+
captured = wcPayCharge.captured
20+
created = wcPayCharge.created
21+
currency = wcPayCharge.currency
22+
paid = wcPayCharge.paid
23+
paymentIntentID = wcPayCharge.paymentIntentID
24+
paymentMethodID = wcPayCharge.paymentMethodID
25+
paymentMethodType = wcPayCharge.paymentMethodDetails.paymentMethodType
26+
refunded = wcPayCharge.refunded
27+
status = wcPayCharge.status.rawValue
28+
}
29+
30+
/// Returns a ReadOnly (`Networking.WCPayCharge`) version of the `Storage.WCPayCharge`
31+
///
32+
public func toReadOnly() -> Yosemite.WCPayCharge {
33+
let paymentMethodDetails = WCPayPaymentMethodDetails(type: paymentMethodType,
34+
cardDetails: cardDetails?.toReadOnly(),
35+
cardPresentDetails: cardPresentDetails?.toReadOnly())
36+
return WCPayCharge(siteID: siteID,
37+
id: chargeID,
38+
amount: amount,
39+
amountCaptured: amountCaptured,
40+
amountRefunded: amountRefunded,
41+
authorizationCode: authorizationCode,
42+
captured: captured,
43+
created: created,
44+
currency: currency,
45+
paid: paid,
46+
paymentIntentID: paymentIntentID,
47+
paymentMethodID: paymentMethodID,
48+
paymentMethodDetails: paymentMethodDetails,
49+
refunded: refunded,
50+
status: WCPayChargeStatus(rawValue: status) ?? .succeeded)
51+
}
52+
}
53+
54+
private extension WCPayPaymentMethodDetails {
55+
var paymentMethodType: String {
56+
switch self {
57+
case .unknown:
58+
return "unknown"
59+
case .card(_):
60+
return "card"
61+
case .cardPresent(_):
62+
return "cardPresent"
63+
}
64+
}
65+
66+
init(type: String, cardDetails: WCPayCardPaymentDetails?, cardPresentDetails: WCPayCardPresentPaymentDetails?) {
67+
switch (type, cardDetails, cardPresentDetails) {
68+
case ("unknown", _, _):
69+
self = .unknown
70+
case ("card", .some(let cardDetails), _):
71+
self = .card(details: cardDetails)
72+
case ("cardPresent", _, .some(let cardPresentDetails)):
73+
self = .cardPresent(details: cardPresentDetails)
74+
default:
75+
self = .unknown
76+
}
77+
}
78+
}

Yosemite/Yosemite/Stores/CardPresentPaymentStore.swift

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public final class CardPresentPaymentStore: Store {
106106
reset()
107107
case .checkCardReaderConnected(onCompletion: let completion):
108108
checkCardReaderConnected(onCompletion: completion)
109+
case .fetchWCPayCharge(let siteID, let chargeID, let completion):
110+
fetchCharge(siteID: siteID, chargeID: chargeID, completion: completion)
109111
}
110112
}
111113
}
@@ -519,7 +521,28 @@ private extension CardPresentPaymentStore {
519521
return
520522
}
521523
})
522-
}}
524+
}
525+
526+
func fetchCharge(siteID: Int64, chargeID: String, completion: @escaping (Result<WCPayCharge, Error>) -> Void) {
527+
switch usingBackend {
528+
case .wcpay:
529+
remote.fetchCharge(for: siteID, chargeID: chargeID) { result in
530+
switch result {
531+
case .success(let charge):
532+
self.upsertCharge(readonlyCharge: charge)
533+
completion(.success(charge))
534+
case .failure(let error):
535+
if case .noSuchChargeError(_) = WCPayChargesError(underlyingError: error) {
536+
self.deleteCharge(siteID: siteID, chargeID: chargeID)
537+
}
538+
completion(.failure(error))
539+
}
540+
}
541+
case .stripe:
542+
break; /// not implemented
543+
}
544+
}
545+
}
523546

524547
// MARK: Storage Methods
525548
private extension CardPresentPaymentStore {
@@ -540,6 +563,62 @@ private extension CardPresentPaymentStore {
540563
storage.deleteObject(storageAccount)
541564
storage.saveIfNeeded()
542565
}
566+
567+
func upsertCharge(readonlyCharge: WCPayCharge) {
568+
let storage = storageManager.viewStorage
569+
let storageWCPayCharge = existingOrNewWCPayCharge(siteID: readonlyCharge.siteID, chargeID: readonlyCharge.id, in: storage)
570+
571+
switch readonlyCharge.paymentMethodDetails {
572+
case .cardPresent(let details):
573+
upsertCardPresentDetails(details, for: storageWCPayCharge, in: storage)
574+
case .card(let details):
575+
upsertCardDetails(details, for: storageWCPayCharge, in: storage)
576+
case .unknown:
577+
storageWCPayCharge.cardDetails = nil
578+
storageWCPayCharge.cardPresentDetails = nil
579+
}
580+
581+
storageWCPayCharge.update(with: readonlyCharge)
582+
}
583+
584+
private func existingOrNewWCPayCharge(siteID: Int64, chargeID: String, in storage: StorageType) -> Storage.WCPayCharge {
585+
storage.loadWCPayCharge(siteID: siteID, chargeID: chargeID) ?? storage.insertNewObject(ofType: Storage.WCPayCharge.self)
586+
}
587+
588+
private func upsertCardPresentDetails(_ details: WCPayCardPresentPaymentDetails,
589+
for storageWCPayCharge: Storage.WCPayCharge,
590+
in storage: StorageType) {
591+
let storageCardPresentDetails = storageWCPayCharge.cardPresentDetails ?? storage.insertNewObject(ofType: Storage.WCPayCardPresentPaymentDetails.self)
592+
let storageReceiptDetails = storageCardPresentDetails.receipt ?? storage.insertNewObject(ofType: Storage.WCPayCardPresentReceiptDetails.self)
593+
594+
storageCardPresentDetails.update(with: details)
595+
storageReceiptDetails.update(with: details.receipt)
596+
597+
storageCardPresentDetails.receipt = storageReceiptDetails
598+
599+
storageWCPayCharge.cardPresentDetails = storageCardPresentDetails
600+
storageWCPayCharge.cardDetails = nil
601+
}
602+
603+
private func upsertCardDetails(_ details: WCPayCardPaymentDetails,
604+
for storageWCPayCharge: Storage.WCPayCharge,
605+
in storage: StorageType) {
606+
let storageCardDetails = storageWCPayCharge.cardDetails ?? storage.insertNewObject(ofType: Storage.WCPayCardPaymentDetails.self)
607+
storageCardDetails.update(with: details)
608+
609+
storageWCPayCharge.cardDetails = storageCardDetails
610+
storageWCPayCharge.cardPresentDetails = nil
611+
}
612+
613+
func deleteCharge(siteID: Int64, chargeID: String) {
614+
let storage = storageManager.viewStorage
615+
guard let charge = storage.loadWCPayCharge(siteID: siteID, chargeID: chargeID) else {
616+
return
617+
}
618+
619+
storage.deleteObject(charge)
620+
storage.saveIfNeeded()
621+
}
543622
}
544623

545624
public enum PaymentGatewayAccountError: Error, LocalizedError {
@@ -604,3 +683,49 @@ public protocol CardReaderCapableRemote {
604683

605684
extension WCPayRemote: CardReaderCapableRemote {}
606685
extension StripeRemote: CardReaderCapableRemote {}
686+
687+
// MARK: - WCPayChargesError
688+
public enum WCPayChargesError: Error, LocalizedError {
689+
case noSuchChargeError(message: String)
690+
case otherError(error: AnyError)
691+
692+
init(underlyingError error: Error) {
693+
guard case let DotcomError.unknown(code, message) = error,
694+
let message = message else {
695+
self = .otherError(error: error.toAnyError)
696+
return
697+
}
698+
699+
/// See if we recognize this DotcomError code
700+
///
701+
self = ErrorCode(rawValue: code)?.error(message: message) ?? .otherError(error: error.toAnyError)
702+
}
703+
704+
enum ErrorCode: String {
705+
case getChargeError = "wcpay_get_charge"
706+
case unknown
707+
708+
func error(message: String) -> WCPayChargesError? {
709+
switch self {
710+
case .getChargeError:
711+
guard message.starts(with: "Error: No such charge") else {
712+
return nil
713+
}
714+
return .noSuchChargeError(message: message)
715+
default:
716+
return nil
717+
}
718+
}
719+
}
720+
721+
public var errorDescription: String? {
722+
switch self {
723+
case .noSuchChargeError(let message):
724+
/// Return the message directly from the store
725+
/// "Error: No such charge: 'ch_3KMVapErrorERROR'"
726+
return message
727+
case .otherError(let error):
728+
return error.localizedDescription
729+
}
730+
}
731+
}

0 commit comments

Comments
 (0)