diff options
| author | Bug Magnet <marco.nikic@mullvad.net> | 2025-05-08 09:32:52 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-05-08 09:32:52 +0200 |
| commit | caf05bc110aeb3f6c0b4db9723629818e7e81d99 (patch) | |
| tree | 4a15da846efe1f5fe9bf486d2c35727b1e2d1b31 /ios | |
| parent | a74a0c0aa3270bb2cf7861f94ad7dc590ed50d22 (diff) | |
| parent | 01660f7d4af6fc539ce938f44f26bdf55337b57a (diff) | |
| download | mullvadvpn-caf05bc110aeb3f6c0b4db9723629818e7e81d99.tar.xz mullvadvpn-caf05bc110aeb3f6c0b4db9723629818e7e81d99.zip | |
Merge branch 'implement-storekit2-api-calls-using-mullvad-api-ios-1158'
Diffstat (limited to 'ios')
17 files changed, 311 insertions, 52 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift index 9880512524..218c43f195 100644 --- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift +++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift @@ -52,4 +52,21 @@ struct APIProxyStub: APIQuerying { ) -> Cancellable { AnyCancellable() } + + func initStorekitPayment( + accountNumber: String, + retryStrategy: MullvadREST.REST.RetryStrategy, + completionHandler: @escaping MullvadREST.ProxyCompletionHandler<String> + ) -> any MullvadTypes.Cancellable { + AnyCancellable() + } + + func checkStorekitPayment( + accountNumber: String, + transaction: MullvadTypes.StorekitTransaction, + retryStrategy: MullvadREST.REST.RetryStrategy, + completionHandler: @escaping MullvadREST.ProxyCompletionHandler<Void> + ) -> any MullvadTypes.Cancellable { + AnyCancellable() + } } diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift index d45745446e..e7b775124c 100644 --- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift +++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift @@ -250,6 +250,25 @@ extension REST { return executor.execute(retryStrategy: retryStrategy, completionHandler: completionHandler) } + + /// Not implemented. Use `MullvadAPIProxy` instead. + public func initStorekitPayment( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<String> + ) -> any MullvadTypes.Cancellable { + AnyCancellable() + } + + /// Not implemented. Use `MullvadAPIProxy` instead. + public func checkStorekitPayment( + accountNumber: String, + transaction: MullvadTypes.StorekitTransaction, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<Void> + ) -> any MullvadTypes.Cancellable { + AnyCancellable() + } } // MARK: - Response types diff --git a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift index 5cf3ee0e37..898809ee1a 100644 --- a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift +++ b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift @@ -40,6 +40,19 @@ public protocol APIQuerying: Sendable { retryStrategy: REST.RetryStrategy, completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.SubmitVoucherResponse> ) -> Cancellable + + func initStorekitPayment( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping @Sendable ProxyCompletionHandler<String> + ) -> Cancellable + + func checkStorekitPayment( + accountNumber: String, + transaction: StorekitTransaction, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping @Sendable ProxyCompletionHandler<Void> + ) -> Cancellable } extension REST { @@ -138,7 +151,49 @@ extension REST { AnyCancellable() } - private func createNetworkOperation<Success: Any>( + public func initStorekitPayment( + accountNumber: String, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<String> + ) -> any MullvadTypes.Cancellable { + struct InitStorekitPaymentResponse: Codable { + let paymentToken: String + } + + let responseHandler = rustResponseHandler( + decoding: InitStorekitPaymentResponse.self, + with: responseDecoder + ) + + return createNetworkOperation( + request: + .initStorekitPayment(retryStrategy: retryStrategy, accountNumber: accountNumber), + responseHandler: responseHandler, + completionHandler: { completionHandler($0.map { $0.paymentToken }) } + ) + } + + public func checkStorekitPayment( + accountNumber: String, + transaction: StorekitTransaction, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<Void> + ) -> any MullvadTypes.Cancellable { + let responseHandler = rustEmptyResponseHandler() + + return createNetworkOperation( + request: + .checkStorekitPayment( + retryStrategy: retryStrategy, + accountNumber: accountNumber, + transaction: transaction + ), + responseHandler: responseHandler, + completionHandler: completionHandler + ) + } + + private func createNetworkOperation<Success>( request: APIRequest, responseHandler: RustResponseHandler<Success>, completionHandler: @escaping @Sendable ProxyCompletionHandler<Success> diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift index ab1087fe5e..f2ecafdbf6 100644 --- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift @@ -18,6 +18,12 @@ public enum APIRequest: Codable, Sendable { case createAccount(_ retryStrategy: REST.RetryStrategy) case getAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String) case deleteAccount(_ retryStrategy: REST.RetryStrategy, accountNumber: String) + case initStorekitPayment(retryStrategy: REST.RetryStrategy, accountNumber: String) + case checkStorekitPayment( + retryStrategy: REST.RetryStrategy, + accountNumber: String, + transaction: StorekitTransaction + ) // Device Proxy case getDevice(_ retryStrategy: REST.RetryStrategy, accountNumber: String, identifier: String) @@ -55,6 +61,10 @@ public enum APIRequest: Codable, Sendable { "rotate-device-key" case .createDevice: "create-device" + case .initStorekitPayment: + "init-storekit-payment" + case .checkStorekitPayment: + "check-storekit-payment" } } @@ -70,8 +80,10 @@ public enum APIRequest: Codable, Sendable { let .getDevice(strategy, _, _), let .getDevices(strategy, _), let .deleteDevice(strategy, _, _), - let .rotateDeviceKey(strategy, _, _, _): - return strategy + let .rotateDeviceKey(strategy, _, _, _), + let .initStorekitPayment(strategy, _), + let .checkStorekitPayment(strategy, _, _): + strategy } } } diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift index 8e2ac4fad2..d204165c0d 100644 --- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequestProxy.swift @@ -45,20 +45,32 @@ public final class APIRequestProxy: APIRequestProxyProtocol, @unchecked Sendable completion(ProxyAPIResponse(data: nil, error: nil)) return } + do { + let cancellable = try transport.sendRequest(proxyRequest.request) { [weak self] response in + guard let self else { return } - let cancellable = transport.sendRequest(proxyRequest.request) { [weak self] response in - guard let self else { return } - - // Use `dispatchQueue` to guarantee thread safe access to `proxiedRequests` - dispatchQueue.async { - _ = self.removeRequest(identifier: proxyRequest.id) - completion(response) + // Use `dispatchQueue` to guarantee thread safe access to `proxiedRequests` + dispatchQueue.async { + _ = self.removeRequest(identifier: proxyRequest.id) + completion(response) + } } - } - // Cancel old task, if there's one scheduled. - let oldTask = self.addRequest(identifier: proxyRequest.id, task: cancellable) - oldTask?.cancel() + // Cancel old task, if there's one scheduled. + let oldTask = self.addRequest(identifier: proxyRequest.id, task: cancellable) + oldTask?.cancel() + } catch { + completion( + ProxyAPIResponse( + data: nil, + error: APIError( + statusCode: 0, + errorDescription: error.localizedDescription, + serverResponseCode: nil + ) + ) + ) + } } } diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift b/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift index 12d9c0346d..643e24fcdb 100644 --- a/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiNetworkOperation.swift @@ -70,28 +70,32 @@ extension REST { } let transport = transportProvider.makeTransport() - networkTask = transport?.sendRequest(request) { [weak self] response in - guard let self else { return } + do { + networkTask = try transport?.sendRequest(request) { [weak self] response in + guard let self else { return } - if let apiError = response.error { - finish(result: .failure(restError(apiError: apiError))) - return - } + if let apiError = response.error { + finish(result: .failure(restError(apiError: apiError))) + return + } - let decodedResponse = responseHandler.handleResponse(response) + let decodedResponse = responseHandler.handleResponse(response) - switch decodedResponse { - case let .success(value): - finish(result: .success(value)) - case let .decoding(block): - do { - finish(result: .success(try block())) - } catch { - finish(result: .failure(REST.Error.unhandledResponse(0, nil))) + switch decodedResponse { + case let .success(value): + finish(result: .success(value)) + case let .decoding(block): + do { + finish(result: .success(try block())) + } catch { + finish(result: .failure(REST.Error.unhandledResponse(0, nil))) + } + case let .unhandledResponse(error): + finish(result: .failure(REST.Error.unhandledResponse(0, error))) } - case let .unhandledResponse(error): - finish(result: .failure(REST.Error.unhandledResponse(0, error))) } + } catch { + finish(result: .failure(error)) } } diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift index 3f29468d9b..20b15d2971 100644 --- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift @@ -11,11 +11,14 @@ import MullvadTypes public struct MullvadApiRequestFactory: Sendable { public let apiContext: MullvadApiContext + private let encoder: JSONEncoder - public init(apiContext: MullvadApiContext) { + public init(apiContext: MullvadApiContext, encoder: JSONEncoder) { self.apiContext = apiContext + self.encoder = encoder } + // swiftlint:disable:next function_body_length public func makeRequest(_ request: APIRequest) -> REST.MullvadApiRequestHandler { { completion in let completionPointer = MullvadApiCompletion { apiResponse in @@ -111,11 +114,35 @@ public struct MullvadApiRequestFactory: Sendable { accountNumber, request.publicKey.rawValue.map { $0 } )) + case let .initStorekitPayment( + retryStrategy: retryStrategy, + accountNumber: accountNumber + ): + return MullvadApiCancellable(handle: mullvad_ios_init_storekit_payment( + apiContext.context, + rawCompletionPointer, + retryStrategy.toRustStrategy(), + accountNumber + )) + case let .checkStorekitPayment( + retryStrategy: retryStrategy, + accountNumber: accountNumber, + transaction: transaction + ): + let body = try encoder.encode(transaction) + return MullvadApiCancellable(handle: mullvad_ios_check_storekit_payment( + apiContext.context, + rawCompletionPointer, + retryStrategy.toRustStrategy(), + accountNumber, + body.map { $0 }, + UInt(body.count) + )) } } } } extension REST { - public typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) -> MullvadApiCancellable + public typealias MullvadApiRequestHandler = (((MullvadApiResponse) throws -> Void)?) throws -> MullvadApiCancellable } diff --git a/ios/MullvadREST/Transport/APITransport.swift b/ios/MullvadREST/Transport/APITransport.swift index d0bf7db48d..ed761f1479 100644 --- a/ios/MullvadREST/Transport/APITransport.swift +++ b/ios/MullvadREST/Transport/APITransport.swift @@ -12,7 +12,7 @@ import MullvadTypes public protocol APITransportProtocol { var name: String { get } - func sendRequest(_ request: APIRequest, completion: @escaping @Sendable (ProxyAPIResponse) -> Void) + func sendRequest(_ request: APIRequest, completion: @escaping @Sendable (ProxyAPIResponse) -> Void) throws -> Cancellable } @@ -30,10 +30,10 @@ public final class APITransport: APITransportProtocol { public func sendRequest( _ request: APIRequest, completion: @escaping @Sendable (ProxyAPIResponse) -> Void - ) -> Cancellable { + ) throws -> Cancellable { let apiRequest = requestFactory.makeRequest(request) - return apiRequest { response in + return try apiRequest { response in let error: APIError? = if !response.success { APIError( statusCode: Int(response.statusCode), diff --git a/ios/MullvadRESTTests/MullvadApiTests.swift b/ios/MullvadRESTTests/MullvadApiTests.swift index 0290f11e66..506c7240f8 100644 --- a/ios/MullvadRESTTests/MullvadApiTests.swift +++ b/ios/MullvadRESTTests/MullvadApiTests.swift @@ -43,7 +43,10 @@ class MullvadApiTests: XCTestCase { let proxy = REST.MullvadAPIProxy( transportProvider: APITransportProvider( - requestFactory: .init(apiContext: context) + requestFactory: .init( + apiContext: context, + encoder: JSONEncoder() + ) ), dispatchQueue: .main, responseDecoder: REST.Coding.makeJSONDecoder() diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h index fa24141e42..e053294fb8 100644 --- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h +++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h @@ -639,6 +639,50 @@ extern const void *swift_get_shadowsocks_bridges(const void *rawBridgeProvider); struct SwiftShadowsocksLoaderWrapper init_swift_shadowsocks_loader_wrapper(const void *shadowsocks_loader); /** + * # Safety + * + * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created + * by calling `mullvad_api_init_new`. + * + * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift + * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish` + * when completion finishes (in completion.finish). + * + * `account_number` must be a pointer to a null terminated string. + * + * This function is not safe to call multiple times with the same `CompletionCookie`. + */ +struct SwiftCancelHandle mullvad_ios_init_storekit_payment(struct SwiftApiContext api_context, + void *completion_cookie, + struct SwiftRetryStrategy retry_strategy, + const char *account_number); + +/** + * # Safety + * + * `api_context` must be pointing to a valid instance of `SwiftApiContext`. A `SwiftApiContext` is created + * by calling `mullvad_api_init_new`. + * + * This function takes ownership of `completion_cookie`, which must be pointing to a valid instance of Swift + * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish` + * when completion finishes (in completion.finish). + * + * `account_number` must be a pointer to a null terminated string. + * + * `body` must be a pointer to a contiguous memory segment + * + * `body_size` must be the size of the body + * + * This function is not safe to call multiple times with the same `CompletionCookie`. + */ +struct SwiftCancelHandle mullvad_ios_check_storekit_payment(struct SwiftApiContext api_context, + void *completion_cookie, + struct SwiftRetryStrategy retry_strategy, + const char *account_number, + const uint8_t *body, + uintptr_t body_size); + +/** * Initializes a valid pointer to an instance of `EncryptedDnsProxyState`. * * # Safety diff --git a/ios/MullvadTypes/Storekit2.swift b/ios/MullvadTypes/Storekit2.swift new file mode 100644 index 0000000000..3407b76f3c --- /dev/null +++ b/ios/MullvadTypes/Storekit2.swift @@ -0,0 +1,7 @@ +public struct StorekitTransaction: Codable, Sendable { + let transaction: String + + public init(transaction: String) { + self.transaction = transaction + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index 9d291a7ae5..64f83215c5 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -1109,6 +1109,7 @@ F910A4312D4A1B41002FF3BB /* InAppPurchaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4302D4A1B3B002FF3BB /* InAppPurchaseCoordinator.swift */; }; F910A43A2D4A283D002FF3BB /* InAppPurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */; }; F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */; }; + F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C5A32DA65F28001F4660 /* Storekit2.swift */; }; F924C65F2DAE4554001F4660 /* ServerRelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */; }; F924C4532D70692E001F4660 /* MullvadApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C4522D706929001F4660 /* MullvadApiTests.swift */; }; F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; }; @@ -2523,6 +2524,7 @@ F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseViewController.swift; sourceTree = "<group>"; }; F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV7.swift; sourceTree = "<group>"; }; F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerRelayTests.swift; sourceTree = "<group>"; }; + F924C5A32DA65F28001F4660 /* Storekit2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Storekit2.swift; sourceTree = "<group>"; }; F924C4522D706929001F4660 /* MullvadApiTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MullvadApiTests.swift; sourceTree = "<group>"; }; F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SKProduct+Sorting.swift"; sourceTree = "<group>"; }; /* End PBXFileReference section */ @@ -3044,6 +3046,7 @@ 581DA2722A1E227D0046ED47 /* RESTTypes.swift */, 58F1311427E0B2AB007AC5BC /* Result+Extensions.swift */, 58DFF7D92B02862E00F864E0 /* ShadowsocksCipherOptions.swift */, + F924C5A32DA65F28001F4660 /* Storekit2.swift */, F0ADF1CC2CFDFF3100299F09 /* StringConversionError.swift */, A91614D02B108D1B00F416EB /* TransportLayer.swift */, 58E511E028DDB7F100B0BCDE /* WrappingError.swift */, @@ -6700,6 +6703,7 @@ 7A307AD92A8CD8DA0017618B /* Duration.swift in Sources */, 58D2240A294C90210029F5F8 /* IPAddress+Codable.swift in Sources */, 58E45A5729F12C5100281ECF /* Result+Extensions.swift in Sources */, + F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */, A90C48672C36BC2600DCB94C /* EphemeralPeerReceiver.swift in Sources */, A9E031782ACB09930095D843 /* BackgroundTaskProvider.swift in Sources */, 58D2240B294C90210029F5F8 /* Cancellable.swift in Sources */, diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 2b8b15042d..e056368264 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -175,7 +175,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD encryptedDNSTransport: encryptedDNSTransport ) - let apiRequestFactory = MullvadApiRequestFactory(apiContext: apiContext) + let apiRequestFactory = MullvadApiRequestFactory( + apiContext: apiContext, + encoder: REST.Coding.makeJSONEncoder() + ) let apiTransportProvider = APITransportProvider(requestFactory: apiRequestFactory) apiTransportMonitor = APITransportMonitor( diff --git a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift index 8a6f44df71..c11899906a 100644 --- a/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift +++ b/ios/MullvadVPN/View controllers/Account/AccountInteractor.swift @@ -49,10 +49,35 @@ final class AccountInteractor: Sendable { await tunnelManager.unsetAccount() } - func sendStoreKitReceipt(_ transaction: VerificationResult<Transaction>, for accountNumber: String) async throws { - _ = try await apiProxy.createApplePayment( - accountNumber: accountNumber, - receiptString: transaction.jwsRepresentation.data(using: .utf8)! - ).execute() + // This function is for testing only + func getPaymentToken(for accountNumber: String) async -> Result<String, Error> { + await withCheckedContinuation { continuation in + _ = apiProxy + .initStorekitPayment( + accountNumber: accountNumber, + retryStrategy: .noRetry, + completionHandler: { result in + continuation.resume(returning: result) + } + ) + } + } + + // This function is for testing only + func sendStoreKitReceipt( + _ transaction: VerificationResult<Transaction>, + for accountNumber: String + ) async -> Result<Void, Error> { + await withCheckedContinuation { c in + _ = apiProxy + .checkStorekitPayment( + accountNumber: accountNumber, + transaction: StorekitTransaction(transaction: transaction.jwsRepresentation), + retryStrategy: .noRetry, + completionHandler: { result in + c.resume(returning: result) + } + ) + } } } diff --git a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift index 6f362e96db..eb3d2acd3b 100644 --- a/ios/MullvadVPN/View controllers/Account/AccountViewController.swift +++ b/ios/MullvadVPN/View controllers/Account/AccountViewController.swift @@ -225,6 +225,7 @@ class AccountViewController: UIViewController, @unchecked Sendable { actionHandler?(.showRestorePurchases) } + // This function is for testing only @objc private func handleStoreKit2Purchase() { guard let accountData = interactor.deviceState.accountData else { return @@ -234,13 +235,30 @@ class AccountViewController: UIViewController, @unchecked Sendable { Task { do { - let product = try await Product.products(for: [storeKit2TestProduct]).first! - let result = try await product.purchase() + let product = try await Product.products( + for: [ + storeKit2TestProduct, + ] + ).first! + let token = switch await interactor + .getPaymentToken(for: accountData.number) { + case let .success(token): + UUID(uuidString: token)! + case let .failure(error): + throw error + } + + let result = try await product.purchase( + options: [.appAccountToken(token)] + ) switch result { case let .success(verification): let transaction = try checkVerified(verification) - await sendReceiptToAPI(accountNumber: accountData.identifier, receipt: verification) + await sendReceiptToAPI( + accountNumber: accountData.number, + receipt: verification + ) await transaction.finish() case .userCancelled: print("User cancelled the purchase") @@ -303,10 +321,10 @@ class AccountViewController: UIViewController, @unchecked Sendable { } private func sendReceiptToAPI(accountNumber: String, receipt: VerificationResult<Transaction>) async { - do { - try await interactor.sendStoreKitReceipt(receipt, for: accountNumber) + switch await interactor.sendStoreKitReceipt(receipt, for: accountNumber) { + case .success: print("Receipt sent successfully") - } catch { + case let .failure(error): print("Error sending receipt: \(error)") errorPresenter.showAlertForStoreKitError(error, context: .purchase) } diff --git a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift index 391e637e23..3119af256f 100644 --- a/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift +++ b/ios/MullvadVPNTests/MullvadVPN/TunnelManager/TunnelManagerTests.swift @@ -156,7 +156,10 @@ class TunnelManagerTests: XCTestCase { relaySelector: relaySelector, transportProvider: transportProvider, apiTransportProvider: APITransportProvider( - requestFactory: MullvadApiRequestFactory(apiContext: apiContext) + requestFactory: MullvadApiRequestFactory( + apiContext: apiContext, + encoder: REST.Coding.makeJSONEncoder() + ) ) ) SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost @@ -227,7 +230,10 @@ class TunnelManagerTests: XCTestCase { relaySelector: relaySelector, transportProvider: transportProvider, apiTransportProvider: APITransportProvider( - requestFactory: MullvadApiRequestFactory(apiContext: apiContext) + requestFactory: MullvadApiRequestFactory( + apiContext: apiContext, + encoder: REST.Coding.makeJSONEncoder() + ) ) ) SimulatorTunnelProvider.shared.delegate = simulatorTunnelProviderHost diff --git a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift index 90f1aa8d69..3f2df2cbdb 100644 --- a/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift +++ b/ios/PacketTunnel/PacketTunnelProvider/PacketTunnelProvider.swift @@ -68,7 +68,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { ) let apiTransportProvider = APITransportProvider( - requestFactory: MullvadApiRequestFactory(apiContext: apiContext) + requestFactory: MullvadApiRequestFactory( + apiContext: apiContext, + encoder: REST.Coding.makeJSONEncoder() + ) ) adapter = WgAdapter(packetTunnelProvider: self) |
