Skip to content

Commit c47ebd1

Browse files
committed
Merge branch 'develop' into issue/5296-order-fields-param
2 parents 450a9df + f901be8 commit c47ebd1

File tree

50 files changed

+617
-146
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+617
-146
lines changed

Hardware/Hardware/CardReader/CardReaderServiceError.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,9 @@ public enum UnderlyingError: Error {
186186
/// The SDK will attempt to auto-disconnect for you and you should instruct your user to reconnect it.
187187
case readerSessionExpired
188188

189+
/// The underlying request returned an API error.
190+
case processorAPIError
191+
189192
/// Catch-all error case. Indicates there is something wrong with the
190193
/// internal state of the CardReaderService.
191194
case internalServiceError
@@ -305,6 +308,9 @@ updating the application or using a different reader
305308
case .readerSessionExpired:
306309
return NSLocalizedString("The card reader session has expired - please disconnect and reconnect the card reader and then try again",
307310
comment: "Error message when the card reader session has timed out.")
311+
case .processorAPIError:
312+
return NSLocalizedString("The payment can not be processed by the payment processor.",
313+
comment: "Error message when the payment can not be processed (i.e. order amount is below the minimum amount allowed.)")
308314
case .internalServiceError:
309315
return NSLocalizedString("Sorry, this payment couldn’t be processed",
310316
comment: "Error message when the card reader service experiences an unexpected internal service error.")

Hardware/Hardware/CardReader/StripeCardReader/UnderlyingError+Stripe.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ extension UnderlyingError {
7979
self = .requestTimedOut
8080
case ErrorCode.Code.sessionExpired.rawValue:
8181
self = .readerSessionExpired
82+
case ErrorCode.Code.stripeAPIError.rawValue:
83+
self = .processorAPIError
8284
default:
8385
self = .internalServiceError
8486
}

Hardware/HardwareTests/ErrorCodesTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@ final class CardReaderServiceErrorTests: XCTestCase {
154154
XCTAssertEqual(.readerSessionExpired, domainError(stripeCode: 9060))
155155
}
156156

157+
func test_stripe_error_api_maps_to_stripeAPI() {
158+
XCTAssertEqual(.processorAPIError, domainError(stripeCode: 9020))
159+
}
160+
157161
func test_stripe_catch_all_error() {
158162
// Any error code not mapped to an specific error will be
159163
// mapped to `internalServiceError`

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@
142142
31104E142630DDA700587C1E /* wcpay-account-wrong-json.json in Resources */ = {isa = PBXBuildFile; fileRef = 31104E132630DDA700587C1E /* wcpay-account-wrong-json.json */; };
143143
311976E02602BD4B006AC56C /* SitePluginsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 311976DF2602BD4B006AC56C /* SitePluginsMapperTests.swift */; };
144144
314703082670222500EF253A /* PaymentGatewayAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 314703072670222500EF253A /* PaymentGatewayAccount.swift */; };
145+
3158A49F2729F3F600C3CFA8 /* wcpay-account-live-live.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158A49E2729F3F600C3CFA8 /* wcpay-account-live-live.json */; };
146+
3158A4A12729F40F00C3CFA8 /* wcpay-account-live-test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158A4A02729F40F00C3CFA8 /* wcpay-account-live-test.json */; };
147+
3158A4A32729F42500C3CFA8 /* wcpay-account-dev-test.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158A4A22729F42500C3CFA8 /* wcpay-account-dev-test.json */; };
145148
3158FE6026129ADD00E566B9 /* wcpay-account-none.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158FE5F26129ADD00E566B9 /* wcpay-account-none.json */; };
146149
3158FE6426129B1300E566B9 /* wcpay-account-complete.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158FE6326129B1300E566B9 /* wcpay-account-complete.json */; };
147150
3158FE6826129CE200E566B9 /* wcpay-account-rejected-fraud.json in Resources */ = {isa = PBXBuildFile; fileRef = 3158FE6726129CE200E566B9 /* wcpay-account-rejected-fraud.json */; };
@@ -704,6 +707,9 @@
704707
31104E132630DDA700587C1E /* wcpay-account-wrong-json.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-wrong-json.json"; sourceTree = "<group>"; };
705708
311976DF2602BD4B006AC56C /* SitePluginsMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SitePluginsMapperTests.swift; sourceTree = "<group>"; };
706709
314703072670222500EF253A /* PaymentGatewayAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaymentGatewayAccount.swift; sourceTree = "<group>"; };
710+
3158A49E2729F3F600C3CFA8 /* wcpay-account-live-live.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-live-live.json"; sourceTree = "<group>"; };
711+
3158A4A02729F40F00C3CFA8 /* wcpay-account-live-test.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-live-test.json"; sourceTree = "<group>"; };
712+
3158A4A22729F42500C3CFA8 /* wcpay-account-dev-test.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-dev-test.json"; sourceTree = "<group>"; };
707713
3158FE5F26129ADD00E566B9 /* wcpay-account-none.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-none.json"; sourceTree = "<group>"; };
708714
3158FE6326129B1300E566B9 /* wcpay-account-complete.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-complete.json"; sourceTree = "<group>"; };
709715
3158FE6726129CE200E566B9 /* wcpay-account-rejected-fraud.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "wcpay-account-rejected-fraud.json"; sourceTree = "<group>"; };
@@ -1707,6 +1713,9 @@
17071713
318E8FD826C324D900F519D7 /* wcpay-customer-error.json */,
17081714
3158FE5F26129ADD00E566B9 /* wcpay-account-none.json */,
17091715
3158FE6326129B1300E566B9 /* wcpay-account-complete.json */,
1716+
3158A49E2729F3F600C3CFA8 /* wcpay-account-live-live.json */,
1717+
3158A4A22729F42500C3CFA8 /* wcpay-account-dev-test.json */,
1718+
3158A4A02729F40F00C3CFA8 /* wcpay-account-live-test.json */,
17101719
31B8D6B326583662008E3DB2 /* wcpay-account-not-eligible.json */,
17111720
3158FE6726129CE200E566B9 /* wcpay-account-rejected-fraud.json */,
17121721
3158FE6B26129D2E00E566B9 /* wcpay-account-rejected-terms-of-service.json */,
@@ -2083,6 +2092,7 @@
20832092
31054734262E36AB00C5C02B /* wcpay-payment-intent-error.json in Resources */,
20842093
45ED4F12239E8C57004F1BE3 /* taxes-classes.json in Resources */,
20852094
B5A2417B217F98FC00595DEF /* broken-notifications.json in Resources */,
2095+
3158A4A32729F42500C3CFA8 /* wcpay-account-dev-test.json in Resources */,
20862096
31104E142630DDA700587C1E /* wcpay-account-wrong-json.json in Resources */,
20872097
3158FE7C26129E2100E566B9 /* wcpay-account-restricted-pending.json in Resources */,
20882098
D823D91422377EE600C90817 /* shipment_tracking_providers.json in Resources */,
@@ -2142,6 +2152,7 @@
21422152
74C947862193A6C70024CB60 /* comment-moderate-unapproved.json in Resources */,
21432153
3105472C262E303400C5C02B /* wcpay-payment-intent-unknown-status.json in Resources */,
21442154
B559EBAA20A0B5CD00836CD4 /* orders-load-all.json in Resources */,
2155+
3158A4A12729F40F00C3CFA8 /* wcpay-account-live-test.json in Resources */,
21452156
74C8F06620EEB76400B6EDC9 /* order-notes.json in Resources */,
21462157
7426CA1321AF34A3004E9FFC /* site-api.json in Resources */,
21472158
453305ED2459E1AA00264E50 /* site-post.json in Resources */,
@@ -2207,6 +2218,7 @@
22072218
74ABA1CB213F19FE00FFAD30 /* top-performers-week.json in Resources */,
22082219
456930AD2652AF00009ED69D /* shipping-label-carriers-and-rates-success.json in Resources */,
22092220
B524194721AC643900D6FC0A /* device-settings.json in Resources */,
2221+
3158A49F2729F3F600C3CFA8 /* wcpay-account-live-live.json in Resources */,
22102222
318E8FD926C324D900F519D7 /* wcpay-customer-error.json in Resources */,
22112223
CEF88DAB233E911A00BED485 /* order-fully-refunded.json in Resources */,
22122224
02698CFA24C188E9005337C4 /* product-variations-load-all-alternative-types.json in Resources */,

Networking/Networking/Model/Product/ProductCategory.swift

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ public struct ProductCategory: Codable, Equatable, GeneratedFakeable {
2727
/// Public initializer for ProductCategory.
2828
///
2929
public init(from decoder: Decoder) throws {
30-
guard let siteID = decoder.userInfo[.siteID] as? Int64 else {
30+
let container = try decoder.container(keyedBy: CodingKeys.self)
31+
32+
guard let siteID = ProductCategory.siteID(from: decoder, container: container) else {
3133
throw ProductCategoryDecodingError.missingSiteID
3234
}
3335

34-
let container = try decoder.container(keyedBy: CodingKeys.self)
35-
3636
let categoryID = try container.decode(Int64.self, forKey: .categoryID)
3737
// Some product endpoints don't include the parent category ID
3838
let parentID = container.failsafeDecodeIfPresent(Int64.self, forKey: .parentID) ?? 0
@@ -48,6 +48,8 @@ public struct ProductCategory: Codable, Equatable, GeneratedFakeable {
4848
try container.encode(categoryID, forKey: .categoryID)
4949
try container.encode(name, forKey: .name)
5050
try container.encode(slug, forKey: .slug)
51+
try container.encode(siteID, forKey: .siteID)
52+
try container.encode(parentID, forKey: .parentID)
5153
}
5254
}
5355

@@ -56,13 +58,30 @@ public struct ProductCategory: Codable, Equatable, GeneratedFakeable {
5658
///
5759
private extension ProductCategory {
5860
enum CodingKeys: String, CodingKey {
61+
case siteID = "siteID"
5962
case categoryID = "id"
6063
case name = "name"
6164
case slug = "slug"
6265
case parentID = "parent"
6366
}
6467
}
6568

69+
private extension ProductCategory {
70+
/// Provides the siteID, that can be found as a encoded value or in the Decoder user info
71+
///
72+
private static func siteID(from decoder: Decoder, container: KeyedDecodingContainer<ProductCategory.CodingKeys>) -> Int64? {
73+
var siteID: Int64?
74+
75+
if let userInfoSiteID = decoder.userInfo[.siteID] as? Int64 {
76+
siteID = userInfoSiteID
77+
} else if let decodedSiteID = try? container.decode(Int64.self, forKey: .siteID) {
78+
siteID = decodedSiteID
79+
}
80+
81+
return siteID
82+
}
83+
}
84+
6685

6786
// MARK: - Comparable Conformance
6887
//

Networking/Networking/Model/WCPayAccount.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ public struct WCPayAccount: Decodable {
44
public static let gatewayID = "woocommerce-payments"
55

66
public let status: WCPayAccountStatusEnum
7+
/// Indicates whether the payment gateway is a live account that can accept actual payments, or just a test/developer account.
8+
/// Not to be confused with "test mode" which is a separate concept (see `isInTestMode`)
9+
///
10+
public let isLiveAccount: Bool
11+
/// Indicates whether the payment gateway is currently in "Test" or "Debug" mode
12+
///
13+
public let isInTestMode: Bool
714
public let hasPendingRequirements: Bool
815
public let hasOverdueRequirements: Bool
916
public let currentDeadline: Date?
@@ -22,6 +29,8 @@ public struct WCPayAccount: Decodable {
2229

2330
public init(
2431
status: WCPayAccountStatusEnum,
32+
isLiveAccount: Bool,
33+
isInTestMode: Bool,
2534
hasPendingRequirements: Bool,
2635
hasOverdueRequirements: Bool,
2736
currentDeadline: Date?,
@@ -32,6 +41,8 @@ public struct WCPayAccount: Decodable {
3241
isCardPresentEligible: Bool
3342
) {
3443
self.status = status
44+
self.isLiveAccount = isLiveAccount
45+
self.isInTestMode = isInTestMode
3546
self.hasPendingRequirements = hasPendingRequirements
3647
self.hasOverdueRequirements = hasOverdueRequirements
3748
self.currentDeadline = currentDeadline
@@ -47,6 +58,8 @@ public struct WCPayAccount: Decodable {
4758
public init(from decoder: Decoder) throws {
4859
let container = try decoder.container(keyedBy: CodingKeys.self)
4960
let status = try container.decode(WCPayAccountStatusEnum.self, forKey: .status)
61+
let isLiveAccount = try container.decode(Bool.self, forKey: .isLive)
62+
let isInTestMode = try container.decode(Bool.self, forKey: .testMode)
5063
let hasPendingRequirements = try container.decode(Bool.self, forKey: .hasPendingRequirements)
5164
let hasOverdueRequirements = try container.decode(Bool.self, forKey: .hasOverdueRequirements)
5265
let currentDeadline = try container.decodeIfPresent(Date.self, forKey: .currentDeadline)
@@ -60,6 +73,8 @@ public struct WCPayAccount: Decodable {
6073

6174
self.init(
6275
status: status,
76+
isLiveAccount: isLiveAccount,
77+
isInTestMode: isInTestMode,
6378
hasPendingRequirements: hasPendingRequirements,
6479
hasOverdueRequirements: hasOverdueRequirements,
6580
currentDeadline: currentDeadline,
@@ -75,6 +90,8 @@ public struct WCPayAccount: Decodable {
7590
public extension WCPayAccount {
7691
static let noAccount = WCPayAccount(
7792
status: .noAccount,
93+
isLiveAccount: false,
94+
isInTestMode: false,
7895
hasPendingRequirements: false,
7996
hasOverdueRequirements: false,
8097
currentDeadline: nil,
@@ -89,17 +106,19 @@ public extension WCPayAccount {
89106
private extension WCPayAccount {
90107
enum CodingKeys: String, CodingKey {
91108
case status = "status"
109+
case isLive = "is_live"
110+
case testMode = "test_mode"
92111
case hasPendingRequirements = "has_pending_requirements"
93112
case hasOverdueRequirements = "has_overdue_requirements"
94113
case currentDeadline = "current_deadline"
95114
case statementDescriptor = "statement_descriptor"
96115
case storeCurrencies = "store_currencies"
97-
case country = "country"
116+
case country
98117
case cardPresentEligible = "card_present_eligible"
99118
}
100119

101120
enum CurrencyCodingKeys: String, CodingKey {
102121
case defaultCurrency = "default"
103-
case supported = "supported"
122+
case supported
104123
}
105124
}

Networking/Networking/Remote/ProductsRemote.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public protocol ProductsRemoteProtocol {
1616
stockStatus: ProductStockStatus?,
1717
productStatus: ProductStatus?,
1818
productType: ProductType?,
19+
productCategory: ProductCategory?,
1920
orderBy: ProductsRemote.OrderKey,
2021
order: ProductsRemote.Order,
2122
excludedProductIDs: [Int64],
@@ -104,16 +105,19 @@ public final class ProductsRemote: Remote, ProductsRemoteProtocol {
104105
stockStatus: ProductStockStatus? = nil,
105106
productStatus: ProductStatus? = nil,
106107
productType: ProductType? = nil,
108+
productCategory: ProductCategory? = nil,
107109
orderBy: OrderKey = .name,
108110
order: Order = .ascending,
109111
excludedProductIDs: [Int64] = [],
110112
completion: @escaping (Result<[Product], Error>) -> Void) {
111113
let stringOfExcludedProductIDs = excludedProductIDs.map { String($0) }
112114
.joined(separator: ",")
115+
113116
let filterParameters = [
114117
ParameterKey.stockStatus: stockStatus?.rawValue ?? "",
115118
ParameterKey.productStatus: productStatus?.rawValue ?? "",
116119
ParameterKey.productType: productType?.rawValue ?? "",
120+
ParameterKey.category: filterProductCategoryParemeterValue(from: productCategory),
117121
ParameterKey.exclude: stringOfExcludedProductIDs
118122
].filter({ $0.value.isEmpty == false })
119123

@@ -298,6 +302,7 @@ public extension ProductsRemote {
298302
static let productStatus: String = "status"
299303
static let productType: String = "type"
300304
static let stockStatus: String = "stock_status"
305+
static let category: String = "category"
301306
static let fields: String = "_fields"
302307
}
303308

@@ -306,6 +311,18 @@ public extension ProductsRemote {
306311
}
307312
}
308313

314+
private extension ProductsRemote {
315+
/// Returns the category Id in string format, or empty string if the product category is nil
316+
///
317+
func filterProductCategoryParemeterValue(from productCategory: ProductCategory?) -> String {
318+
guard let productCategory = productCategory else {
319+
return ""
320+
}
321+
322+
return String(productCategory.categoryID)
323+
}
324+
}
325+
309326
private extension ProductsRemote.OrderKey {
310327
var value: String {
311328
switch self {

Networking/NetworkingTests/Remote/WCPayRemoteTests.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,63 @@ final class WCPayRemoteTests: XCTestCase {
310310
XCTAssertTrue(error is DecodingError)
311311
}
312312

313+
/// Properly decodes live account in live mode wcpay-account-live-live
314+
///
315+
func test_loadAccount_properly_handles_live_account_in_live_mode() throws {
316+
let remote = WCPayRemote(network: network)
317+
318+
network.simulateResponse(requestUrlSuffix: "payments/accounts", filename: "wcpay-account-live-live")
319+
320+
let result: Result<WCPayAccount, Error> = waitFor { promise in
321+
remote.loadAccount(for: self.sampleSiteID) { result in
322+
promise(result)
323+
}
324+
}
325+
326+
XCTAssertTrue(result.isSuccess)
327+
let account = try result.get()
328+
XCTAssertEqual(account.isLiveAccount, true)
329+
XCTAssertEqual(account.isInTestMode, false)
330+
}
331+
332+
/// Properly decodes live account in test mode wcpay-account-live-test
333+
///
334+
func test_loadAccount_properly_handles_live_account_in_test_mode() throws {
335+
let remote = WCPayRemote(network: network)
336+
337+
network.simulateResponse(requestUrlSuffix: "payments/accounts", filename: "wcpay-account-live-test")
338+
339+
let result: Result<WCPayAccount, Error> = waitFor { promise in
340+
remote.loadAccount(for: self.sampleSiteID) { result in
341+
promise(result)
342+
}
343+
}
344+
345+
XCTAssertTrue(result.isSuccess)
346+
let account = try result.get()
347+
XCTAssertEqual(account.isLiveAccount, true)
348+
XCTAssertEqual(account.isInTestMode, true)
349+
}
350+
351+
/// Properly decodes developer account in test mode wcpay-account-dev-test
352+
///
353+
func test_loadAccount_properly_handles_dev_account_in_test_mode() throws {
354+
let remote = WCPayRemote(network: network)
355+
356+
network.simulateResponse(requestUrlSuffix: "payments/accounts", filename: "wcpay-account-dev-test")
357+
358+
let result: Result<WCPayAccount, Error> = waitFor { promise in
359+
remote.loadAccount(for: self.sampleSiteID) { result in
360+
promise(result)
361+
}
362+
}
363+
364+
XCTAssertTrue(result.isSuccess)
365+
let account = try result.get()
366+
XCTAssertEqual(account.isLiveAccount, false)
367+
XCTAssertEqual(account.isInTestMode, true)
368+
}
369+
313370
/// Verifies that loadAccount properly handles networking errors
314371
///
315372
func test_loadAccount_properly_handles_networking_errors() throws {

Networking/NetworkingTests/Responses/wcpay-account-complete.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"data": {
3+
"is_live": true,
34
"has_pending_requirements": false,
45
"has_overdue_requirements": false,
56
"current_deadline": null,
@@ -12,6 +13,7 @@
1213
]
1314
},
1415
"country": "US",
15-
"card_present_eligible": true
16+
"card_present_eligible": true,
17+
"test_mode": false
1618
}
1719
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"data": {
3+
"is_live": false,
4+
"has_pending_requirements": false,
5+
"has_overdue_requirements": false,
6+
"current_deadline": null,
7+
"status": "complete",
8+
"statement_descriptor": "MY.FANCY.US.STORE",
9+
"store_currencies": {
10+
"default": "usd",
11+
"supported": [
12+
"usd"
13+
]
14+
},
15+
"country": "US",
16+
"card_present_eligible": true,
17+
"test_mode": true
18+
}
19+
}

0 commit comments

Comments
 (0)