diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index bd39dd288dd..c90136ad21e 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -676,6 +676,15 @@ DEC51AFB2769C66B009F3DF4 /* SystemStatusMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */; }; DEC51B02276AFB35009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */; }; E12552C526385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */; }; + E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */; }; + E16C59B528F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */; }; + E16C59B728F92782007D55BB /* iap-sample-receipt.json in Resources */ = {isa = PBXBuildFile; fileRef = E16C59B628F92782007D55BB /* iap-sample-receipt.json */; }; + E16C59B928F927CA007D55BB /* iap-order-create.json in Resources */ = {isa = PBXBuildFile; fileRef = E16C59B828F927CA007D55BB /* iap-order-create.json */; }; + E18152BE28F85B5B0011A0EC /* InAppPurchasesRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */; }; + E18152C028F85D4A0011A0EC /* InAppPurchasesProductMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */; }; + E18152C228F85E0A0011A0EC /* iap-products.json in Resources */ = {isa = PBXBuildFile; fileRef = E18152C128F85E0A0011A0EC /* iap-products.json */; }; + E18152C428F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */; }; + E1A5C27228F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */; }; EE8A86F1286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json in Resources */ = {isa = PBXBuildFile; fileRef = EE8A86F0286C5226003E8AA4 /* media-update-product-id-in-wordpress-site.json */; }; EECB7EE8286555180028C888 /* media-update-product-id.json in Resources */ = {isa = PBXBuildFile; fileRef = EECB7EE7286555180028C888 /* media-update-product-id.json */; }; FE28F6E226840DED004465C7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28F6E126840DED004465C7 /* User.swift */; }; @@ -1384,6 +1393,15 @@ DEC51AFA2769C66B009F3DF4 /* SystemStatusMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemStatusMapperTests.swift; sourceTree = ""; }; DEC51B01276AFB34009F3DF4 /* SystemStatus+DropinMustUsePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SystemStatus+DropinMustUsePlugin.swift"; sourceTree = ""; }; E12552C426385B05001CEE70 /* ShippingLabelAddressValidationSuccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLabelAddressValidationSuccess.swift; sourceTree = ""; }; + E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesRemoteTests.swift; sourceTree = ""; }; + E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseOrderResultMapper.swift; sourceTree = ""; }; + E16C59B628F92782007D55BB /* iap-sample-receipt.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-sample-receipt.json"; sourceTree = ""; }; + E16C59B828F927CA007D55BB /* iap-order-create.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-order-create.json"; sourceTree = ""; }; + E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesRemote.swift; sourceTree = ""; }; + E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesProductMapper.swift; sourceTree = ""; }; + E18152C128F85E0A0011A0EC /* iap-products.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "iap-products.json"; sourceTree = ""; }; + E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchasesProductsMapperTests.swift; sourceTree = ""; }; + E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseOrderResultMapperTests.swift; sourceTree = ""; }; 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 = ""; }; EECB7EE7286555180028C888 /* media-update-product-id.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "media-update-product-id.json"; sourceTree = ""; }; F3F25DC15EC1D7C631169CB5 /* Pods_Networking.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Networking.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1612,6 +1630,7 @@ B524194821AC659500D6FC0A /* DevicesRemoteTests.swift */, 24F98C5F2502EF8200F49B68 /* FeatureFlagRemoteTests.swift */, 4513382127A8409000AE5E78 /* InboxNotesRemoteTests.swift */, + E13BAD5228F8625600217769 /* InAppPurchasesRemoteTests.swift */, 26B2F74824C55ACE0065CCC8 /* LeaderboardsRemoteTests.swift */, 020D07BF23D8587700FD9580 /* MediaRemoteTests.swift */, B554FA8A2180B1D500C54DFF /* NotificationsRemoteTests.swift */, @@ -1745,6 +1764,7 @@ 45E461BB26837CC500011BF2 /* DataRemote.swift */, B572F69921AC475C003EEFF0 /* DevicesRemote.swift */, 24F98C512502E79800F49B68 /* FeatureFlagsRemote.swift */, + E18152BD28F85B5B0011A0EC /* InAppPurchasesRemote.swift */, 4513381F27A8227F00AE5E78 /* InboxNotesRemote.swift */, 26B2F74024C1F2C10065CCC8 /* LeaderboardsRemote.swift */, B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */, @@ -1954,6 +1974,9 @@ B524194621AC643900D6FC0A /* device-settings.json */, 24F98C612502EFF600F49B68 /* feature-flags-load-all.json */, 268B68FA24C87384007EBF1D /* leaderboards-products.json */, + E18152C128F85E0A0011A0EC /* iap-products.json */, + E16C59B628F92782007D55BB /* iap-sample-receipt.json */, + E16C59B828F927CA007D55BB /* iap-order-create.json */, 26B2F74624C55A6E0065CCC8 /* leaderboards-year.json */, 268B68FC24C87E37007EBF1D /* leaderboards-year-alt.json */, B505F6D420BEE4E600BB1B69 /* me.json */, @@ -2144,6 +2167,8 @@ 24F98C572502EA8800F49B68 /* FeatureFlagMapper.swift */, DE50295C28C6068B00551736 /* JetpackUserMapper.swift */, AEF9458A27297FF6001DCCFB /* IgnoringResponseMapper.swift */, + E18152BF28F85D4A0011A0EC /* InAppPurchasesProductMapper.swift */, + E16C59B428F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift */, 4513382327A951B300AE5E78 /* InboxNoteMapper.swift */, 45CCFCE527A2E3710012E8CB /* InboxNoteListMapper.swift */, 26B2F74424C5573F0065CCC8 /* LeaderboardListMapper.swift */, @@ -2293,6 +2318,8 @@ 45150A9F26837357006922EA /* CountryListMapperTests.swift */, B524194221AC622500D6FC0A /* DotcomDeviceMapperTests.swift */, AED8AEBB272A997500663FCC /* IgnoringResponseMapperTests.swift */, + E18152C328F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift */, + E1A5C27128F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift */, 4513382527A96DB700AE5E78 /* InboxNoteMapperTests.swift */, 45CCFCE727A2E5020012E8CB /* InboxNoteListMapperTests.swift */, B554FA922180C17200C54DFF /* NoteHashListMapperTests.swift */, @@ -2583,6 +2610,7 @@ 740211DF2193985A002248DA /* comment-moderate-spam.json in Resources */, B5147876211B9227007562E5 /* broken-orders-mark-2.json in Resources */, 3158FE6C26129D2E00E566B9 /* wcpay-account-rejected-terms-of-service.json in Resources */, + E16C59B928F927CA007D55BB /* iap-order-create.json in Resources */, 31054728262E2FEE00C5C02B /* wcpay-payment-intent-canceled.json in Resources */, 31A451CC27863A2E00FE81AA /* stripe-account-rejected-fraud.json in Resources */, 31A451D827863A2E00FE81AA /* stripe-account-restricted-overdue.json in Resources */, @@ -2710,6 +2738,7 @@ 31054714262E2F3B00C5C02B /* wcpay-payment-intent-requires-payment-method.json in Resources */, 2683D71024456EE4002A1589 /* categories-extra.json in Resources */, A69FE19D2588D70E0059A96B /* order-with-deleted-refunds.json in Resources */, + E18152C228F85E0A0011A0EC /* iap-products.json in Resources */, DE2095C127966EC800171F1C /* coupon-reports.json in Resources */, 453305F52459ED2700264E50 /* site-post-update.json in Resources */, CECC759E23D6231A00486676 /* order-560-all-refunds.json in Resources */, @@ -2739,6 +2768,7 @@ CC9A254626442CA7005DE56E /* shipping-label-eligibility-failure.json in Resources */, 4524CD9C242CEFAB00B2F20A /* product-on-sale-with-empty-sale-price.json in Resources */, 020220E223966CD900290165 /* product-shipping-classes-load-one.json in Resources */, + E16C59B728F92782007D55BB /* iap-sample-receipt.json in Resources */, 3158FE7826129DF300E566B9 /* wcpay-account-restricted.json in Resources */, 45A4B85625D2E75300776FB4 /* shipping-label-address-validation-success.json in Resources */, 457FC68C2382B2FD00B41B02 /* product-update.json in Resources */, @@ -3055,10 +3085,12 @@ 2685C0FE263B5D8900D9EE97 /* AddOnGroupRemote.swift in Sources */, 450106912399B2C800E24722 /* TaxClassListMapper.swift in Sources */, B963A5CC2853870000EFADA0 /* OrderItemRefundMetaData.swift in Sources */, + E18152BE28F85B5B0011A0EC /* InAppPurchasesRemote.swift in Sources */, 0329CF9B27A82E19008AFF91 /* WCPayCharge.swift in Sources */, 74749B97224134FF005C4CF2 /* ProductMapper.swift in Sources */, 26455E2A25F669F0008A1D32 /* ProductAttributeTermMapper.swift in Sources */, 0359EA1127AAC6740048DE2D /* WCPayPaymentMethodType.swift in Sources */, + E16C59B528F8640B007D55BB /* InAppPurchaseOrderResultMapper.swift in Sources */, 026CF61A237D607A009563D4 /* ProductVariationAttribute.swift in Sources */, D8FBFF1A22D4DF7A006E3336 /* OrderStatsV4.swift in Sources */, 74A1D26B21189B8100931DFA /* SiteVisitStatsItem.swift in Sources */, @@ -3141,6 +3173,7 @@ CE0A0F1B223989670075ED8D /* ProductsRemote.swift in Sources */, 0359EA1527AAC7460048DE2D /* WCPayCardBrand.swift in Sources */, 2665032E261F4FBF0079A159 /* ProductAddOnOption.swift in Sources */, + E18152C028F85D4A0011A0EC /* InAppPurchasesProductMapper.swift in Sources */, 028296F7237D588700E84012 /* ProductVariation.swift in Sources */, DEC51AEF2768A628009F3DF4 /* SystemStatus+Database.swift in Sources */, ); @@ -3158,6 +3191,7 @@ 45B204BA24890A8C00FE6526 /* ProductCategoryMapperTests.swift in Sources */, 74749B9522413119005C4CF2 /* ProductsRemoteTests.swift in Sources */, B524194321AC622500D6FC0A /* DotcomDeviceMapperTests.swift in Sources */, + E1A5C27228F93ED900081046 /* InAppPurchaseOrderResultMapperTests.swift in Sources */, 45150AA026837357006922EA /* CountryListMapperTests.swift in Sources */, 74D5BECE217E0F98007B0348 /* SiteSettingsRemoteTests.swift in Sources */, D8FBFF1C22D51C34006E3336 /* OrderStatsRemoteV4Tests.swift in Sources */, @@ -3221,6 +3255,7 @@ 2670C3FC270F4E06002FE931 /* SiteListMapperTests.swift in Sources */, 025CA2C6238F4F3500B05C81 /* ProductShippingClassRemoteTests.swift in Sources */, D800DA0A25EFEB9C001E13CE /* WCPayRemoteTests.swift in Sources */, + E13BAD5328F8625600217769 /* InAppPurchasesRemoteTests.swift in Sources */, CC851D1425E52AB500249E9C /* Decimal+ExtensionsTests.swift in Sources */, B554FA8B2180B1D500C54DFF /* NotificationsRemoteTests.swift in Sources */, B518662A20A09C6F00037A38 /* OrdersRemoteTests.swift in Sources */, @@ -3267,6 +3302,7 @@ 740211E121939908002248DA /* CommentRemoteTests.swift in Sources */, B57B1E6A21C925280046E764 /* DotcomValidatorTests.swift in Sources */, B567AF3020A0FB8F00AB6C62 /* DotcomRequestTests.swift in Sources */, + E18152C428F85E5C0011A0EC /* InAppPurchasesProductsMapperTests.swift in Sources */, D8A2849A25FBB2E70019A84B /* ProductAttributeTermListMapperTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Networking/Networking/Mapper/InAppPurchaseOrderResultMapper.swift b/Networking/Networking/Mapper/InAppPurchaseOrderResultMapper.swift new file mode 100644 index 00000000000..9f55e4e7f70 --- /dev/null +++ b/Networking/Networking/Mapper/InAppPurchaseOrderResultMapper.swift @@ -0,0 +1,28 @@ +import Foundation + + +/// Mapper: IAP Order Creation Result +/// +struct InAppPurchaseOrderResultMapper: Mapper { + + /// (Attempts) to extract the order ID from a given JSON Encoded response. + /// + func map(response: Data) throws -> Int { + + let dictionary = try JSONDecoder().decode([String: AnyDecodable].self, from: response) + guard let orderId = (dictionary[Constants.orderIdKey]?.value as? Int) else { + throw Error.parseError + } + return orderId + } +} + +private extension InAppPurchaseOrderResultMapper { + enum Constants { + static let orderIdKey: String = "order_id" + } + + enum Error: Swift.Error { + case parseError + } +} diff --git a/Networking/Networking/Mapper/InAppPurchasesProductMapper.swift b/Networking/Networking/Mapper/InAppPurchasesProductMapper.swift new file mode 100644 index 00000000000..716b83200ad --- /dev/null +++ b/Networking/Networking/Mapper/InAppPurchasesProductMapper.swift @@ -0,0 +1,13 @@ +import Foundation + +/// Mapper: IAP Product +/// +struct InAppPurchasesProductMapper: Mapper { + /// (Attempts) to convert a dictionary into a list of product identifiers. + /// + func map(response: Data) throws -> [String] { + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.Defaults.dateTimeFormatter) + return try decoder.decode([String].self, from: response) + } +} diff --git a/Networking/Networking/Remote/InAppPurchasesRemote.swift b/Networking/Networking/Remote/InAppPurchasesRemote.swift new file mode 100644 index 00000000000..9615bc386ba --- /dev/null +++ b/Networking/Networking/Remote/InAppPurchasesRemote.swift @@ -0,0 +1,119 @@ +import Alamofire +import Foundation + +/// In-app Purchases Endpoints +/// +public class InAppPurchasesRemote: Remote { + /// Retrieves a list of product identifiers available for purchase + /// + /// - Parameters: + /// - completion: Closure to be executed upon completion + /// + public func loadProducts(completion: @escaping (Swift.Result<[String], Error>) -> Void) { + let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .get, path: Constants.productsPath) + let request = augmentedRequestWithAppId(dotComRequest) + let mapper = InAppPurchasesProductMapper() + enqueue(request, mapper: mapper, completion: completion) + } + + /// Creates a new order for a new In-app Purchase + /// - Parameters: + /// - siteID: Site the purchase is for + /// - price: An integer representation of the price in cents (5.99 -> 599) + /// - productIdentifier: The IAP sku for the product + /// - appStoreCountryCode: The country of the user's App Store + /// - receiptData: The transaction receipt from Apple + /// - completion: Closure to be executed upon completion + /// + public func createOrder( + for siteID: Int64, + price: Int, + productIdentifier: String, + appStoreCountryCode: String, + receiptData: Data, + completion: @escaping (Swift.Result) -> Void) { + let parameters: [String: Any] = [ + Constants.siteIDKey: siteID, + Constants.priceKey: price, + Constants.productIDKey: productIdentifier, + Constants.appStoreCountryCodeKey: appStoreCountryCode, + Constants.receiptDataKey: receiptData.base64EncodedString() + ] + let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .post, path: Constants.ordersPath, parameters: parameters) + let request = augmentedRequestWithAppId(dotComRequest) + let mapper = InAppPurchaseOrderResultMapper() + enqueue(request, mapper: mapper, completion: completion) + } +} + +// MARK: - Async methods + +public extension InAppPurchasesRemote { + /// Retrieves a list of product identifiers available for purchase + /// + /// - Returns: a list of product identifiers. + /// + func loadProducts() async throws -> [String] { + try await withCheckedThrowingContinuation { continuation in + loadProducts { result in + continuation.resume(with: result) + } + } + } + + /// Creates a new order for a new In-app Purchase + /// - Parameters: + /// - siteID: Site the purchase is for + /// - price: An integer representation of the price in cents (5.99 -> 599) + /// - productIdentifier: The IAP sku for the product + /// - appStoreCountryCode: The country of the user's App Store + /// - receiptData: The transaction receipt from Apple + /// + /// - Returns: The ID of the created order. + /// + func createOrder( + for siteID: Int64, + price: Int, + productIdentifier: String, + appStoreCountryCode: String, + receiptData: Data + ) async throws -> Int { + try await withCheckedThrowingContinuation { continuation in + createOrder( + for: siteID, + price: price, + productIdentifier: productIdentifier, + appStoreCountryCode: appStoreCountryCode, + receiptData: receiptData + ) { result in + continuation.resume(with: result) + } + } + } +} + +private extension InAppPurchasesRemote { + func augmentedRequestWithAppId(_ request: URLRequestConvertible) -> URLRequestConvertible { + guard let bundleIdentifier = Bundle.main.bundleIdentifier, + var augmented = try? request.asURLRequest() else { + return request + } + + augmented.setValue(bundleIdentifier, forHTTPHeaderField: "X-APP-ID") + + return augmented + } +} + +private extension InAppPurchasesRemote { + enum Constants { + static let productsPath = "iap/products" + static let ordersPath = "iap/orders" + + static let siteIDKey = "site_id" + static let priceKey = "price" + static let productIDKey = "product_id" + static let appStoreCountryCodeKey = "appstore_country" + static let receiptDataKey = "apple_receipt" + } +} diff --git a/Networking/NetworkingTests/Mapper/InAppPurchaseOrderResultMapperTests.swift b/Networking/NetworkingTests/Mapper/InAppPurchaseOrderResultMapperTests.swift new file mode 100644 index 00000000000..794c421887b --- /dev/null +++ b/Networking/NetworkingTests/Mapper/InAppPurchaseOrderResultMapperTests.swift @@ -0,0 +1,16 @@ +import XCTest +@testable import Networking + +final class InAppPurchasesOrderResultMapperTests: XCTestCase { + func test_iap_order_creation_is_decoded_from_json_response() throws { + // Given + let jsonData = try XCTUnwrap(Loader.contentsOf("iap-order-create")) + let expectedOrderId = 12345 + + // When + let orderId = try InAppPurchaseOrderResultMapper().map(response: jsonData) + + // Then + assertEqual(expectedOrderId, orderId) + } +} diff --git a/Networking/NetworkingTests/Mapper/InAppPurchasesProductsMapperTests.swift b/Networking/NetworkingTests/Mapper/InAppPurchasesProductsMapperTests.swift new file mode 100644 index 00000000000..b1ae97d65a7 --- /dev/null +++ b/Networking/NetworkingTests/Mapper/InAppPurchasesProductsMapperTests.swift @@ -0,0 +1,18 @@ +import XCTest +@testable import Networking + +final class InAppPurchasesProductsMapperTests: XCTestCase { + func test_iap_products_list_is_decoded_from_json_response() throws { + // Given + let jsonData = try XCTUnwrap(Loader.contentsOf("iap-products")) + let expectedProductIdentifiers = [ + "woocommerce_entry_monthly", + "woocommerce_entry_yearly"] + + // When + let products = try InAppPurchasesProductMapper().map(response: jsonData) + + // Then + assertEqual(expectedProductIdentifiers, products) + } +} diff --git a/Networking/NetworkingTests/Remote/InAppPurchasesRemoteTests.swift b/Networking/NetworkingTests/Remote/InAppPurchasesRemoteTests.swift new file mode 100644 index 00000000000..06c4c16881b --- /dev/null +++ b/Networking/NetworkingTests/Remote/InAppPurchasesRemoteTests.swift @@ -0,0 +1,75 @@ +import XCTest +@testable import Networking + + +/// InAppPurchasesRemote Unit Tests +/// +class InAppPurchasesRemoteTests: XCTestCase { + + /// Dummy Network Wrapper + /// + let network = MockNetwork() + + /// Dummy Site ID + /// + let sampleSiteID: Int64 = 1234 + + /// Dummy Order ID + /// + let sampleOrderId: Int = 12345 + + /// Repeat always! + /// + override func setUp() { + network.removeAllSimulatedResponses() + } + + + /// Verifies that 'moderateComment' as spam properly parses the successful response + /// + func test_load_products_returns_list_of_products() throws { + // Given + let remote = InAppPurchasesRemote(network: network) + + network.simulateResponse(requestUrlSuffix: "iap/products", filename: "iap-products") + + // When + var result: Result<[String], Error>? + waitForExpectation { expectation in + remote.loadProducts() { aResult in + result = aResult + expectation.fulfill() + } + } + + // Then + let identifiers = try XCTUnwrap(result?.get()) + XCTAssert(identifiers.count == 2) + } + + func test_purchase_product_returns_created_order() throws { + // Given + let remote = InAppPurchasesRemote(network: network) + let receiptData = Loader.contentsOf("iap-sample-receipt")! + + network.simulateResponse(requestUrlSuffix: "iap/orders", filename: "iap-order-create") + + // When + var result: Result? + waitForExpectation { expectation in + remote.createOrder( + for: sampleSiteID, + price: 2499, + productIdentifier: "woocommerce_entry_monthly", + appStoreCountryCode: "us", + receiptData: receiptData) { aResult in + result = aResult + expectation.fulfill() + } + } + + // Then + let orderId = try XCTUnwrap(result?.get()) + XCTAssertEqual(sampleOrderId, orderId) + } +} diff --git a/Networking/NetworkingTests/Responses/iap-order-create.json b/Networking/NetworkingTests/Responses/iap-order-create.json new file mode 100644 index 00000000000..dd4f5ff0f24 --- /dev/null +++ b/Networking/NetworkingTests/Responses/iap-order-create.json @@ -0,0 +1,3 @@ +{ + "order_id": 12345 +} diff --git a/Networking/NetworkingTests/Responses/iap-products.json b/Networking/NetworkingTests/Responses/iap-products.json new file mode 100644 index 00000000000..96982014360 --- /dev/null +++ b/Networking/NetworkingTests/Responses/iap-products.json @@ -0,0 +1,4 @@ +[ + "woocommerce_entry_monthly", + "woocommerce_entry_yearly" +] diff --git a/Networking/NetworkingTests/Responses/iap-sample-receipt.json b/Networking/NetworkingTests/Responses/iap-sample-receipt.json new file mode 100644 index 00000000000..8c43a6efb9a --- /dev/null +++ b/Networking/NetworkingTests/Responses/iap-sample-receipt.json @@ -0,0 +1 @@ +{"productId":"woocommerce_entry_monthly","environment":"Xcode","quantity":1,"bundleId":"com.automattic.woocommerce","appAccountToken":"e1f15059-6f07-4f45-b520-6a5c088cb2c9","originalTransactionId":"0","isUpgraded":false,"expiresDate":1668148772496.969,"deviceVerificationNonce":"442ab6df-fc6d-4346-8671-ffbf7db1c3ce","signedDate":1665470374481.2778,"subscriptionGroupIdentifier":"00631319","purchaseDate":1665470372496.969,"type":"Auto-Renewable Subscription","transactionId":"0","webOrderLineItemId":"0","deviceVerification":"eOfePOo2T4MaA6XavHDq5zQPzArWqbeSWuWCZ1w4BgEkgqTgswWsYfUG9wBIqp0V","inAppOwnershipType":"PURCHASED","originalPurchaseDate":1665470372496.969}