diff --git a/Networking/Networking.xcodeproj/project.pbxproj b/Networking/Networking.xcodeproj/project.pbxproj index 02acd64b376..9f3cc878a0b 100644 --- a/Networking/Networking.xcodeproj/project.pbxproj +++ b/Networking/Networking.xcodeproj/project.pbxproj @@ -531,6 +531,7 @@ B5C6FCD620A3768900A4F8E4 /* order.json in Resources */ = {isa = PBXBuildFile; fileRef = B5C6FCD520A3768900A4F8E4 /* order.json */; }; B5DAEFF02180DD5A0002356A /* NotificationsRemote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */; }; B963A5CC2853870000EFADA0 /* OrderItemRefundMetaData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B963A5CB2853870000EFADA0 /* OrderItemRefundMetaData.swift */; }; + B99CFA6B291409F100B85A3F /* SettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B99CFA6A291409F100B85A3F /* SettingsTests.swift */; }; BAB373722795A1FB00837B4A /* OrderTaxLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAB373712795A1FB00837B4A /* OrderTaxLine.swift */; }; CC07865F267799EE00BA9AC1 /* ShippingLabelPurchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC07865E267799EE00BA9AC1 /* ShippingLabelPurchase.swift */; }; CC0786612677B2DA00BA9AC1 /* ShippingLabelPurchaseMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC0786602677B2DA00BA9AC1 /* ShippingLabelPurchaseMapper.swift */; }; @@ -1267,6 +1268,7 @@ B5C6FCD520A3768900A4F8E4 /* order.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = order.json; sourceTree = ""; }; B5DAEFEF2180DD5A0002356A /* NotificationsRemote.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationsRemote.swift; sourceTree = ""; }; B963A5CB2853870000EFADA0 /* OrderItemRefundMetaData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderItemRefundMetaData.swift; sourceTree = ""; }; + B99CFA6A291409F100B85A3F /* SettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTests.swift; sourceTree = ""; }; BAB373712795A1FB00837B4A /* OrderTaxLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderTaxLine.swift; sourceTree = ""; }; BD9439D9B8F2C1ED2EADAA51 /* Pods-NetworkingTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-NetworkingTests.debug.xcconfig"; path = "../Pods/Target Support Files/Pods-NetworkingTests/Pods-NetworkingTests.debug.xcconfig"; sourceTree = ""; }; C8F9A8CC6F90A8C9B5EF2EE2 /* Pods-Networking.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Networking.release.xcconfig"; path = "../Pods/Target Support Files/Pods-Networking/Pods-Networking.release.xcconfig"; sourceTree = ""; }; @@ -1715,6 +1717,7 @@ children = ( 45551F132523E7FF007EF104 /* UserAgentTests.swift */, B518663320A0A2E800037A38 /* Constants.swift */, + B99CFA6A291409F100B85A3F /* SettingsTests.swift */, ); path = Settings; sourceTree = ""; @@ -3363,6 +3366,7 @@ 2685C102263B6A1000D9EE97 /* AddOnGroupRemoteTests.swift in Sources */, B57B1E6721C916850046E764 /* NetworkErrorTests.swift in Sources */, D8FBFF0F22D3B25E006E3336 /* WooAPIVersionTests.swift in Sources */, + B99CFA6B291409F100B85A3F /* SettingsTests.swift in Sources */, 45152831257A8E1A0076B03C /* ProductAttributeMapperTests.swift in Sources */, 26B2F74924C55ACE0065CCC8 /* LeaderboardsRemoteTests.swift in Sources */, 45CCFCE827A2E5020012E8CB /* InboxNoteListMapperTests.swift in Sources */, diff --git a/Networking/Networking/Remote/InAppPurchasesRemote.swift b/Networking/Networking/Remote/InAppPurchasesRemote.swift index 9615bc386ba..51213bf75f1 100644 --- a/Networking/Networking/Remote/InAppPurchasesRemote.swift +++ b/Networking/Networking/Remote/InAppPurchasesRemote.swift @@ -31,6 +31,7 @@ public class InAppPurchasesRemote: Remote { productIdentifier: String, appStoreCountryCode: String, receiptData: Data, + wpComSandboxUsername: String? = nil, completion: @escaping (Swift.Result) -> Void) { let parameters: [String: Any] = [ Constants.siteIDKey: siteID, @@ -39,7 +40,11 @@ public class InAppPurchasesRemote: Remote { Constants.appStoreCountryCodeKey: appStoreCountryCode, Constants.receiptDataKey: receiptData.base64EncodedString() ] - let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, method: .post, path: Constants.ordersPath, parameters: parameters) + let dotComRequest = DotcomRequest(wordpressApiVersion: .wpcomMark2, + method: .post, + path: Constants.ordersPath, + parameters: parameters, + wpComSandboxUsername: wpComSandboxUsername) let request = augmentedRequestWithAppId(dotComRequest) let mapper = InAppPurchaseOrderResultMapper() enqueue(request, mapper: mapper, completion: completion) @@ -76,7 +81,8 @@ public extension InAppPurchasesRemote { price: Int, productIdentifier: String, appStoreCountryCode: String, - receiptData: Data + receiptData: Data, + wpComSandboxUsername: String? = nil ) async throws -> Int { try await withCheckedThrowingContinuation { continuation in createOrder( @@ -84,7 +90,8 @@ public extension InAppPurchasesRemote { price: price, productIdentifier: productIdentifier, appStoreCountryCode: appStoreCountryCode, - receiptData: receiptData + receiptData: receiptData, + wpComSandboxUsername: wpComSandboxUsername ) { result in continuation.resume(with: result) } diff --git a/Networking/Networking/Requests/DotcomRequest.swift b/Networking/Networking/Requests/DotcomRequest.swift index f1a1430a16f..260e1f150dc 100644 --- a/Networking/Networking/Requests/DotcomRequest.swift +++ b/Networking/Networking/Requests/DotcomRequest.swift @@ -22,6 +22,10 @@ struct DotcomRequest: URLRequestConvertible { /// let parameters: [String: Any]? + /// WPCom Sandbox username, so the public WordPress.com API can be routed. If nil it will be ignored + /// + let wpComSandboxUsername: String? + /// Designated Initializer. /// @@ -31,17 +35,18 @@ struct DotcomRequest: URLRequestConvertible { /// - path: RPC that should be executed. /// - parameters: Collection of String parameters to be passed over to our target RPC. /// - init(wordpressApiVersion: WordPressAPIVersion, method: HTTPMethod, path: String, parameters: [String: Any]? = nil) { + init(wordpressApiVersion: WordPressAPIVersion, method: HTTPMethod, path: String, parameters: [String: Any]? = nil, wpComSandboxUsername: String? = nil) { self.wordpressApiVersion = wordpressApiVersion self.method = method self.path = path + self.wpComSandboxUsername = wpComSandboxUsername self.parameters = parameters ?? [:] } /// Returns a URLRequest instance representing the current WordPress.com Request. /// func asURLRequest() throws -> URLRequest { - let dotcomURL = URL(string: Settings.wordpressApiBaseURL + wordpressApiVersion.path + path)! + let dotcomURL = URL(string: Settings.wordpressApiBaseURL(wpComSandboxUsername: wpComSandboxUsername) + wordpressApiVersion.path + path)! let dotcomRequest = try URLRequest(url: dotcomURL, method: method, headers: nil) return try URLEncoding.default.encode(dotcomRequest, with: parameters) diff --git a/Networking/Networking/Settings/Settings.swift b/Networking/Networking/Settings/Settings.swift index 7fc26111258..ba29ce75258 100644 --- a/Networking/Networking/Settings/Settings.swift +++ b/Networking/Networking/Settings/Settings.swift @@ -7,11 +7,13 @@ public struct Settings { /// WordPress.com API Base URL /// - public static var wordpressApiBaseURL: String = { + public static func wordpressApiBaseURL(wpComSandboxUsername: String? = nil) -> String { if ProcessInfo.processInfo.arguments.contains("mocked-wpcom-api") { return "http://localhost:8282/" + } else if let wpComSandboxUsername = wpComSandboxUsername { + return "https://\(wpComSandboxUsername).dev.dfw.wordpress.com/sandboxed-api/" } return "https://public-api.wordpress.com/" - }() + } } diff --git a/Networking/NetworkingTests/Requests/DotcomRequestTests.swift b/Networking/NetworkingTests/Requests/DotcomRequestTests.swift index 35740fd39dd..0e19d3bd0d5 100644 --- a/Networking/NetworkingTests/Requests/DotcomRequestTests.swift +++ b/Networking/NetworkingTests/Requests/DotcomRequestTests.swift @@ -34,7 +34,7 @@ class DotcomRequestTests: XCTestCase { func test_request_url_contains_expected_components() { let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: sampleRPC) - let expectedURL = URL(string: Settings.wordpressApiBaseURL + request.wordpressApiVersion.path + sampleRPC)! + let expectedURL = URL(string: Settings.wordpressApiBaseURL() + request.wordpressApiVersion.path + sampleRPC)! let generatedURL = try! request.asURLRequest().url! XCTAssertEqual(expectedURL, generatedURL) } @@ -44,7 +44,7 @@ class DotcomRequestTests: XCTestCase { func test_parameters_are_serialized_as_part_of_the_url_query_when_method_is_set_to_get() { let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .get, path: sampleRPC, parameters: sampleParameters) - let expectedURL = URL(string: Settings.wordpressApiBaseURL + request.wordpressApiVersion.path + sampleRPC + sampleParametersForQuery)! + let expectedURL = URL(string: Settings.wordpressApiBaseURL() + request.wordpressApiVersion.path + sampleRPC + sampleParametersForQuery)! let generatedURL = try! request.asURLRequest().url! /// Note: Why not compare URL's directly?. As of iOS 12, URLQueryItem's serialization to string can result in swizzled entries. @@ -68,7 +68,7 @@ class DotcomRequestTests: XCTestCase { let request = DotcomRequest(wordpressApiVersion: .mark1_1, method: .post, path: sampleRPC, parameters: sampleParameters) let generatedURL = try! request.asURLRequest().url! - let expectedURL = URL(string: Settings.wordpressApiBaseURL + request.wordpressApiVersion.path + sampleRPC)! + let expectedURL = URL(string: Settings.wordpressApiBaseURL() + request.wordpressApiVersion.path + sampleRPC)! XCTAssertEqual(expectedURL, generatedURL) } diff --git a/Networking/NetworkingTests/Requests/JetpackRequestTests.swift b/Networking/NetworkingTests/Requests/JetpackRequestTests.swift index f859efcda35..1788d292a2a 100644 --- a/Networking/NetworkingTests/Requests/JetpackRequestTests.swift +++ b/Networking/NetworkingTests/Requests/JetpackRequestTests.swift @@ -25,7 +25,7 @@ final class JetpackRequestTests: XCTestCase { /// Base URL: Mapping the Sample Site + Jetpack Tunneling API /// private var jetpackEndpointBaseURL: String { - return Settings.wordpressApiBaseURL + JetpackRequest.wordpressApiVersion.path + "jetpack-blogs/" + String(sampleSiteID) + "/rest-api/" + return Settings.wordpressApiBaseURL() + JetpackRequest.wordpressApiVersion.path + "jetpack-blogs/" + String(sampleSiteID) + "/rest-api/" } diff --git a/Networking/NetworkingTests/Settings/SettingsTests.swift b/Networking/NetworkingTests/Settings/SettingsTests.swift new file mode 100644 index 00000000000..0a9f8a4ae56 --- /dev/null +++ b/Networking/NetworkingTests/Settings/SettingsTests.swift @@ -0,0 +1,11 @@ +import XCTest +@testable import Networking + +final class SettingsTests: XCTestCase { + func test_wordpressApiBaseURL_when_wpComSandboxUsername_is_passed_then_it_returns_sandboxed_url() { + let wpComSandboxUsername = "wpcomtester" + let expectedResult = "https://\(wpComSandboxUsername).dev.dfw.wordpress.com/sandboxed-api/" + + XCTAssertEqual(Settings.wordpressApiBaseURL(wpComSandboxUsername: wpComSandboxUsername), expectedResult) + } +} diff --git a/WooCommerce/Classes/Authentication/AuthenticationManager.swift b/WooCommerce/Classes/Authentication/AuthenticationManager.swift index 30103ab994e..7c02b11650a 100644 --- a/WooCommerce/Classes/Authentication/AuthenticationManager.swift +++ b/WooCommerce/Classes/Authentication/AuthenticationManager.swift @@ -61,7 +61,7 @@ class AuthenticationManager: Authentication { wpcomSecret: ApiCredentials.dotcomSecret, wpcomScheme: ApiCredentials.dotcomAuthScheme, wpcomTermsOfServiceURL: WooConstants.URLs.termsOfService.rawValue, - wpcomAPIBaseURL: Settings.wordpressApiBaseURL, + wpcomAPIBaseURL: Settings.wordpressApiBaseURL(), whatIsWPComURL: isSimplifiedLoginI1Enabled ? nil : WooConstants.URLs.whatIsWPCom.rawValue, googleLoginClientId: ApiCredentials.googleClientId, googleLoginServerClientId: ApiCredentials.googleServerId, diff --git a/WooCommerce/Classes/ViewRelated/InAppPurchases/InAppPurchasesDebugView.swift b/WooCommerce/Classes/ViewRelated/InAppPurchases/InAppPurchasesDebugView.swift index 5cf43277f30..a5d59de096d 100644 --- a/WooCommerce/Classes/ViewRelated/InAppPurchases/InAppPurchasesDebugView.swift +++ b/WooCommerce/Classes/ViewRelated/InAppPurchases/InAppPurchasesDebugView.swift @@ -19,7 +19,8 @@ struct InAppPurchasesDebugView: View { } } } - Section("Products") { + Section(header: Text("Products"), footer: Text("Be sure that you have an updated WPCOM sandbox linked to the" + + " WPCOM username you used to authenticate in the app and a session started via ssh")) { if products.isEmpty { Text("No products") } else { diff --git a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift index 7c0c8bb2258..ef606721757 100644 --- a/WooCommerce/Classes/Yosemite/AuthenticatedState.swift +++ b/WooCommerce/Classes/Yosemite/AuthenticatedState.swift @@ -37,7 +37,7 @@ class AuthenticatedState: StoresManagerState { CouponStore(dispatcher: dispatcher, storageManager: storageManager, network: network), CustomerStore(dispatcher: dispatcher, storageManager: storageManager, network: network), DataStore(dispatcher: dispatcher, storageManager: storageManager, network: network), - InAppPurchaseStore(dispatcher: dispatcher, storageManager: storageManager, network: network), + InAppPurchaseStore(dispatcher: dispatcher, storageManager: storageManager, network: network, wpComUsername: credentials.username), InboxNotesStore(dispatcher: dispatcher, storageManager: storageManager, network: network), JustInTimeMessageStore(dispatcher: dispatcher, storageManager: storageManager, network: network), MediaStore(dispatcher: dispatcher, storageManager: storageManager, network: network), diff --git a/Yosemite/Yosemite/Stores/AccountStore.swift b/Yosemite/Yosemite/Stores/AccountStore.swift index 44f732c20dd..75afc87f364 100644 --- a/Yosemite/Yosemite/Stores/AccountStore.swift +++ b/Yosemite/Yosemite/Stores/AccountStore.swift @@ -28,7 +28,7 @@ public class AccountStore: Store { let remote = AccountRemote(network: network) let dotcomAPI = WordPressComRestApi(oAuthToken: dotcomAuthToken, userAgent: UserAgent.defaultUserAgent, - baseUrlString: Settings.wordpressApiBaseURL) + baseUrlString: Settings.wordpressApiBaseURL()) let dotcomRemote = AccountSettingsRemote(wordPressComRestApi: dotcomAPI) self.init(dispatcher: dispatcher, storageManager: storageManager, network: network, remote: remote, dotcomRemote: dotcomRemote) } diff --git a/Yosemite/Yosemite/Stores/AnnouncementsStore.swift b/Yosemite/Yosemite/Stores/AnnouncementsStore.swift index dde9d2a5025..22d5b9fa88b 100644 --- a/Yosemite/Yosemite/Stores/AnnouncementsStore.swift +++ b/Yosemite/Yosemite/Stores/AnnouncementsStore.swift @@ -13,7 +13,7 @@ public protocol AnnouncementsRemoteProtocol { extension AnnouncementServiceRemote: AnnouncementsRemoteProtocol { public override convenience init() { - self.init(wordPressComRestApi: WordPressComRestApi(baseUrlString: Settings.wordpressApiBaseURL)) + self.init(wordPressComRestApi: WordPressComRestApi(baseUrlString: Settings.wordpressApiBaseURL())) } } diff --git a/Yosemite/Yosemite/Stores/InAppPurchaseStore.swift b/Yosemite/Yosemite/Stores/InAppPurchaseStore.swift index 873b0aeddc6..144f468a6fa 100644 --- a/Yosemite/Yosemite/Stores/InAppPurchaseStore.swift +++ b/Yosemite/Yosemite/Stores/InAppPurchaseStore.swift @@ -9,9 +9,11 @@ public class InAppPurchaseStore: Store { private var listenTask: Task? private let remote: InAppPurchasesRemote private var useBackend = true + private let wpComUsername: String - public override init(dispatcher: Dispatcher, storageManager: StorageManagerType, network: Network) { + public init(dispatcher: Dispatcher, storageManager: StorageManagerType, network: Network, wpComUsername: String) { remote = InAppPurchasesRemote(network: network) + self.wpComUsername = wpComUsername super.init(dispatcher: dispatcher, storageManager: storageManager, network: network) listenForTransactions() } @@ -181,6 +183,18 @@ private extension InAppPurchaseStore { let receiptData = try await getAppReceipt() logInfo("Sending transaction to API for site \(siteID)") + #if DEBUG + // In debug use WPCOM Sandbox, as we have to enable the billing system sandbox-mode + // https://taptopayp2.wordpress.com/2022/10/25/iap-testing-caveats/ + let orderID = try await remote.createOrder( + for: siteID, + price: priceInCents, + productIdentifier: product.id, + appStoreCountryCode: countryCode, + receiptData: receiptData, + wpComSandboxUsername: wpComUsername + ) + #else let orderID = try await remote.createOrder( for: siteID, price: priceInCents, @@ -188,6 +202,8 @@ private extension InAppPurchaseStore { appStoreCountryCode: countryCode, receiptData: receiptData ) + #endif + logInfo("Successfully registered purchase with Order ID \(orderID)") }