Files
purchases-ios/Sources/Networking/RedeemWebPurchaseAPI.swift
Toni RicoandClaude Fable 5.1 747250d1f8 Do not apply a previous user's CustomerInfo after an identity change (#7758)
* Do not apply a previous user's CustomerInfo after an identity change

Scope CustomerInfo results to the app user ID that requested them so an
anonymous fetch or receipt post that finishes after logIn is not surfaced
to observers or cached under the identified user.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Switch the mocked current user in logIn cache-clearing tests

The mocked IdentityManager never changes the current app user ID, so
caching under the new ID is now correctly ignored unless the test
simulates the identity switch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Remove timing-dependent integration tests

They passed on main as well because the anonymous fetch is usually
enqueued before logIn, so they did not guard against the regression.
The race was verified locally by holding the anonymous fetch.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* Add integration test for anonymous CustomerInfo arriving after logIn

Blocks the head of the serial request pipeline with a stubbed offerings
request so that logIn and a fetch for the anonymous user are queued in a
known order, reproducing the race deterministically against the backend.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-17 11:03:02 +02:00

54 lines
2.0 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
//
// RedeemWebPurchaseAPI.swift
//
// Created by Antonio Rico Diez on 2024-10-17.
import Foundation
class RedeemWebPurchaseAPI {
typealias RedeemWebPurchaseResponseHandler = Backend.ResponseHandler<CustomerInfo>
private let redeemWebPurchaseResponseCallbacksCache: CallbackCache<CustomerInfoCallback>
private let backendConfig: BackendConfiguration
init(backendConfig: BackendConfiguration) {
self.backendConfig = backendConfig
self.redeemWebPurchaseResponseCallbacksCache = .init()
}
func postRedeemWebPurchase(appUserID: String,
redemptionToken: String,
completion: @escaping RedeemWebPurchaseResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let factory = PostRedeemWebPurchaseOperation.createFactory(
configuration: config,
postData: .init(appUserID: appUserID, redemptionToken: redemptionToken),
customerInfoCallbackCache: self.redeemWebPurchaseResponseCallbacksCache
)
let callback = CustomerInfoCallback(cacheKey: factory.cacheKey,
appUserID: appUserID,
source: PostRedeemWebPurchaseOperation.self,
completion: completion)
let cacheStatus = self.redeemWebPurchaseResponseCallbacksCache.add(callback)
self.backendConfig.addCacheableOperation(
with: factory,
delay: .none,
cacheStatus: cacheStatus
)
}
}