Files
Cesar de la VegaandRevenueCat Git Bot c516c1df39 Attribute workflow purchases to their launching checkpoint (#8037)
* feat(checkpoints): attribute purchases to launching checkpoint

* fix test

* Update baseline swiftinterface files for `cesar/wfl-734-ios-attribute-workflow-purchases-to-their-launching` (#8042)

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2026-10-16 15:37:29 +00:00

137 lines
6.2 KiB
Swift

//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// WebBillingAPI.swift
//
// Created by Antonio Pallares on 7/29/25.
import Foundation
class WebBillingAPI {
typealias WebBillingProductsResponseHandler = Backend.ResponseHandler<WebBillingProductsResponse>
typealias HostedCheckoutResponseHandler = Backend.ResponseHandler<HostedCheckoutResponse>
typealias HostedCheckoutStatusResponseHandler = Backend.ResponseHandler<HostedCheckoutStatusResponse>
private let webBillingProductsCallbackCache: CallbackCache<WebBillingProductsCallback>
private let hostedCheckoutCallbackCache: CallbackCache<HostedCheckoutCallback>
private let hostedCheckoutStatusCallbackCache: CallbackCache<HostedCheckoutStatusCallback>
private let backendLanes: BackendLanes
init(lanes: BackendLanes) {
self.backendLanes = lanes
self.webBillingProductsCallbackCache = .init()
self.hostedCheckoutCallbackCache = .init()
self.hostedCheckoutStatusCallbackCache = .init()
}
func getWebBillingProducts(
appUserID: String, productIds: Set<String>, completion: @escaping WebBillingProductsResponseHandler
) {
let backendConfig = self.backendLanes[.default]
let config = NetworkOperation.UserSpecificConfiguration(httpClient: backendConfig.httpClient,
appUserID: appUserID)
let factory = GetWebBillingProductsOperation.createFactory(
configuration: config,
webBillingProductsCallbackCache: self.webBillingProductsCallbackCache,
productIds: productIds
)
let webProductsCallback = WebBillingProductsCallback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.webBillingProductsCallbackCache.add(webProductsCallback)
backendConfig.addCacheableOperation(
with: factory,
delay: .none,
cacheStatus: cacheStatus
)
}
/// Creates a checkout session with the payment provider and returns the page to present for it.
///
/// - Parameter paywall: The paywall the checkout was started from, where it was started from one.
/// - Parameter externalPurchaseTokenID: Identifies the Apple external purchase token registered for
/// this purchase. Pass `nil` where no token applies.
/// - Parameter previousOperationSessionID: The session this customer was given before, for the backend to
/// resume instead of creating a new one where it can.
// swiftlint:disable:next function_parameter_count
func postHostedCheckout(
appUserID: String,
packageID: String,
presentedOfferingContext: PresentedOfferingContext,
paywall: PostHostedCheckoutOperation.Paywall?,
externalPurchaseTokenID: String?,
previousOperationSessionID: String?,
completion: @escaping HostedCheckoutResponseHandler
) {
// Runs on the checkout lane so hosted checkout is not delayed by unrelated backend work.
let backendConfig = self.backendLanes[.checkout]
let config = NetworkOperation.UserSpecificConfiguration(httpClient: backendConfig.httpClient,
appUserID: appUserID)
let factory = PostHostedCheckoutOperation.createFactory(
configuration: config,
postData: .init(appUserID: appUserID,
packageID: packageID,
presentedOfferingIdentifier: presentedOfferingContext.offeringIdentifier,
presentedPlacementIdentifier: presentedOfferingContext.placementIdentifier,
presentedCheckpointIdentifier: presentedOfferingContext.checkpointIdentifier,
appliedTargetingRule: presentedOfferingContext.targetingContext.map {
.init(revision: $0.revision, ruleID: $0.ruleId)
},
paywall: paywall,
externalPurchaseTokenID: externalPurchaseTokenID,
previousOperationSessionID: previousOperationSessionID),
hostedCheckoutCallbackCache: self.hostedCheckoutCallbackCache
)
let callback = HostedCheckoutCallback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.hostedCheckoutCallbackCache.add(callback)
// The customer is waiting on this request before checkout can open, so it is never delayed.
backendConfig.addCacheableOperation(
with: factory,
delay: .none,
cacheStatus: cacheStatus
)
}
/// Asks for the outcome of a checkout session.
///
/// - Parameter operationSessionID: The session to ask about, as returned by ``postHostedCheckout``.
/// Answered with a 404 where this customer does not own it.
func getHostedCheckoutStatus(
appUserID: String,
operationSessionID: String,
completion: @escaping HostedCheckoutStatusResponseHandler
) {
let backendConfig = self.backendLanes[.checkout]
let config = NetworkOperation.UserSpecificConfiguration(httpClient: backendConfig.httpClient,
appUserID: appUserID)
let factory = GetHostedCheckoutStatusOperation.createFactory(
configuration: config,
operationSessionID: operationSessionID,
callbackCache: self.hostedCheckoutStatusCallbackCache
)
let callback = HostedCheckoutStatusCallback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.hostedCheckoutStatusCallbackCache.add(callback)
backendConfig.addCacheableOperation(
with: factory,
delay: .none,
cacheStatus: cacheStatus
)
}
}
// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension WebBillingAPI: @unchecked Sendable {}