Skip to content

Commit b6e0951

Browse files
authored
Merge pull request #7875 from woocommerce/issue/7835-iap-networking
Implement networking for In-app purchases
2 parents f3dfdea + 30e2828 commit b6e0951

File tree

10 files changed

+313
-0
lines changed

10 files changed

+313
-0
lines changed

Networking/Networking.xcodeproj/project.pbxproj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,15 @@
676676
DEC51AFB2769C66B009F3DF4 /* SystemStatusMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */; };
677677
DEC51B02276AFB35009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */; };
678678
E12552C526385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */; };
679+
E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */; };
680+
E16C59B528F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */; };
681+
E16C59B728F92782007D55BB /* iap-sample-receipt.json in Resources */ = {isa = PBXBuildFile; fileRef = E16C59B628F92782007D55BB /* iap-sample-receipt.json */; };
682+
E16C59B928F927CA007D55BB /* iap-order-create.json in Resources */ = {isa = PBXBuildFile; fileRef = E16C59B828F927CA007D55BB /* iap-order-create.json */; };
683+
E18152BE28F85B5B0011A0EC /* InAppPurchasesRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */; };
684+
E18152C028F85D4A0011A0EC /* InAppPurchasesProductMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */; };
685+
E18152C228F85E0A0011A0EC /* iap-products.json in Resources */ = {isa = PBXBuildFile; fileRef = E18152C128F85E0A0011A0EC /* iap-products.json */; };
686+
E18152C428F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */; };
687+
E1A5C27228F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */; };
679688
EE8A86F1286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */; };
680689
EECB7EE8286555180028C888 /* media-update-product-id.json in Resources */ = {isa = PBXBuildFile; fileRef = EECB7EE7286555180028C888 /* media-update-product-id.json */; };
681690
FE28F6E226840DED004465C7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E126840DED004465C7 /* User.swift */; };
@@ -1384,6 +1393,15 @@
13841393
DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemStatusMapperTests.swift; sourceTree = "<group>"; };
13851394
DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SystemStatus+DropinMustUsePlugin.swift"; sourceTree = "<group>"; };
13861395
E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelAddressValidationSuccess.swift; sourceTree = "<group>"; };
1396+
E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesRemoteTests.swift; sourceTree = "<group>"; };
1397+
E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseOrderResultMapper.swift; sourceTree = "<group>"; };
1398+
E16C59B628F92782007D55BB /* iap-sample-receipt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-sample-receipt.json"; sourceTree = "<group>"; };
1399+
E16C59B828F927CA007D55BB /* iap-order-create.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-order-create.json"; sourceTree = "<group>"; };
1400+
E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesRemote.swift; sourceTree = "<group>"; };
1401+
E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesProductMapper.swift; sourceTree = "<group>"; };
1402+
E18152C128F85E0A0011A0EC /* iap-products.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-products.json"; sourceTree = "<group>"; };
1403+
E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesProductsMapperTests.swift; sourceTree = "<group>"; };
1404+
E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseOrderResultMapperTests.swift; sourceTree = "<group>"; };
13871405
EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id-in-wordpress-site.json"; sourceTree = "<group>"; };
13881406
EECB7EE7286555180028C888 /* media-update-product-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id.json"; sourceTree = "<group>"; };
13891407
F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1612,6 +1630,7 @@
16121630
B524194821AC659500D6FC0A /* DevicesRemoteTests.swift */,
16131631
24F98C5F2502EF8200F49B68 /* FeatureFlagRemoteTests.swift */,
16141632
4513382127A8409000AE5E78 /* InboxNotesRemoteTests.swift */,
1633+
E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */,
16151634
26B2F74824C55ACE0065CCC8 /* LeaderboardsRemoteTests.swift */,
16161635
020D07BF23D8587700FD9580 /* MediaRemoteTests.swift */,
16171636
B554FA8A2180B1D500C54DFF /* NotificationsRemoteTests.swift */,
@@ -1745,6 +1764,7 @@
17451764
45E461BB26837CC500011BF2 /* DataRemote.swift */,
17461765
B572F69921AC475C003EEFF0 /* DevicesRemote.swift */,
17471766
24F98C512502E79800F49B68 /* FeatureFlagsRemote.swift */,
1767+
E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */,
17481768
4513381F27A8227F00AE5E78 /* InboxNotesRemote.swift */,
17491769
26B2F74024C1F2C10065CCC8 /* LeaderboardsRemote.swift */,
17501770
B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */,
@@ -1954,6 +1974,9 @@
19541974
B524194621AC643900D6FC0A /* device-settings.json */,
19551975
24F98C612502EFF600F49B68 /* feature-flags-load-all.json */,
19561976
268B68FA24C87384007EBF1D /* leaderboards-products.json */,
1977+
E18152C128F85E0A0011A0EC /* iap-products.json */,
1978+
E16C59B628F92782007D55BB /* iap-sample-receipt.json */,
1979+
E16C59B828F927CA007D55BB /* iap-order-create.json */,
19571980
26B2F74624C55A6E0065CCC8 /* leaderboards-year.json */,
19581981
268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */,
19591982
B505F6D420BEE4E600BB1B69 /* me.json */,
@@ -2144,6 +2167,8 @@
21442167
24F98C572502EA8800F49B68 /* FeatureFlagMapper.swift */,
21452168
DE50295C28C6068B00551736 /* JetpackUserMapper.swift */,
21462169
AEF9458A27297FF6001DCCFB /* IgnoringResponseMapper.swift */,
2170+
E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */,
2171+
E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */,
21472172
4513382327A951B300AE5E78 /* InboxNoteMapper.swift */,
21482173
45CCFCE527A2E3710012E8CB /* InboxNoteListMapper.swift */,
21492174
26B2F74424C5573F0065CCC8 /* LeaderboardListMapper.swift */,
@@ -2293,6 +2318,8 @@
22932318
45150A9F26837357006922EA /* CountryListMapperTests.swift */,
22942319
B524194221AC622500D6FC0A /* DotcomDeviceMapperTests.swift */,
22952320
AED8AEBB272A997500663FCC /* IgnoringResponseMapperTests.swift */,
2321+
E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */,
2322+
E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */,
22962323
4513382527A96DB700AE5E78 /* InboxNoteMapperTests.swift */,
22972324
45CCFCE727A2E5020012E8CB /* InboxNoteListMapperTests.swift */,
22982325
B554FA922180C17200C54DFF /* NoteHashListMapperTests.swift */,
@@ -2583,6 +2610,7 @@
25832610
740211DF2193985A002248DA /* comment-moderate-spam.json in Resources */,
25842611
B5147876211B9227007562E5 /* broken-orders-mark-2.json in Resources */,
25852612
3158FE6C26129D2E00E566B9 /* wcpay-account-rejected-terms-of-service.json in Resources */,
2613+
E16C59B928F927CA007D55BB /* iap-order-create.json in Resources */,
25862614
31054728262E2FEE00C5C02B /* wcpay-payment-intent-canceled.json in Resources */,
25872615
31A451CC27863A2E00FE81AA /* stripe-account-rejected-fraud.json in Resources */,
25882616
31A451D827863A2E00FE81AA /* stripe-account-restricted-overdue.json in Resources */,
@@ -2710,6 +2738,7 @@
27102738
31054714262E2F3B00C5C02B /* wcpay-payment-intent-requires-payment-method.json in Resources */,
27112739
2683D71024456EE4002A1589 /* categories-extra.json in Resources */,
27122740
A69FE19D2588D70E0059A96B /* order-with-deleted-refunds.json in Resources */,
2741+
E18152C228F85E0A0011A0EC /* iap-products.json in Resources */,
27132742
DE2095C127966EC800171F1C /* coupon-reports.json in Resources */,
27142743
453305F52459ED2700264E50 /* site-post-update.json in Resources */,
27152744
CECC759E23D6231A00486676 /* order-560-all-refunds.json in Resources */,
@@ -2739,6 +2768,7 @@
27392768
CC9A254626442CA7005DE56E /* shipping-label-eligibility-failure.json in Resources */,
27402769
4524CD9C242CEFAB00B2F20A /* product-on-sale-with-empty-sale-price.json in Resources */,
27412770
020220E223966CD900290165 /* product-shipping-classes-load-one.json in Resources */,
2771+
E16C59B728F92782007D55BB /* iap-sample-receipt.json in Resources */,
27422772
3158FE7826129DF300E566B9 /* wcpay-account-restricted.json in Resources */,
27432773
45A4B85625D2E75300776FB4 /* shipping-label-address-validation-success.json in Resources */,
27442774
457FC68C2382B2FD00B41B02 /* product-update.json in Resources */,
@@ -3055,10 +3085,12 @@
30553085
2685C0FE263B5D8900D9EE97 /* AddOnGroupRemote.swift in Sources */,
30563086
450106912399B2C800E24722 /* TaxClassListMapper.swift in Sources */,
30573087
B963A5CC2853870000EFADA0 /* OrderItemRefundMetaData.swift in Sources */,
3088+
E18152BE28F85B5B0011A0EC /* InAppPurchasesRemote.swift in Sources */,
30583089
0329CF9B27A82E19008AFF91 /* WCPayCharge.swift in Sources */,
30593090
74749B97224134FF005C4CF2 /* ProductMapper.swift in Sources */,
30603091
26455E2A25F669F0008A1D32 /* ProductAttributeTermMapper.swift in Sources */,
30613092
0359EA1127AAC6740048DE2D /* WCPayPaymentMethodType.swift in Sources */,
3093+
E16C59B528F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift in Sources */,
30623094
026CF61A237D607A009563D4 /* ProductVariationAttribute.swift in Sources */,
30633095
D8FBFF1A22D4DF7A006E3336 /* OrderStatsV4.swift in Sources */,
30643096
74A1D26B21189B8100931DFA /* SiteVisitStatsItem.swift in Sources */,
@@ -3141,6 +3173,7 @@
31413173
CE0A0F1B223989670075ED8D /* ProductsRemote.swift in Sources */,
31423174
0359EA1527AAC7460048DE2D /* WCPayCardBrand.swift in Sources */,
31433175
2665032E261F4FBF0079A159 /* ProductAddOnOption.swift in Sources */,
3176+
E18152C028F85D4A0011A0EC /* InAppPurchasesProductMapper.swift in Sources */,
31443177
028296F7237D588700E84012 /* ProductVariation.swift in Sources */,
31453178
DEC51AEF2768A628009F3DF4 /* SystemStatus+Database.swift in Sources */,
31463179
);
@@ -3158,6 +3191,7 @@
31583191
45B204BA24890A8C00FE6526 /* ProductCategoryMapperTests.swift in Sources */,
31593192
74749B9522413119005C4CF2 /* ProductsRemoteTests.swift in Sources */,
31603193
B524194321AC622500D6FC0A /* DotcomDeviceMapperTests.swift in Sources */,
3194+
E1A5C27228F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift in Sources */,
31613195
45150AA026837357006922EA /* CountryListMapperTests.swift in Sources */,
31623196
74D5BECE217E0F98007B0348 /* SiteSettingsRemoteTests.swift in Sources */,
31633197
D8FBFF1C22D51C34006E3336 /* OrderStatsRemoteV4Tests.swift in Sources */,
@@ -3221,6 +3255,7 @@
32213255
2670C3FC270F4E06002FE931 /* SiteListMapperTests.swift in Sources */,
32223256
025CA2C6238F4F3500B05C81 /* ProductShippingClassRemoteTests.swift in Sources */,
32233257
D800DA0A25EFEB9C001E13CE /* WCPayRemoteTests.swift in Sources */,
3258+
E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */,
32243259
CC851D1425E52AB500249E9C /* Decimal+ExtensionsTests.swift in Sources */,
32253260
B554FA8B2180B1D500C54DFF /* NotificationsRemoteTests.swift in Sources */,
32263261
B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */,
@@ -3267,6 +3302,7 @@
32673302
740211E121939908002248DA /* CommentRemoteTests.swift in Sources */,
32683303
B57B1E6A21C925280046E764 /* DotcomValidatorTests.swift in Sources */,
32693304
B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */,
3305+
E18152C428F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift in Sources */,
32703306
D8A2849A25FBB2E70019A84B /* ProductAttributeTermListMapperTests.swift in Sources */,
32713307
);
32723308
runOnlyForDeploymentPostprocessing = 0;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
4+
/// Mapper: IAP Order Creation Result
5+
///
6+
struct InAppPurchaseOrderResultMapper: Mapper {
7+
8+
/// (Attempts) to extract the order ID from a given JSON Encoded response.
9+
///
10+
func map(response: Data) throws -> Int {
11+
12+
let dictionary = try JSONDecoder().decode([String: AnyDecodable].self, from: response)
13+
guard let orderId = (dictionary[Constants.orderIdKey]?.value as? Int) else {
14+
throw Error.parseError
15+
}
16+
return orderId
17+
}
18+
}
19+
20+
private extension InAppPurchaseOrderResultMapper {
21+
enum Constants {
22+
static let orderIdKey: String = "order_id"
23+
}
24+
25+
enum Error: Swift.Error {
26+
case parseError
27+
}
28+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Foundation
2+
3+
/// Mapper: IAP Product
4+
///
5+
struct InAppPurchasesProductMapper: Mapper {
6+
/// (Attempts) to convert a dictionary into a list of product identifiers.
7+
///
8+
func map(response: Data) throws -> [String] {
9+
let decoder = JSONDecoder()
10+
decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter)
11+
return try decoder.decode([String].self, from: response)
12+
}
13+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import Alamofire
2+
import Foundation
3+
4+
/// In-app Purchases Endpoints
5+
///
6+
public class InAppPurchasesRemote: Remote {
7+
/// Retrieves a list of product identifiers available for purchase
8+
///
9+
/// - Parameters:
10+
/// - completion: Closure to be executed upon completion
11+
///
12+
public func loadProducts(completion: @escaping (Swift.Result<[String], Error>) -> Void) {
13+
let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: Constants.productsPath)
14+
let request = augmentedRequestWithAppId(dotComRequest)
15+
let mapper = InAppPurchasesProductMapper()
16+
enqueue(request, mapper: mapper, completion: completion)
17+
}
18+
19+
/// Creates a new order for a new In-app Purchase
20+
/// - Parameters:
21+
/// - siteID: Site the purchase is for
22+
/// - price: An integer representation of the price in cents (5.99 -> 599)
23+
/// - productIdentifier: The IAP sku for the product
24+
/// - appStoreCountryCode: The country of the user's App Store
25+
/// - receiptData: The transaction receipt from Apple
26+
/// - completion: Closure to be executed upon completion
27+
///
28+
public func createOrder(
29+
for siteID: Int64,
30+
price: Int,
31+
productIdentifier: String,
32+
appStoreCountryCode: String,
33+
receiptData: Data,
34+
completion: @escaping (Swift.Result<Int, Error>) -> Void) {
35+
let parameters: [String: Any] = [
36+
Constants.siteIDKey: siteID,
37+
Constants.priceKey: price,
38+
Constants.productIDKey: productIdentifier,
39+
Constants.appStoreCountryCodeKey: appStoreCountryCode,
40+
Constants.receiptDataKey: receiptData.base64EncodedString()
41+
]
42+
let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .post, path: Constants.ordersPath, parameters: parameters)
43+
let request = augmentedRequestWithAppId(dotComRequest)
44+
let mapper = InAppPurchaseOrderResultMapper()
45+
enqueue(request, mapper: mapper, completion: completion)
46+
}
47+
}
48+
49+
// MARK: - Async methods
50+
51+
public extension InAppPurchasesRemote {
52+
/// Retrieves a list of product identifiers available for purchase
53+
///
54+
/// - Returns: a list of product identifiers.
55+
///
56+
func loadProducts() async throws -> [String] {
57+
try await withCheckedThrowingContinuation { continuation in
58+
loadProducts { result in
59+
continuation.resume(with: result)
60+
}
61+
}
62+
}
63+
64+
/// Creates a new order for a new In-app Purchase
65+
/// - Parameters:
66+
/// - siteID: Site the purchase is for
67+
/// - price: An integer representation of the price in cents (5.99 -> 599)
68+
/// - productIdentifier: The IAP sku for the product
69+
/// - appStoreCountryCode: The country of the user's App Store
70+
/// - receiptData: The transaction receipt from Apple
71+
///
72+
/// - Returns: The ID of the created order.
73+
///
74+
func createOrder(
75+
for siteID: Int64,
76+
price: Int,
77+
productIdentifier: String,
78+
appStoreCountryCode: String,
79+
receiptData: Data
80+
) async throws -> Int {
81+
try await withCheckedThrowingContinuation { continuation in
82+
createOrder(
83+
for: siteID,
84+
price: price,
85+
productIdentifier: productIdentifier,
86+
appStoreCountryCode: appStoreCountryCode,
87+
receiptData: receiptData
88+
) { result in
89+
continuation.resume(with: result)
90+
}
91+
}
92+
}
93+
}
94+
95+
private extension InAppPurchasesRemote {
96+
func augmentedRequestWithAppId(_ request: URLRequestConvertible) -> URLRequestConvertible {
97+
guard let bundleIdentifier = Bundle.main.bundleIdentifier,
98+
var augmented = try? request.asURLRequest() else {
99+
return request
100+
}
101+
102+
augmented.setValue(bundleIdentifier, forHTTPHeaderField: "X-APP-ID")
103+
104+
return augmented
105+
}
106+
}
107+
108+
private extension InAppPurchasesRemote {
109+
enum Constants {
110+
static let productsPath = "iap/products"
111+
static let ordersPath = "iap/orders"
112+
113+
static let siteIDKey = "site_id"
114+
static let priceKey = "price"
115+
static let productIDKey = "product_id"
116+
static let appStoreCountryCodeKey = "appstore_country"
117+
static let receiptDataKey = "apple_receipt"
118+
}
119+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
final class InAppPurchasesOrderResultMapperTests: XCTestCase {
5+
func test_iap_order_creation_is_decoded_from_json_response() throws {
6+
// Given
7+
let jsonData = try XCTUnwrap(Loader.contentsOf("iap-order-create"))
8+
let expectedOrderId = 12345
9+
10+
// When
11+
let orderId = try InAppPurchaseOrderResultMapper().map(response: jsonData)
12+
13+
// Then
14+
assertEqual(expectedOrderId, orderId)
15+
}
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import XCTest
2+
@testable import Networking
3+
4+
final class InAppPurchasesProductsMapperTests: XCTestCase {
5+
func test_iap_products_list_is_decoded_from_json_response() throws {
6+
// Given
7+
let jsonData = try XCTUnwrap(Loader.contentsOf("iap-products"))
8+
let expectedProductIdentifiers = [
9+
"woocommerce_entry_monthly",
10+
"woocommerce_entry_yearly"]
11+
12+
// When
13+
let products = try InAppPurchasesProductMapper().map(response: jsonData)
14+
15+
// Then
16+
assertEqual(expectedProductIdentifiers, products)
17+
}
18+
}

0 commit comments

Comments
 (0)