diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-05-21 14:25:00 +0200 |
|---|---|---|
| committer | Bug Magnet <marco.nikic@mullvad.net> | 2025-05-22 14:43:26 +0200 |
| commit | ea1b212d9e254bfcf236071ef00c05f010c6e4d8 (patch) | |
| tree | e152dc9bee20d31e26c192b4134712dd986c2b57 | |
| parent | 350667e42d085281429cd92bba1c2612545c2368 (diff) | |
| download | mullvadvpn-ea1b212d9e254bfcf236071ef00c05f010c6e4d8.tar.xz mullvadvpn-ea1b212d9e254bfcf236071ef00c05f010c6e4d8.zip | |
Migrate legacy storekit payment to Mullvad API
| -rw-r--r-- | ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift | 9 | ||||
| -rw-r--r-- | ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift | 10 | ||||
| -rw-r--r-- | ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift | 79 | ||||
| -rw-r--r-- | ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift | 14 | ||||
| -rw-r--r-- | ios/MullvadRustRuntime/include/mullvad_rust_runtime.h | 28 | ||||
| -rw-r--r-- | ios/MullvadTypes/Storekit2.swift | 8 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 9 | ||||
| -rw-r--r-- | ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift | 2 | ||||
| -rw-r--r-- | ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift | 35 | ||||
| -rw-r--r-- | mullvad-api/src/lib.rs | 15 | ||||
| -rw-r--r-- | mullvad-ios/src/api_client/storekit.rs | 80 |
12 files changed, 272 insertions, 25 deletions
diff --git a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift index 218c43f195..fa0bdc0ac7 100644 --- a/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift +++ b/ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift @@ -53,6 +53,15 @@ struct APIProxyStub: APIQuerying { AnyCancellable() } + func legacyStorekitPayment( + accountNumber: String, + request: LegacyStorekitRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse> + ) -> any Cancellable { + AnyCancellable() + } + func initStorekitPayment( accountNumber: String, retryStrategy: MullvadREST.REST.RetryStrategy, diff --git a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift index e7b775124c..8a74f8f6be 100644 --- a/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift +++ b/ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift @@ -252,6 +252,16 @@ extension REST { } /// Not implemented. Use `MullvadAPIProxy` instead. + public func legacyStorekitPayment( + accountNumber: String, + request: LegacyStorekitRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse> + ) -> any MullvadTypes.Cancellable { + AnyCancellable() + } + + /// Not implemented. Use `MullvadAPIProxy` instead. public func initStorekitPayment( accountNumber: String, retryStrategy: REST.RetryStrategy, diff --git a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift index 898809ee1a..6f270bf607 100644 --- a/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift +++ b/ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift @@ -28,6 +28,13 @@ public protocol APIQuerying: Sendable { receiptString: Data ) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse> + func legacyStorekitPayment( + accountNumber: String, + request: LegacyStorekitRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.CreateApplePaymentResponse> + ) -> Cancellable + func sendProblemReport( _ body: ProblemReportRequest, retryStrategy: REST.RetryStrategy, @@ -74,7 +81,7 @@ extension REST { public func getAddressList( retryStrategy: REST.RetryStrategy, - completionHandler: @escaping @Sendable ProxyCompletionHandler<[AnyIPEndpoint]> + completionHandler: @escaping ProxyCompletionHandler<[AnyIPEndpoint]> ) -> Cancellable { let responseHandler = rustResponseHandler( decoding: [AnyIPEndpoint].self, @@ -91,7 +98,7 @@ extension REST { public func getRelays( etag: String?, retryStrategy: REST.RetryStrategy, - completionHandler: @escaping @Sendable ProxyCompletionHandler<REST.ServerRelaysCacheResponse> + completionHandler: @escaping ProxyCompletionHandler<REST.ServerRelaysCacheResponse> ) -> Cancellable { if var etag { // Enforce weak validator to account for some backend caching quirks. @@ -121,15 +128,6 @@ extension REST { ) } - public func createApplePayment( - accountNumber: String, - receiptString: Data - ) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse> { - RESTRequestExecutorStub<REST.CreateApplePaymentResponse>(success: { - .timeAdded(42, .distantFuture) - }) - } - public func sendProblemReport( _ body: ProblemReportRequest, retryStrategy: REST.RetryStrategy, @@ -151,11 +149,58 @@ extension REST { AnyCancellable() } + /// Not implemented. Use `RESTAPIProxy` instead. + public func createApplePayment( + accountNumber: String, + receiptString: Data + ) -> any RESTRequestExecutor<REST.CreateApplePaymentResponse> { + RESTRequestExecutorStub<REST.CreateApplePaymentResponse>(success: { + .timeAdded(0, .now) + }) + } + + public func legacyStorekitPayment( + accountNumber: String, + request: LegacyStorekitRequest, + retryStrategy: REST.RetryStrategy, + completionHandler: @escaping ProxyCompletionHandler<REST.CreateApplePaymentResponse> + ) -> Cancellable { + let responseHandler: REST.RustResponseHandler<REST.CreateApplePaymentResponse> = + rustCustomResponseHandler { [weak self] data, _ in + guard let serverResponse = try? self?.responseDecoder.decode( + CreateApplePaymentRawResponse.self, + from: data + ) else { + return nil + } + + return if serverResponse.timeAdded > 0 { + .timeAdded( + serverResponse.timeAdded, + serverResponse.newExpiry + ) + } else { + .noTimeAdded(serverResponse.newExpiry) + } + } + + return createNetworkOperation( + request: + .legacyStorekitPayment( + retryStrategy: retryStrategy, + accountNumber: accountNumber, + request: request + ), + responseHandler: responseHandler, + completionHandler: completionHandler + ) + } + public func initStorekitPayment( accountNumber: String, retryStrategy: REST.RetryStrategy, completionHandler: @escaping ProxyCompletionHandler<String> - ) -> any MullvadTypes.Cancellable { + ) -> Cancellable { struct InitStorekitPaymentResponse: Codable { let paymentToken: String } @@ -178,7 +223,7 @@ extension REST { transaction: StorekitTransaction, retryStrategy: REST.RetryStrategy, completionHandler: @escaping ProxyCompletionHandler<Void> - ) -> any MullvadTypes.Cancellable { + ) -> Cancellable { let responseHandler = rustEmptyResponseHandler() return createNetworkOperation( @@ -221,11 +266,7 @@ extension REST { case newContent(_ etag: String?, _ rawData: Data) } - private struct CreateApplePaymentRequest: Encodable, Sendable { - let receiptString: Data - } - - public enum CreateApplePaymentResponse: Sendable { + public enum CreateApplePaymentResponse: Sendable, Decodable { case noTimeAdded(_ expiry: Date) case timeAdded(_ timeAdded: Int, _ newExpiry: Date) @@ -261,7 +302,7 @@ extension REST { } } -// TODO: Remove when "createApplePayment" func is implemented. +// TODO: Remove when Mullvad API is production ready. private struct RESTRequestExecutorStub<Success: Sendable>: RESTRequestExecutor { var success: (() -> Success)? diff --git a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift index f2ecafdbf6..5f27695672 100644 --- a/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift +++ b/ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift @@ -18,6 +18,11 @@ 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 legacyStorekitPayment( + retryStrategy: REST.RetryStrategy, + accountNumber: String, + request: LegacyStorekitRequest + ) case initStorekitPayment(retryStrategy: REST.RetryStrategy, accountNumber: String) case checkStorekitPayment( retryStrategy: REST.RetryStrategy, @@ -61,6 +66,8 @@ public enum APIRequest: Codable, Sendable { "rotate-device-key" case .createDevice: "create-device" + case .legacyStorekitPayment: + "legacy-storekit-payment" case .initStorekitPayment: "init-storekit-payment" case .checkStorekitPayment: @@ -81,6 +88,7 @@ public enum APIRequest: Codable, Sendable { let .getDevices(strategy, _), let .deleteDevice(strategy, _, _), let .rotateDeviceKey(strategy, _, _, _), + let .legacyStorekitPayment(strategy, _, _), let .initStorekitPayment(strategy, _), let .checkStorekitPayment(strategy, _, _): strategy diff --git a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift index 20b15d2971..b7e6c8412f 100644 --- a/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift +++ b/ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift @@ -114,6 +114,20 @@ public struct MullvadApiRequestFactory: Sendable { accountNumber, request.publicKey.rawValue.map { $0 } )) + case let .legacyStorekitPayment( + retryStrategy: retryStrategy, + accountNumber: accountNumber, + request: request + ): + let body = try encoder.encode(request) + return MullvadApiCancellable(handle: mullvad_ios_legacy_storekit_payment( + apiContext.context, + rawCompletionPointer, + retryStrategy.toRustStrategy(), + accountNumber, + body.map { $0 }, + UInt(body.count) + )) case let .initStorekitPayment( retryStrategy: retryStrategy, accountNumber: accountNumber diff --git a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h index ab0de2e1f3..accd302d53 100644 --- a/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h +++ b/ios/MullvadRustRuntime/include/mullvad_rust_runtime.h @@ -679,6 +679,34 @@ struct SwiftShadowsocksLoaderWrapper init_swift_shadowsocks_loader_wrapper(const * object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish` * when completion finishes (in completion.finish). * + * `retry_strategy` must have been created by a call to either of the following functions + * `mullvad_api_retry_strategy_never`, `mullvad_api_retry_strategy_constant` or `mullvad_api_retry_strategy_exponential` + * + * `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_legacy_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); + +/** + * # 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. * * `retry_strategy` must have been created by a call to either of the following functions diff --git a/ios/MullvadTypes/Storekit2.swift b/ios/MullvadTypes/Storekit2.swift index 3407b76f3c..149d82f84b 100644 --- a/ios/MullvadTypes/Storekit2.swift +++ b/ios/MullvadTypes/Storekit2.swift @@ -5,3 +5,11 @@ public struct StorekitTransaction: Codable, Sendable { self.transaction = transaction } } + +public struct LegacyStorekitRequest: Codable, Sendable { + let receiptString: Data + + public init(receiptString: Data) { + self.receiptString = receiptString + } +} diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index af02794d84..fb003e53b1 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -1109,14 +1109,13 @@ F910A43A2D4A283D002FF3BB /* InAppPurchaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A4392D4A2839002FF3BB /* InAppPurchaseViewController.swift */; }; F910A8572D523812002FF3BB /* TunnelSettingsV7.swift in Sources */ = {isa = PBXBuildFile; fileRef = F910A8562D523812002FF3BB /* TunnelSettingsV7.swift */; }; F91B94A72DC9EB5E00132C28 /* MullvadInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F91B94A62DC9EB5E00132C28 /* MullvadInfoHeaderView.swift */; }; - F924C4532D70692E001F4660 /* MullvadApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C4522D706929001F4660 /* MullvadApiTests.swift */; }; - F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C5A32DA65F28001F4660 /* Storekit2.swift */; }; - F924C65F2DAE4554001F4660 /* ServerRelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */; }; F9276C622DBA2103006FE43D /* Font+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9276C612DBA20FC006FE43D /* Font+Mullvad.swift */; }; F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; }; - F9394EED2DBF56B6009595EA /* Color+Mullvad.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEB2DBF56AA009595EA /* Color+Mullvad.swift */; }; F9394EF02DC0B58D009595EA /* MullvadListNavigationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EEF2DC0B58D009595EA /* MullvadListNavigationItemView.swift */; }; F9394EF32DC21D8C009595EA /* MullvadList.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9394EF22DC21D8C009595EA /* MullvadList.swift */; }; + F924C4532D70692E001F4660 /* MullvadApiTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C4522D706929001F4660 /* MullvadApiTests.swift */; }; + F924C5A42DA65F28001F4660 /* Storekit2.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C5A32DA65F28001F4660 /* Storekit2.swift */; }; + F924C65F2DAE4554001F4660 /* ServerRelayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F924C65E2DAE4554001F4660 /* ServerRelayTests.swift */; }; F998EFF82D359C4600D88D01 /* SKProduct+Formatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BEF24238EB300112C88 /* SKProduct+Formatting.swift */; }; F998EFFA2D3656BA00D88D01 /* SKProduct+Sorting.swift in Sources */ = {isa = PBXBuildFile; fileRef = F998EFF92D3656B100D88D01 /* SKProduct+Sorting.swift */; }; F9E3BCF72DD35B78009986C3 /* ListAccessViewModelBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E3BCF62DD35B78009986C3 /* ListAccessViewModelBridge.swift */; }; @@ -6539,7 +6538,7 @@ 586C0D832B03D2FF00E7CDD7 /* ShadowsocksSectionHandler.swift in Sources */, 58B26E262943522400D5980C /* NotificationProvider.swift in Sources */, 58CE5E64224146200008646E /* AppDelegate.swift in Sources */, - F9394EED2DBF56B6009595EA /* Color+Mullvad.swift in Sources */, + F9394EEC2DBF56B6009595EA /* Color+Mullvad.swift in Sources */, F0DA87492A9CBA9F006044F1 /* AccountDeviceRow.swift in Sources */, 58FF9FE42B075BDD00E4C97D /* EditAccessMethodItemIdentifier.swift in Sources */, 449E9A6F2D283C7400F8574A /* ButtonPanel.swift in Sources */, diff --git a/ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift b/ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift index f16ec8e46c..8c4a3d42c5 100644 --- a/ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift +++ b/ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift @@ -75,7 +75,7 @@ extension StorePaymentManagerError: DisplayError { comment: "" ) var errorString = String(format: errorFormat, reason) - errorString.append("\n\n") + errorString.append("\n") errorString.append(recoverySuggestion) return errorString diff --git a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift index 4d43b35021..c892213968 100644 --- a/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift +++ b/ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift @@ -130,6 +130,40 @@ class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse } } + #if DEBUG + private func sendReceipt(_ receiptData: Data) { + submitReceiptTask = apiProxy.legacyStorekitPayment( + accountNumber: accountNumber, + request: LegacyStorekitRequest(receiptString: receiptData), + retryStrategy: .default, + completionHandler: { result in + switch result { + case let .success(response): + self.logger.info( + """ + AppStore receipt was processed. \ + Time added: \(response.timeAdded), \ + New expiry: \(response.newExpiry.logFormatted) + """ + ) + self.finish(result: .success(response)) + + case let .failure(error): + if error.isOperationCancellationError { + self.logger.debug("Receipt submission cancelled.") + self.finish(result: .failure(error)) + } else { + self.logger.error( + error: error, + message: "Failed to send the AppStore receipt." + ) + self.finish(result: .failure(StorePaymentManagerError.sendReceipt(error))) + } + } + } + ) + } + #else private func sendReceipt(_ receiptData: Data) { submitReceiptTask = apiProxy.createApplePayment( accountNumber: accountNumber, @@ -160,6 +194,7 @@ class SendStoreReceiptOperation: ResultOperation<REST.CreateApplePaymentResponse } } } + #endif } struct StoreReceiptNotFound: LocalizedError { diff --git a/mullvad-api/src/lib.rs b/mullvad-api/src/lib.rs index d203d4dd04..079fbb80cf 100644 --- a/mullvad-api/src/lib.rs +++ b/mullvad-api/src/lib.rs @@ -604,6 +604,21 @@ impl AccountsProxy { } #[cfg(target_os = "ios")] + pub async fn legacy_storekit_payment( + &self, + account: AccountNumber, + body: Vec<u8>, + ) -> Result<rest::Response<Incoming>, rest::Error> { + let request = self + .handle + .factory + .post_json_bytes(&format!("{APP_URL_PREFIX}/create-apple-payment"), body)? + .expected_status(&[StatusCode::OK]) + .account(account)?; + self.handle.service.request(request).await + } + + #[cfg(target_os = "ios")] pub async fn init_storekit_payment( &self, account: AccountNumber, diff --git a/mullvad-ios/src/api_client/storekit.rs b/mullvad-ios/src/api_client/storekit.rs index 861cf1c906..61967eec9b 100644 --- a/mullvad-ios/src/api_client/storekit.rs +++ b/mullvad-ios/src/api_client/storekit.rs @@ -25,6 +25,86 @@ use super::{ /// object `MullvadApiCompletion`. The pointer will be freed by calling `mullvad_api_completion_finish` /// when completion finishes (in completion.finish). /// +/// `retry_strategy` must have been created by a call to either of the following functions +/// `mullvad_api_retry_strategy_never`, `mullvad_api_retry_strategy_constant` or `mullvad_api_retry_strategy_exponential` +/// +/// `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`. +#[no_mangle] +pub unsafe extern "C" fn mullvad_ios_legacy_storekit_payment( + api_context: SwiftApiContext, + completion_cookie: *mut libc::c_void, + retry_strategy: SwiftRetryStrategy, + account_number: *const c_char, + body: *const u8, + body_size: usize, +) -> SwiftCancelHandle { + let completion_handler = SwiftCompletionHandler::new(CompletionCookie::new(completion_cookie)); + + let Ok(tokio_handle) = crate::mullvad_ios_runtime() else { + completion_handler.finish(SwiftMullvadApiResponse::no_tokio_runtime()); + return SwiftCancelHandle::empty(); + }; + + let api_context = api_context.rust_context(); + // SAFETY: See SwiftRetryStrategy::into_rust. + let retry_strategy = unsafe { retry_strategy.into_rust() }; + + let completion = completion_handler.clone(); + + // SAFETY: See param documentation for `account_number`. + let account_number = unsafe { AccountNumber::from(convert_c_string(account_number)) }; + + // SAFETY: See param documentation for `body`. + let body = unsafe { std::slice::from_raw_parts(body, body_size) }.to_vec(); + let task = tokio_handle.spawn(async move { + match mullvad_ios_legacy_storekit_payment_inner( + api_context.rest_handle(), + retry_strategy, + account_number, + body, + ) + .await + { + Ok(response) => completion.finish(response), + Err(err) => { + log::error!("{err:?}"); + completion.finish(SwiftMullvadApiResponse::rest_error(err)); + } + } + }); + + RequestCancelHandle::new(task, completion_handler.clone()).into_swift() +} + +async fn mullvad_ios_legacy_storekit_payment_inner( + rest_client: MullvadRestHandle, + retry_strategy: RetryStrategy, + account_number: AccountNumber, + body: Vec<u8>, +) -> Result<SwiftMullvadApiResponse, rest::Error> { + let account_proxy = AccountsProxy::new(rest_client); + + let future_factory = + || account_proxy.legacy_storekit_payment(account_number.clone(), body.clone()); + + do_request(retry_strategy, future_factory).await +} + +/// # 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. /// /// `retry_strategy` must have been created by a call to either of the following functions |
