summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-05-21 14:25:00 +0200
committerBug Magnet <marco.nikic@mullvad.net>2025-05-22 14:43:26 +0200
commitea1b212d9e254bfcf236071ef00c05f010c6e4d8 (patch)
treee152dc9bee20d31e26c192b4134712dd986c2b57
parent350667e42d085281429cd92bba1c2612545c2368 (diff)
downloadmullvadvpn-ea1b212d9e254bfcf236071ef00c05f010c6e4d8.tar.xz
mullvadvpn-ea1b212d9e254bfcf236071ef00c05f010c6e4d8.zip
Migrate legacy storekit payment to Mullvad API
-rw-r--r--ios/MullvadMockData/MullvadREST/APIProxy+Stubs.swift9
-rw-r--r--ios/MullvadREST/ApiHandlers/RESTAPIProxy.swift10
-rw-r--r--ios/MullvadREST/MullvadAPI/APIHandlers/MullvadAPIProxy.swift79
-rw-r--r--ios/MullvadREST/MullvadAPI/APIRequest/APIRequest.swift8
-rw-r--r--ios/MullvadREST/MullvadAPI/MullvadApiRequestFactory.swift14
-rw-r--r--ios/MullvadRustRuntime/include/mullvad_rust_runtime.h28
-rw-r--r--ios/MullvadTypes/Storekit2.swift8
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj9
-rw-r--r--ios/MullvadVPN/Extensions/StorePaymentManagerError+Display.swift2
-rw-r--r--ios/MullvadVPN/StorePaymentManager/SendStoreReceiptOperation.swift35
-rw-r--r--mullvad-api/src/lib.rs15
-rw-r--r--mullvad-ios/src/api_client/storekit.rs80
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