* Rename internal AdMobSSV symbols, URL and metric to RewardVerification
Renames the internal AdMobSSV* types and SPI introduced in #6641 to
RewardVerification* so the wording stays adapter-agnostic. Also renames
the backend URL path and the client-side metric to match, so the SPI is
provider-agnostic end to end.
Renames:
- Sources/Ads/AdMobSSV/ -> Sources/Ads/RewardVerification/
- AdMobSSVPollStatus -> RewardVerificationPollStatus
- RewardVerificationPollStatus.validated -> .verified
- AdMobSSVStatusResponse -> RewardVerificationStatusResponse
- RewardVerificationStatusResponse.Status.validated -> .verified
(the decoder still accepts the legacy "validated" wire value during
the rollout window; covered by a new decoder unit test)
- AdMobSSVStatusCallback -> RewardVerificationStatusCallback
- GetAdMobSSVStatusOperation -> GetRewardVerificationStatusOperation
- AdsAPI.getAdMobSSVStatus -> getRewardVerificationStatus
- Purchases.pollAdMobSSVStatus -> pollRewardVerificationStatus
- HTTPRequest.Path.adMobSSVStatus -> .rewardVerificationStatus
- URL path: /ads/admob/ssv/{tx} -> /ads/reward_verifications/{tx}
- Metric name: get_admob_ssv_status -> get_reward_verification_status
- BackendErrorStrings.unknown_admob_ssv_status -> .unknown_reward_verification_status
- Test files, test methods and snapshot artifacts renamed to match.
- Xcode project file references updated in lockstep.
* Drop legacy `validated` wire-value compat in RewardVerification decoder
The `RewardVerificationStatusResponse` decoder was accepting both `"verified"`
and the legacy `"validated"` status from the backend as a backwards-compat
hedge during rollout. The endpoint has not been shipped and the SDK has no
public entry point that polls it yet, so there is no compatibility window to
preserve — the decoder can require `"verified"` outright.
While here, simplify the decoder to look up known cases via
`Status(rawValue:)` instead of a string-literal switch, so adding a future
case can't silently drift from the enum.
Removes the corresponding `testDecodesLegacyValidatedWireValueAsVerified`
test. Existing coverage for `verified`, `pending`, `failed`, and the
`unknown` fallback (with the `Logger.warn` assertion) is unchanged.
* Update Sources/Purchasing/Purchases/Purchases.swift
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
* test: assert warning is logged when decoding literal "unknown" status
Address review feedback: cover the case where the backend sends the
literal "unknown" wire value, which still falls through the
unmapped-status branch and should emit a warning.
---------
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
* Add workflows network layer for multipage paywalls
- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components
iOS equivalent of https://github.com/RevenueCat/purchases-android/pull/3300
Made-with: Cursor
* fix xcodeproj
* reset Package.resolved
* Update WorkflowStep models to match actual backend response
- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)
Made-with: Cursor
* Update WorkflowResponseTests to match updated models
- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload
Made-with: Cursor
* Disable file_length lint rule in HTTPRequestPath.swift
Made-with: Cursor
* Fix GetWorkflowOperation to compute result once before distributing to callbacks
CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.
Made-with: Cursor
* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly
- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown
Made-with: Cursor
* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation
- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await
Made-with: Cursor
* Make CDN fetcher and processor completion-handler based for consistency
Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.
- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern
Made-with: Cursor
* Fix ambiguous cache key delimiter in GetWorkflowOperation
Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.
Made-with: Cursor
* PR comments
* remove cdn fetcher
* fix response in BackendGetWorkflowsTests.swift
* fix WorkflowResponseTests
* fix error
* [skip ci] Generating new test snapshots (#6584)
* [skip ci] Generating new test snapshots (#6585)
* [skip ci] Generating new test snapshots (#6586)
* [skip ci] Generating new test snapshots (#6587)
* [skip ci] Generating new test snapshots (#6588)
* [skip ci] Generating new test snapshots (#6589)
* [skip ci] Generating new test snapshots (#6590)
* Test CDN mock is not re-assignable per test
* [skip ci] Generating new test snapshots (#6597)
* [skip ci] Generating new test snapshots (#6598)
* [skip ci] Generating new test snapshots (#6599)
* [skip ci] Generating new test snapshots (#6600)
* [skip ci] Generating new test snapshots (#6601)
* [skip ci] Generating new test snapshots (#6602)
* [skip ci] Generating new test snapshots (#6603)
* getWorkflow is signed
* add type parameter
* add response verification for CDN response
* remove workflows list
* add value to WorkflowTriggerAction
* step id
* hash and filerepo
* missingCdnHash
* linter and project
* fix project
* revert Package.resolved
* @unchecked Sendable
* use hash in generateOrGetCachedFileURL
* fix compilation
* skip responseVerificationMode in cdn
* change basePath of caches
* Add workflow-based paywall resolution (iOS parity with Android #3350)
Implements the workflows endpoint so paywalls can be resolved from a
workflow document rather than the offerings cache, gated behind the
ENABLE_WORKFLOWS_ENDPOINT compile flag. Validates the step→screen→
offeringId chain and maps the workflow screen into Offering.PaywallComponents
using the existing V2 rendering path.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add tests for workflow networking layer and WorkflowScreenMapper
BackendGetWorkflowTests covers HTTP call, request caching, per-user and
per-workflow-ID deduplication, error propagation, empty appUserID, and
full response decoding. WorkflowScreenMapperTests verifies all
PaywallComponentsData fields and the UIConfig are mapped correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* remove reserialization
* Use explicit type in GetWorkflowOperation.createFactory for greppability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Use explicit type in GetWorkflowOperation.createFactory for greppability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix lint errors and refactor workflow offering resolution into PaywallViewConfiguration
- Add swiftlint:disable comments for _revision identifier name in WorkflowsResponse.swift
- Add missing_docs disable in WorkflowsResponse.swift (all @_spi(Internal) types)
- Add file_length disable in Offering.swift
- Fix identifier name 'd' → 'doubleValue' in WorkflowDetailProcessorTests.swift
- Move workflow offering resolution from PaywallView into PaywallViewConfiguration.resolveOfferingOrThrow()
- Add cachedInitialOffering() to PaywallViewConfiguration.Content
- Add PaywallViewConfigurationTests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests
Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Inject PaywallPurchasesType into offering resolution; remove dead code
- Add offerings(), cachedOfferings, and workflow() to PaywallPurchasesType
so PaywallViewConfiguration.Content no longer calls Purchases.shared directly
- Thread purchases through cachedInitialOffering/resolveOffering/resolveOfferingOrThrow
and all private helpers; callers pass purchaseHandler.purchasesInstance
- Expose purchasesInstance on PurchaseHandler for call sites
- Add stubs to NotConfiguredPurchases, LoadingPaywallPurchases, and MockPurchases
(MockPurchases gets offeringsBlock/workflowBlock for test injection)
- Remove dead WorkflowDetailProcessingError.missingInlineData case
- Replace manual JSONSerialization parseEnvelope with Data.asJSONDictionary()
- Collapse double #if nesting into single compound condition
- Remove WHAT doc comment from WorkflowDetailProcessor
- Refactor resolveOffering() to delegate to resolveOfferingOrThrow()
- Parallelize workflow + offerings fetches with async let
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Move offering resolution from Content to PurchaseHandler
Resolving an offering requires a purchases instance, so it belongs on
PurchaseHandler rather than as a method on the Content enum that takes
purchases as a parameter. purchasesInstance accessor removed.
Also fix MockPurchases.map() to forward offeringsBlock, workflowBlock,
and cachedOfferings to the mapped instance, and simplify
PaywallViewConfigurationTests to use MockPurchases directly instead of
configuring a real Purchases singleton.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add clarifying comment to workflow(forOfferingIdentifier:) explaining parameter naming
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix CI failures: remove duplicate switch cases and add @_spi(Internal) to test imports
- Remove duplicate .getWorkflow cases in HTTPRequestPath.swift switches
(auto-merge artifact that caused duplicate_conditions lint violations)
- Add @_spi(Internal) to @testable imports in three unit test files so they
can access WorkflowFetchResult, PublishedWorkflow, and WorkflowStep which
are now @_spi(Internal) public
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix @_spi access in WorkflowDetailProcessorTests
WorkflowResponseTests already had @_spi(Internal) @testable import RevenueCat;
WorkflowDetailProcessorTests was missing the @_spi annotation, causing
'id', 'steps', and 'initialStepId' to be inaccessible on PublishedWorkflow.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix MockWorkflowsAPI.swift missing from StoreKitUnitTests and UnitTests targets
The PBXBuildFile entries for DB7EA77F2F9764F700BCC082 and DB7EA7802F9764F700BCC082
were referenced in the StoreKitUnitTests and UnitTests source phases but never
defined, causing MockWorkflowsAPI to be missing at compile time and
MockBackend.swift to fail with 'cannot find MockWorkflowsAPI in scope'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add offering_identifier field to WorkflowScreen for parity with Android
Android PR #3350 split offering_id (internal ID) from offering_identifier
(human-readable identifier used for lookup). iOS was only decoding
offering_id and using it for the offering lookup, which would fail if the
workflow response sends the public identifier in offering_identifier.
- Add offeringIdentifier (from offering_identifier) to WorkflowScreen
- Update PurchaseHandler to prefer offeringIdentifier, falling back to offeringId
- Fix typo: workflow.screelns -> workflow.screens
- Add decode tests for both offering fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix WorkflowResponseTests: add missing padding field to stack component fixtures
Stack component decoding requires a padding field; test JSON fixtures were
missing it causing keyNotFound decode errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix WorkflowResponseTests: add missing margin field to stack component fixtures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Replace ENABLE_WORKFLOWS_ENDPOINT compile flag with runtime argument check
The -EnableWorkflowsEndpoint launch argument replaces the compile-time flag,
allowing workflow resolution to be toggled without rebuilding.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add Workflow presentation mode to PaywallsTester
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Remove offeringId shim, allow no-paywall offerings to open as workflows
- Drop WorkflowScreen.offeringId / offering_id CodingKey (Android already
uses offering_identifier exclusively — aligns both platforms)
- Simplify PurchaseHandler to use screen.offeringIdentifier directly
- No-paywall offerings in PaywallsTester now render as tappable rows
(workflow mode) instead of plain read-only text, with a context menu
restricted to the Workflow option
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Rename WorkflowFetchResult to WorkflowDataResult, extract ProcessInfo extension
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix project
---------
Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add AdMob SSV poll endpoint and `pollAdMobSSVStatus` SPI
* Trim verbose AdMob SSV transport comments.
Align AdsAPI and cache-key inline documentation with existing repository comment density while preserving behavior.
* Trim pollAdMobSSVStatus SPI documentation.
Reduce prescriptive guidance in the internal API comment while keeping the key cancellation behavior note.
* [skip ci] Generating new test snapshots (#6648)
* [skip ci] Generating new test snapshots (#6647)
* [skip ci] Generating new test snapshots (#6646)
* [skip ci] Generating new test snapshots (#6645)
* [skip ci] Generating new test snapshots (#6644)
* [skip ci] Generating new test snapshots (#6642)
* [skip ci] Generating new test snapshots (#6643)
* Trigger CI for PR 6641 checks.
Add a non-skip commit so required CircleCI statuses are reported on the current head.
* Validate non-empty client transaction ID for AdMob SSV polling.
Guard against empty client transaction IDs in the SPI and backend operation and add focused backend tests for the new error path.
* [skip ci] Generating new test snapshots (#6652)
* [skip ci] Generating new test snapshots (#6653)
* Trigger CI for PR 6641 checks.
Add a non-skip commit so required CircleCI statuses are reported on the current head.
* Enable signature verification for AdMob SSV status path.
Update HTTP request path tests to cover AdMob SSV signature and nonce behavior.
* fix: address review — move AdMob SPI extension to dedicated file
* fix: address review — fail fast when AdMob status mock is unstubbed
* fix: address review — dedupe client transaction validation in backend operation
* fix: address review — use enum for AdMob SSV poll status
* fix: address review — allow extracted Purchases extensions to access backend
* fix: address review — log unknown AdMob SSV statuses
* fix: address review — add Purchases AdMob SSV mapping tests
* fix: address review — map missing client transaction ID to configuration error
* fix: address review — keep backend private by co-locating AdMob extension
* fix: address review — remove unused raw-value conformance
* fix: address review — assert warning log for unknown AdMob status
* fix: address review — surface unknown AdMob SSV status to adapters
* fix: address review — enable eTag support for AdMob SSV status path
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* Add workflows network layer for multipage paywalls
- Add `getWorkflows` and `getWorkflow` endpoint paths
- Add `WorkflowsListResponse` and `PublishedWorkflow` response models
- Add `WorkflowDetailProcessor` to handle `inline`/`use_cdn` response actions
- Add `WorkflowCdnFetcher` protocol and `DirectWorkflowCdnFetcher` implementation
- Add `GetWorkflowsOperation` and `GetWorkflowOperation` cacheable network operations
- Add `WorkflowsAPI` facade and wire into `Backend`
- Add unit tests for all new components
iOS equivalent of https://github.com/RevenueCat/purchases-android/pull/3300
Made-with: Cursor
* fix xcodeproj
* reset Package.resolved
* Update WorkflowStep models to match actual backend response
- Add WorkflowTrigger struct (name, type, action_id, component_id)
- Add triggers, outputs, and metadata fields to WorkflowStep
- Add metadata field to PublishedWorkflow
- Remove value field from WorkflowTriggerAction (backend uses step_id only)
Made-with: Cursor
* Update WorkflowResponseTests to match updated models
- Replace value/resolvedTargetStepId assertions with stepId
- Remove testDecodeWorkflowTriggerActionValueTakesPrecedence (value field removed)
- Add testDecodeWorkflowTrigger for new WorkflowTrigger struct
- Add testDecodePublishedWorkflowWithMetadata
- Update testDecodeWorkflowStepDefaults to cover triggers, outputs, metadata
- Add testDecodeWorkflowStepMatchingActualBackendResponse with real backend payload
Made-with: Cursor
* Disable file_length lint rule in HTTPRequestPath.swift
Made-with: Cursor
* Fix GetWorkflowOperation to compute result once before distributing to callbacks
CDN fetch and JSON decoding were running once per deduplicated callback.
Compute the Result<WorkflowFetchResult, BackendError> once outside the
performOnAllItemsAndRemoveFromCache loop, matching the pattern used by
GetOfferingsOperation and other operations in the codebase.
Made-with: Cursor
* Fix CDN fetcher: use URLSession instead of Data(contentsOf:), classify errors correctly
- Replace Data(contentsOf:) with URLSession.shared.dataTask + DispatchSemaphore
in DirectWorkflowCdnFetcher; gets URLSession timeout, HTTP status validation,
and proper network stack semantics
- Add WorkflowDetailProcessingError.cdnFetchFailed typed error so CDN I/O
failures are distinguishable from envelope parsing failures
- Catch cdnFetchFailed in GetWorkflowOperation and map to NetworkError.networkError
instead of NetworkError.decoding, fixing misleading error classification
- Update WorkflowDetailProcessorTests to assert the typed error is thrown
Made-with: Cursor
* Replace semaphore CDN fetch with async/await using withCheckedThrowingContinuation
- Make WorkflowCdnFetcher.fetchCompiledWorkflowData async throws; use
withCheckedThrowingContinuation to bridge URLSession.dataTask into async,
avoiding any thread-blocking
- Make WorkflowDetailProcessor.process async throws to propagate async
- Bridge into async in GetWorkflowOperation via Task {}; completion() is
called inside the Task after CDN fetch and decoding complete
- Update WorkflowDetailProcessorTests to async throws with await
Made-with: Cursor
* Make CDN fetcher and processor completion-handler based for consistency
Avoids Task{} in GetWorkflowOperation and keeps all operations calling
completion() synchronously from within the HTTP callback, matching every
other operation in the codebase.
- WorkflowCdnFetcher.fetchCompiledWorkflowData now takes a completion handler;
DirectWorkflowCdnFetcher uses URLSession.dataTask (non-blocking, no semaphore)
- WorkflowDetailProcessor.process now takes a completion handler; inline action
completes synchronously, use_cdn fans out to the fetcher callback
- GetWorkflowOperation splits into getWorkflow/handleResponse/backendResult/
distribute helpers to stay within line-length limits
- WorkflowDetailProcessorTests updated to use waitUntilValue pattern
Made-with: Cursor
* Fix ambiguous cache key delimiter in GetWorkflowOperation
Space-separated appUserID+workflowId could collide (e.g. user 'a b' + workflow 'c'
== user 'a' + workflow 'b c'). Use newline as delimiter, matching the precedent
set by GetWebBillingProductsOperation.
Made-with: Cursor
* PR comments
* remove cdn fetcher
* fix response in BackendGetWorkflowsTests.swift
* fix WorkflowResponseTests
* fix error
* [skip ci] Generating new test snapshots (#6584)
* [skip ci] Generating new test snapshots (#6585)
* [skip ci] Generating new test snapshots (#6586)
* [skip ci] Generating new test snapshots (#6587)
* [skip ci] Generating new test snapshots (#6588)
* [skip ci] Generating new test snapshots (#6589)
* [skip ci] Generating new test snapshots (#6590)
* Test CDN mock is not re-assignable per test
* [skip ci] Generating new test snapshots (#6597)
* [skip ci] Generating new test snapshots (#6598)
* [skip ci] Generating new test snapshots (#6599)
* [skip ci] Generating new test snapshots (#6600)
* [skip ci] Generating new test snapshots (#6601)
* [skip ci] Generating new test snapshots (#6602)
* [skip ci] Generating new test snapshots (#6603)
* getWorkflow is signed
* add type parameter
* add response verification for CDN response
* remove workflows list
* add value to WorkflowTriggerAction
* step id
* hash and filerepo
* missingCdnHash
* linter and project
* fix project
* revert Package.resolved
* @unchecked Sendable
* use hash in generateOrGetCachedFileURL
* fix compilation
* skip responseVerificationMode in cdn
* change basePath of caches
* remove reserialization
* Use explicit type in GetWorkflowOperation.createFactory for greppability
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix swiftlint identifier_name violation in WorkflowDetailProcessorTests
Rename short variable `d` to `doubleValue` in two `if case .double(let d)`
patterns to satisfy the identifier_name rule requiring names >= 3 characters.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* remove default FileRepositoryType
* fix project
* fix test
* test preserves camel case
* Generating new test snapshots for `feat/workflows-network-layer` - ios-15 (#6654)
* Generating new test snapshots for `feat/workflows-network-layer` - ios-14 (#6656)
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Facundo Menzella <facumenzella@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Facundo Menzella <facumenzella@users.noreply.github.com>
Adapter modules that import the SDK with `@_spi(Internal) import RevenueCat`
(e.g. `RevenueCatAdMob`) need to read the configured public API key when
constructing payloads such as the AdMob SSV `customRewardString`.
- Store the API key on `SystemInfo` and wire it through `Purchases` init.
- Add `@_spi(Internal) public var apiKey: String` on `Purchases`.
- Add a Swift API tester case so any future signature change is caught.
- Add a unit test verifying the accessor returns the configured key.
The key is already public plaintext in the app binary, so exposing it as SPI
does not introduce any new security risk.
* Send X-Installation-Method header in all API requests
Detect at compile time which package manager was used to integrate the
SDK (SPM, CocoaPods, or prebuilt XCFramework) and send it as a new
X-Installation-Method header on every HTTP request. This helps the
backend understand how customers integrate the SDK.
Made-with: Cursor
* Add installation method assertions to installation tests
Expose installationMethod via @_spi(Internal) on Purchases and verify
the correct value in each installation test project (SPM, CocoaPods,
Carthage, Xcode direct, and XCFramework).
Made-with: Cursor
* [skip ci] Generating new test snapshots (#6433)
* [skip ci] Generating new test snapshots (#6435)
* [skip ci] Generating new test snapshots (#6434)
* [skip ci] Generating new test snapshots (#6440)
* [skip ci] Generating new test snapshots (#6436)
* [skip ci] Generating new test snapshots (#6437)
* [skip ci] Generating new test snapshots (#6438)
* [skip ci] Generating new test snapshots (#6439)
* [skip ci] Generating new test snapshots (#6443)
* Fix missing COCOAPODS_INSTALLATION flag on CocoaPods test targets
The flag was only set on the app target, not on the test targets, so
the #if COCOAPODS_INSTALLATION branches in InstallationTests.swift
were never compiled for CocoaPods tests. Add OTHER_SWIFT_FLAGS with
-DCOCOAPODS_INSTALLATION to all four CocoaPods test target configs.
Made-with: Cursor
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* Small cleanup
* add `LocalTransactionDetailsStorage`
* Add models for local transaction metadata caching. Reset PurchasesOrchestrator
* Complete `Codable`, lossless implementation of `LocalTransactionMetadata`
* Rename `LocalTransactionMetadataCache` to `LocalTransactionMetadataStore`
* Comment
* lint
* Remove unnecessary import
* Remove unused method
* Replace `Storefront` with `storeCountry` in both `ProductRequestData` and `PurchasedTransactionData`
This removes the need of a `StorefrontEncodedWrapper` object
* Disambiguate `asDictionary()` method
Now that `SubscriberAttribute` is `Codable`, `Encodable`'s `asDictionary()` and `SubscriberAttribute`'s `asDictionary()` were clashing
* Undo `Codable` conformance of `PresentedOfferingContext` and `TargetingContext`
* lint
* `LocalTransactionMetadata` now refers to a single transaction. `LocalTransactionMetadataStore` now stores one file per transaction and does not use an in-memory cache
* Temporarily remove `LocalTransactionMetadata` and `LocalTransactionMetadataStore` for easier PR review
* Revert "Temporarily remove `LocalTransactionMetadata` and `LocalTransactionMetadataStore` for easier PR review"
This reverts commit d958601711.
* Small code cleanup
* Remove `EncodedAppleReceipt` from `LocalTransactionMetadata`
* Undo `Codable` conformance of `EncodedAppleReceipt`
Not needed for now
* Remove `self.appTransactionJWS` from `LocalTransactionMetadata`
* Adapt to API changes in `SynchronizedLargeItemCache`
* Plug in `LocalTransactionMetadataStore` and store + get stored `LocalTransactionMetadata`
* Removed unused parameter
* Remove `schemaVersion` property from `LocalTransactionMetadata`
* Add `MockLocalTransactionMetadataStore` for tests
* Remove `appUserID` from `PurchasedTransactionData`
* Update tests
* Add `transaction_id` field to POST receipt
* adapt tests
* Add transaction ID parameter to cache key for `PostReceiptDataOperation`'s factory
* [skip ci] Generating new test snapshots (#6024)
* [skip ci] Generating new test snapshots (#6025)
* [skip ci] Generating new test snapshots (#6027)
* [skip ci] Generating new test snapshots (#6026)
* [skip ci] Generating new test snapshots (#6028)
* [skip ci] Generating new test snapshots (#6029)
* [skip ci] Generating new test snapshots (#6033)
* [skip ci] Generating new test snapshots (#6032)
* Fix lint
* Only send transaction ID when there's associated metadata
* Fix tests compilation
* Add unit test
* Add tests
* Add tests for `LocalTransactionMetadataStore`
* Have `PurchasesOrchestrator` post transaction with syncing purchases in SK2 go through `TransactionPoster` to include the `LocalTransactionMetadata` logic there
* Correct values for `purchaseCompletedBy` field in `PostReceiptDataOperation`
* Fix finish transaction logic
* Add more tests and fix test compilation
* More tests
* [skip ci] Generating new test snapshots (#6057)
* [skip ci] Generating new test snapshots (#6050)
* [skip ci] Generating new test snapshots (#6051)
* [skip ci] Generating new test snapshots (#6052)
* [skip ci] Generating new test snapshots (#6053)
* [skip ci] Generating new test snapshots (#6054)
* [skip ci] Generating new test snapshots (#6055)
* [skip ci] Generating new test snapshots (#6056)
* Fix lint
* Remove TODO
* Remove redundancy
* Fix tests and add test
* [skip ci] Generating new test snapshots (#6059)
* [skip ci] Generating new test snapshots (#6060)
* [skip ci] Generating new test snapshots (#6061)
* [skip ci] Generating new test snapshots (#6062)
* [skip ci] Generating new test snapshots (#6063)
* [skip ci] Generating new test snapshots (#6064)
* [skip ci] Generating new test snapshots (#6068)
* [skip ci] Generating new test snapshots (#6069)
* Add `transactionId` to `LocalTransactionMetadata`
* Add `getAllStoredMetadata()` method to `LocalTransactionMetadataStore`
* Cleanup stored metadata file if it fails to be read or decode
* Fix lint
* Simplify parameters in `handlePostReceiptResult` method in `PurchasesOrchestrator`
The same subscriber attributes were already included in the `PurchasedTransactionData` object
* Fix recovering `PresentedOfferingContext` from `LocalTransactionMetadata`
* Add `EncodedAppleReceipt` to `LocalTransactionMetadata`
* Take `source: PurchaseSource` out from `PurchasedTransactionData`
* Rename `PurchaseSource` to `PostReceiptSource` and `ProductRequestData.InitiationSource` to `PostReceiptSource.InitiationSource`
* Pass receipt as parameter directly
* Fix lint
* Remove unused variable
* Add internal interface to sync cached local transaction metadata
* Adapt unit tests
* Trigger cached transaction metadata sync on `applicationWillEnterForeground`
* fix test with previous value in snapshot
* Fix compilation of tests
* Introduce `sdk_originated` parameter in POST /receipt
* Adapt tests to new `sdkOriginated` property
* TEMP: Remove sdkOriginated field (to split into 2 PRs)
* Revert "TEMP: Remove sdkOriginated field (to split into 2 PRs)"
This reverts commit 6d361fde30.
* Remove sdkOriginated field as we won't be adding it for now
This reverts commit e4989a3d33.
* Store transaction metadata when offering context or paywall is present
* Fix PaywallEvent initialization in tests
* Use variable
* Cache CustomerInfo when receiving responses from cached transaction metadata syncs
* Add more tests
* Fix tests
* Move syncing of cached local metadata to `applicationDidBecomeActive`, which does happen on cold app launches
* Prevent concurrent cached transaction metadata syncs
* Add tests for applicationDidBecomeActive syncing cached transaction metadata
* Small improvements
* Perform sync in `applicationWillEnterForeground` + at `configure` time
* Fix tests after merge from main
* Fix and improved tests
* Fix tests
* test improvements
* Use `transactionId` in `PostData` as cacheKey only if it `containsAttributionData`
This means that POST /receipt for renewals are deduplicated, while purchases with attribution data are guaranteed to always be sent (regardless of whether there's an in-flight POST /receipt request for a renewal of that purchase)
* Adapt tests to new behavior + new tests
* Update Sources/Networking/Operations/PostReceiptDataOperation.swift
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
* Do not clear `LocalTransactionMetadata` when receiving offline-computed CustomerInfo
* Add tests
* Fix test + add test
* Fix test
* Fix lint
* Fix test compilation
* Improve comment
* Fix codable `CodingKeys` to not use snake case in rawValues As the `JSONEncoder.default` uses `convertToSnakeCase` `keyEncodingStrategy`
This allows decoding using the `JSONDecoder.default (which uses `convertFromSnakeCase` `keyDecodingStrategy` to correctly decode the JSONs resulting from encoding with `JSONEncoder.default`.
* Add tests
* Improve comment
* Add `Codable` idempotency tests for `EncodedAppleReceipt`
* Add tests for encoding/decoding `LocalTransactionMetadata`
* Have `LocalTransactionMetadataStore` use a `.persistence` `DirectoryType` instead of a `.cache` one
* Add E2E tests for `LocalTransactionMetadataStore`
* Fix test
* Add some logs and reduce some flakiness of tests
* Remove test (will be added in a future PR after a small rewrite)
* Reduce test flakiness
* Change directory name
* Perform cached transaction metadata sync without delay on tests
* Remove unnecessary helper in tests
* Fix deadlock
* Inject `applicationSupportDirectory` to `LocalTransactionMetadataStore` for more reliable tests
* Fix lint
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
* Renamed applicationSupport directory type to persistence
* Only deleting the old diagnostics file when the new diagnostics file does not exist yet
* Implemented PR feedback
* Changed testing-only check from assertion to #if DEBUG
* Implemented migration + deleting LargeItemCache files from the Documents directory for Etags + DeviceCache caching
* Fixed compile issue after rebase
* Fixed test compilation after rebase
* Updated test
* Refactored logica to the specific classes that actually store the data, removing the logic from the large item cache
* Fix compilation issue
* Moved comment
* Implemented PR feedback
* Implemented most PR feedback
* Implemented PR feedback
* Simplified ETagManager's old Documents directory removal by just doing it on init if needed
* Added a swiftlint rule that disallows direct use of storage directory URL related APIs
* Fix compilation
* Renamed applicationSupport directory type to persistence
* Only deleting the old diagnostics file when the new diagnostics file does not exist yet
* Implemented PR feedback
* Changed testing-only check from assertion to #if DEBUG
* Implemented migration + deleting LargeItemCache files from the Documents directory for Etags + DeviceCache caching
* Fixed compile issue after rebase
* Fixed test compilation after rebase
* Updated test
* Refactored logica to the specific classes that actually store the data, removing the logic from the large item cache
* Fix compilation issue
* Moved comment
* Implemented PR feedback
* Implemented most PR feedback
* Implemented PR feedback
* Simplified ETagManager's old Documents directory removal by just doing it on init if needed
* Simplified old documents directory removal in EtagManager
* Also deleting old files from documents if we're clearing caches
* Fixed swiftlint warning
* Simplified directory cleanup between tests
* Updated FileRepository to keep using the existing directory in order to avoid duplicate cache files or having to migrate them right now
* Fixed tests not compiling
* Added additional test assertions that check whether new cache directory was created
* Add locking to DeviceCache migration logic to be safe
* Handle multiple concurrent reads across threads only actually migrating the data once
* PR feedback tweak
* First pass–replace user defaults with file cache
* update tests and encoding mechanism
* Start on tests
* Wrap up tests
* clear for large item cache
* Migrate to a version of the cache that allows lookup by key instead of by index
* Mock cache is not used in this file, update it to the tailor made one just in case it's needed later
* Implemented initial HTTPRequestTimeoutManager + added first couple of tests
* Cleaned up the code and added a bunch more unit tests based on the Android test suite
* Updated doc format
* Add new files to workspace
* Added HTTPClient tests testing interaction with timeout manager
* Swiftlint fixes
* Updated comment
* Add missing file to workspace
* Fix after rebase
* Added extra HTTPClient <-> HTTPRequestTimeoutManager tests asserting on the recorded events
* Implemented PR feedback
* Fixed compile issue
* Fix compile issue
* Implemented PR feedback
* Removed deleted file reference from workspace
* Fixed test after changing reset timeout
* Make sure the default HTTPRequistTimeoutManager is using the default value from the HTTPClient
* Add a sophisticated test testing the full timeout flow against an unroutable IP for a real world scenario
* Updated timeout case name
* Fixed case where .timeoutOnMainBackendForFallbackSupportedEndpoint was also recorded for non timeouts and added tests to prevent regression
* Fixed only stubbing the HTTP response based on path, since that could be the same across hosts, resulting in flaky tests
* Implemented tracking ConnectionErrorReason using DiagnosticsTracker on HTTP errors
* Add new file to workspace
* Updated string value of ConnectionErrorReason cases to match Android implementation
* [skip ci] Generating new test snapshots (#5868)
* [skip ci] Generating new test snapshots (#5869)
* [skip ci] Generating new test snapshots (#5870)
* [skip ci] Generating new test snapshots (#5871)
* [skip ci] Generating new test snapshots (#5872)
* [skip ci] Generating new test snapshots (#5874)
* [skip ci] Generating new test snapshots (#5873)
* Updated the list of URLError.code's that we consider a network error
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* Implemented initial HTTPRequestTimeoutManager + added first couple of tests
* Cleaned up the code and added a bunch more unit tests based on the Android test suite
* Updated doc format
* Add new files to workspace
* Added HTTPClient tests testing interaction with timeout manager
* Swiftlint fixes
* Updated comment
* Add missing file to workspace
* Fix after rebase
* Added extra HTTPClient <-> HTTPRequestTimeoutManager tests asserting on the recorded events
* Implemented PR feedback
* Fixed compile issue
* Fix compile issue
* Implemented PR feedback
* Removed deleted file reference from workspace
* Fixed test after changing reset timeout
* Make sure the default HTTPRequistTimeoutManager is using the default value from the HTTPClient
* Add a sophisticated test testing the full timeout flow against an unroutable IP for a real world scenario
* Updated timeout case name
* Fixed case where .timeoutOnMainBackendForFallbackSupportedEndpoint was also recorded for non timeouts and added tests to prevent regression
* Fixed only stubbing the HTTP response based on path, since that could be the same across hosts, resulting in flaky tests
* feat: add contact support button and new sheet for ticket creation
* chore: use localisation
* feat: send support ticket details to backend
* feat: only show the support ticket creation button when config is set up
* feat: only show to correct customer type
* test: add base tests
* chore: remove unrequired var
* chore: use translations
* docs: update link to tuist
* refactor: move to cc api
* refactor: api calls
* test: update setup
* chore: better email validation
* chore: improved focus state
* chore: add dismiss
* chore: hide keyboard on dismiss
* chore: fix indentation
* feat: add charcter count
* chore: add rest of cc config
* test: clean up unit tests
* refactor: pull email validation out into struct
* chore: refine email checks
* refactor: pull out max characters into own var
* Use offerings cache on (most) network errors
* Add unit tests for `NetworkError`'s `shouldFallBackToCachedOfferings`
* Add unit tests for `shouldFallBackToCachedOfferings`
* Fix lint
* Add some more unit tests
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`
Keeps the values also when returned by the ETagManager
* lint
* Add some tests for ETagManager
* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues
* Add tests for actual source of original cached response of ETagManager
* Add tests to HTTPClientTests regarding the new booleans
* lint
* Add `originalSource` to `CustomerInfo`
* Adds `isLoadedFromCache` boolean to CustomerInfo
* Add docs
* Unit tests for new customer info properties
* Make `OriginalSource.CustomerInfo` a `String` enum
* Add decoding tests for Customer Info with new original source field
* Add `.loadShedder` check for Customer Info obtained in Load Shedder integration tests
* Update fixture
* improve internal docs
* Add intermediate object `Offerings.Contents` to wrap `OfferingsResponse` + `OriginalSource` and `loadedFromCache`
* Adapt existing unit tests to changes in `Offerings` underlying objects
* Check Offerings original source in existing integration tests
* Fix StoreKit unit tests
* Fix RevenueCatUI tests
* More Offerings.Contents tests
* Fix test
* Add more tests
* Fix lint
* [skip ci] Generating new test snapshots (#5741)
* [skip ci] Generating new test snapshots (#5742)
* [skip ci] Generating new test snapshots (#5743)
* [skip ci] Generating new test snapshots (#5744)
* [skip ci] Generating new test snapshots (#5745)
* [skip ci] Generating new test snapshots (#5746)
* [skip ci] Generating new test snapshots (#5747)
* [skip ci] Generating new test snapshots (#5748)
* Use enum for `HTTPResponseSource`
* Update tests
* Adapt to changes in `VerifiedHTTPResponse`
* Lint
* Adapt to changes in `VerifiedHTTPResponse`
* update tests
* Add log when a request to the fallback URL was handled by load shedder (which should never happen)
* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`
* Adapt to changes
* fix lint
* Adapt to changes
* Fix CustomerInfo's OriginalSource implementation + fix unit test
* Fix tests compilation
* Add Cached offerings integration test
* Fix RCUI compilation
* Move isFromCache boolean from `Offerings.Contents` to `Offerings`
* Expand `ForceServerErrorStrategy` functionality
* Improve log when faking error response
* Correct implementation of fake error response without making a request (ForceServerErrorStrategy)
* Update tests + more tests
* Fix compilation of `OfferingsManagerTests`
* Fix compilation of `DebugViewSwiftUITests`
* Improve tests
* Fix `OfflineWithNoMappingStoreKitIntegrationTests`
* FIx compilation
---------
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`
Keeps the values also when returned by the ETagManager
* lint
* Add some tests for ETagManager
* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues
* Add tests for actual source of original cached response of ETagManager
* Add tests to HTTPClientTests regarding the new booleans
* lint
* Use enum for `HTTPResponseSource`
* Update tests
* Add log when a request to the fallback URL was handled by load shedder (which should never happen)
* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`
* fix lint
* Added an internal API for overriding the API base URL
* Updated and added tests for API baseURL configuration option
* Revert change that removed serverHostURL from HTTPRequestPath since it broke other types implementing this protocol
* swiftlint
* Removed the apiBaseURL property from Purchases since it's only used for internal testing anyways
* Remove unused @_spi(Internal)
* Updated comment on api base URL vs proxy URL
* Fixed linting issue
* Rename paywallsEventsManager to eventsManager
* Rename PaywallEventsManagerType to EventsManagerType
* Rename PaywallEventsManager to EventsManager
* Rename PaywallEventStore to FeatureEventStore
* Rename InternalAPI.postPaywallEvents to postFeatureEvents
* Rename StoredEventSerializer to StoredFeatureEventSerializer
* Rename createStoredEvent to createFeatureStoredEvent
* Rename StoredEvent to StoredFeatureEvent
* Rename HTTPRequest.PaywallPath to HTTPRequest.FeatureEventsPath
* Rearrange events files
* Rename event related files
* Extract shared events endpoint configuration to EventsHTTPRequestPath
Both FeatureEventsPath and AdPath share the same /v1/events endpoint
configuration, differing only in domain (api-paywalls.revenuecat.com vs
a.revenue.cat) and name. Extract common logic to EventsHTTPRequestPath
protocol in Sources/Events/Networking.
* Move Sources/FeatureEvents to Sources/Events/FeatureEvents
* Rename EventsRequest to FeatureEventsRequest
- Rename EventsRequest.swift to FeatureEventsRequest.swift
- Move file to Sources/Events/FeatureEvents/Networking/
- Update all references in source and test files
- Update Xcode project file references
This makes it clear that this request type is specifically for the
feature events endpoint, not a generic events request.
* Remove ad events from feature events system
Ad events should not be sent through the feature events system as they
require their own separate infrastructure with a different API endpoint.
Changes:
- Remove FeatureEvent conformance from AdEvent
- Remove .ads case from Feature enum
- Remove ad events handling from FeatureEventsRequest
- Add no-op track(adEvent:) method to EventsManager with comment
- Update AdTracker to call the new track(adEvent:) method
Ad events tracking now becomes a no-op until proper implementation with:
- StoredAdEvent structure
- AdEventStore for persistence
- InternalAPI.postAdEvents() using HTTPRequest.AdPath.postEvents
* Rename PostEventsOperation to PostFeatureEventsOperation
- Rename PostEventsOperation.swift to PostFeatureEventsOperation.swift
- Move file to Sources/Events/FeatureEvents/Networking/
- Update class name and remove misleading "generic" comment
- Update references in InternalAPI.swift
- Update Xcode project file references
This makes it clear that this operation is specifically for posting
feature events, not a generic operation for various event types.
* change fallback URL logic to support a different path from the main server
* Adapt unit tests to changes in fallback urls logic
* lint
* Add unit test for fallback urls
* fix switched logic
* Add RC Ads event tracking infrastructure
Implements event tracking for RevenueCat Ads following the Paywalls Events pattern.
- Adds three event types: ad displayed, ad opened, and ad revenue
- Uses actor-based concurrency with file-based event storage
- Posts events to e.revenue.cat/v1/events endpoint
- Marks track(adEvent:) method as @_spi(Internal) for internal use
- Includes comprehensive unit tests with snapshot testing
- Available on iOS 15.0+, tvOS 15.0+, macOS 12.0+, watchOS 8.0+
Event schema follows khepri RC Ads input events specification with
app_session_id field matching backend expectations.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Avoid mapping in EventsRequest+Ad
* Consolidate ad events into PaywallEventsManager
Eliminates duplication between AdEventsManager and PaywallEventsManager by:
- Making PaywallEventsManager handle all event types (ads, paywalls, customerCenter)
- Adding feature-aware routing in flushEvents() to send events to correct domains
- Ad events → https://e.revenue.cat/v1/events
- Other events → https://api-paywalls.revenuecat.com/v1/events
- Removing AdEventsManager, AdEventStore, and their tests (~500 lines)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove AdsStrings and use PaywallsStrings instead
AdsStrings only had 2 strings in use, both of which already exist in PaywallsStrings. Consolidates logging strings to eliminate duplication.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Namespace Precision enum inside AdEvent.RevenueData
Moves Precision from a top-level public enum to AdEvent.RevenueData.Precision to avoid polluting the global namespace and reduce autocomplete noise.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Move Precision conformances to end of file
Follows the pattern of other types by declaring protocol conformances at the end of the file rather than inline with the type definition.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove redundant sessionIdentifier from AdImpressionData
The sessionIdentifier field in AdImpressionData was redundant since
StoredEvent already tracks appSessionID. This change:
- Removes sessionIdentifier from AdImpressionData struct
- Uses storedEvent.appSessionID directly when building requests
- Renames sessionId to appSessionId to match backend field app_session_id
- Updates documentation examples to remove sessionIdentifier parameter
- Adds guard statement for optional appSessionID
- Updates all test fixtures and regenerates snapshots
- Removes iOS26 snapshot references from project
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove unneeded SessionId
* Add proper assertions to PurchasesAdEventsTests
Enhanced test coverage for public ad tracking APIs:
- Verify events are tracked via mockPaywallEventsManager
- Assert correct event types (displayed, opened, revenue)
- Validate all impression data fields
- Verify revenue-specific fields (revenueMicros, currency, precision)
All 3 tests now pass with proper verification.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add ENABLE_AD_EVENTS_TRACKING compiler flag
* Simplify track ad events APIs
* Fix ENABLE_AD_EVENTS_TRACKING scope and refactor Precision to struct
Compiler flag fixes:
- Move ENABLE_AD_EVENTS_TRACKING #endif to wrap all ad event code including internal enum
- Add compiler flag guards to all three test files
Precision API refactor:
- Change AdRevenue.Precision from enum to struct following Apple's API patterns (like Message.Reason)
- Use internal init(rawValue:) to prevent custom precision values
- Convert enum cases to static properties maintaining same public API
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Refactor mediatorName from String to MediatorName struct
Replace string-based mediator names with a type-safe struct following Apple's API patterns:
- Add MediatorName struct with public init(rawValue:) to support custom values
- Include predefined static properties for .adMob and .appLovin
- Update AdEventData protocol to require MediatorName type
- Update all event types (AdDisplayed, AdOpened, AdRevenue) to use MediatorName
- Use .rawValue when serializing to network requests
- Update all tests and documentation examples to use .appLovin
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Remove RawRepresentable conformance from Precision and update test snapshots
- Remove RawRepresentable conformance from AdRevenue.Precision to allow internal init
- Update test assertions to use .appLovin instead of string comparison
- Update all snapshot test files to use "AppLovin" instead of "MAX"
- All ad event tests now passing (11/11)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix PaywallDataDecodingTests locale check for region overrides
The test was failing when the system locale included region overrides
(e.g., "en_US@rg=eszzzz" instead of just "en_US").
Changes:
- Use beginWith("en_US") instead of exact equality check
- Compare against enConfig directly instead of fetching via Locale.current
This makes the test more robust while maintaining the same behavior validation.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Rename timestampMs to timestamp but keep serialized timestamp_ms
* Use different serverHostURL
* Simplify and paralelize postEvents
* Adjust usage of ENABLE_AD_EVENTS_TRACKING flag
* Move track ad event methods to AdTracker
* Remove PaywallEventsManager implementation
* Improving naming overlapping naming AdEvent->AdEventRequest
* Simplify compiler flag usage
* Use Experimental
* Remove duplicate with PostPaywallEventsOperation and PostAdEventsOperation
* Remove postAdEvents methods assuming reuse of event storage
* Make Precision rawValue internal
* Remove runtime checks
* Trigger build
* Move Public Types MARK comment after internal protocol
* Add documentation for AdDisplayed, AdOpened, and AdRevenue properties
* Remove redundant smoke tests
The "DoesNotCrash" tests were completely redundant with the
comprehensive "StoresEvent" tests. The comprehensive tests already:
1. Verify methods don't crash (implicitly)
2. Verify events are stored
3. Verify event data is correct
Removed 3 redundant tests, keeping only the 3 comprehensive tests.
* Fix bug not tracking initialized events
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Checksum and tests
* add checksum to video component models
* Update cache warming
* put the checksum in the right spot and update the view model
* File repo updates
* add missing assertion
* revert accidental trailiing comma
* Checksum from file
* Ensure cache invalidates when the checksum changes
* Use checksum as filename if available
* greedily fill space while loading occurs
* update remote image usage of the repository
* Reduce memory footprint of large file downloads by streaming the download to a temp file
* Update keyed deferred value storage key to include checksum
* remove unused argument
* remove wip comment
* Get tests to compile
* Write tests for the large item cache type
* remove test from the file repository suite
* trailing commas
* remove cache warming of videos
* remove un-needed change causing existential types
* delete the temporary file after success or failure of the code block
* log with the checksums
* optimize checksum generation to occur at the same time as the temp file storage
* starting to work on border gradients,
* Revert "starting to work on border gradients,"
This reverts commit 429e6d2fab.
* clarity regarding dark mode urls
* Pull files from staging branch to introduce a file repository and intelligent task sharing
* removed typed throws and fix some docs
* Make tests satisfy predictive swift 6 failures
* @_spi(Internal) public
* Stop using the shared instance in an instance method
* Pull cache directory logic out of file repository
* update cache naming conventions
* @fire-at-will feedback -> use url instead of path
* Use strings file pattern
* remove priority from repository
* Remove RC Internal access from keyed deferred value store
* Remove spi internal from cache and network service
* makeSUT -> makeSystemUnderTest
* remove public scope in extensions
* rename function for clarity
* Add validation logic for Test Store API keys under the TEST_STORE compiler flag
* add unit test for test store api key validation
* Move API key validation to `configure` and store validation result in `SystemInfo`
* start new flow for purchase of `TestStoreProduct`s
* WIP: implement alert for purchase of a Test Store product
* Add file to Xcode workspace
* WIP: implement fake purchase alert in all platforms and OSs
* Enforce only one test purchases at once. Use constants for test purchase alert strings
* Place new test store purchase APIs under the TEST_STORE compiler flag
* fix lint
* fix unit tests after change of internal APIs
* PR comments
* temporarily return hardcoded `CustomerInfo` on confirmed test purchase
* tests: adapt unit tests to new `TestStorePurchaseHandler` dependency of `PurchasesOrchestrator`
* fix lint
* Fix backend integration tests compilation
* Remove deprecation warning
* remove unnecessary OSs checks
Since the minimum supported versions of the SDK are iOS 13 and tvOS 13
* add `GetWebProductsOperation`
* avoid duplicating decoding objects from WebOfferingProductsResponse and WebProductsResponse
* add missing tests snapshots
* Add `WebProductsResponse.ProductType` enum
* Fix tests
* add web products decoding tests
* Rename WebProducts to WebBillingProducts
* (more) rename WebProducts to WebBillingProducts
* (even more) rename WebProducts to WebBillingProducts
* Move `getWebBillingProducts` outside of `OfferingsAPI` into a new `WebBillingAPI` object
* improve test
* remove unnecessary `sorted()` of `Set`
* fix lint
* fix tests
Since the GetWebBilling request path contains an unordered set of productIds, which the tests do not support, we're only doing snapshot tests with 1 product ID
* fix more tests
* Revert "Temporary revert of automatic health reporting in the SDK (#5353)"
This reverts commit dd7086fd98.
* Do not run health check when running integration tests
* Revert "fix `Purchases` temporary leak when running SDK health check (#5350)"
This reverts commit 26de54308c.
* Revert "[DX-457] Log the SDK configuration report on every `#DEBUG` run (#5317)"
This reverts commit 21f226de00.
* Creates a brand new SDK Manager to reuse logic across different classes
* Updates to use the new manager
* Adds a new method to log the health report to the console only for debug builds
* Run health check for debug builds
* Fixes sendable warnings that got introduced with new manager
* Fixes swiftlint violations
* Adds unit tests for the new SDK Health manager
* Creates a new authorization provider
* Injects the new authorization provider
* Tidies and cleans up unit tests
* Adds a new configuration value
* Updates configuration unit tests
* Makes sure that values are only set on DEBUG configuration
* Reverts state of default value
* Updates wording of output message
* Fixes the wrong wording of error message
* Logs the word RevenueCat along with report title
* Fixes wording issues in error messages
* Addresses PR comments
* Adds extra checks
* Renames configuration value
* Removes unnecessary `Task`
* Renames parameter
* Calls the new endpoint in the BE to check if the app is available or not
* Do not log or perform the report operations if it is not available
* Missing xcodeproj changes
* Adds unit test coverage
* Turns validation on by default
* Creates a new path for the report availability endpoint
* We need to bypass auth but make sure that requests are still signed
* Updates the name of the response field
* Updates unit tests target with new response structure
* Handles no credentials case more gracefully
* Adds new unit tests for the new No ASC credentials warning
* Updates the file's header comment
* Adds documentation
* Wraps calls in necessary compiler directives
* Hide all report-related content behind compiler directives
* Makes more health-related functionality DEBUG only
* Addresses PR comments
* Updates all unit test build failures
* Gets it back to compile on `CUSTOM_ENTITLEMENT_COMPUTATION`
* Removes configuration value
* Updates logging to only include issues
* Adds a mock method for the health report availability request
* Add new public APIs to RevenueCatUI to set the preferred local for the UI components
* fix compliation of RevenueCatUI with new APIs
* add API tests
* implement preferred locale in paywalls
* complete implementation
* adapt unit tests
* adapt paywalls tester
* Add new tests to check preferred locale override
* fix lint
* discard all changes in RevenueCatUI (for separate PR)
* remove trailing comma
* fix tests
* Fix tests
* fix paywalls tester compilation
* remove spoiler from future PR
* Refactor `PreferredLocalesProvider` (#5299)
* refactor: reduce `PreferredLocalesProviderType` into `PreferredLocalesProvider` class
* make `preferredLocaleOverride` private in `PreferredLocalesProvider`
* make `SystemInfo`'s `preferredLocalesProvider` private
* rename `PreferredLocalesProvider`'s mock init parameters
* fix compilation issue
* Change to use new endpoint to fetch web product info
* [skip ci] Generating new test snapshots (#5142)
* [skip ci] Generating new test snapshots (#5141)
* [skip ci] Generating new test snapshots (#5140)
* [skip ci] Generating new test snapshots (#5139)
* [skip ci] Generating new test snapshots (#5138)
* [skip ci] Generating new test snapshots (#5137)
* [skip ci] Generating new test snapshots (#5136)
* Add small utility function to know if offerings has any web biling offering available
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* Add support for fetching web billing products
* Some fixes
* Escape product ids
* Fixes from review
* Support etags
* Change to Int64
* Add method to MockOfferingsAPI
* feat: implement fallback host in all URL requests
* cleanup fallback host API implementation
* Adapt existing tests to changes in `HTTPRequestPath`
* fix implementation
* add unit tests for fallback API host
* add extra unit test
* fix tests
* fix lint
* implement new `host` parameter in `http_request_performed` diagnostics event
* add unit tests for new `host` diagnostics parameter
* change the logic from fallback hosts to fallback paths
* fix test
* fix fallback endpoint for get product entitlement mapping
* fix backend integration tests not building after changes
* rewrite fallback host functionality knowing that only some specific requests support a fallback host
For those requests that support a fallback host, the path, headers and response structure remain the same
* fix text name
* fix unit test
* remove extra comma
* fix unit test
* feat: implement fallback host in all URL requests
* cleanup fallback host API implementation
* Adapt existing tests to changes in `HTTPRequestPath`
* fix implementation
* add unit tests for fallback API host
* add extra unit test
* fix tests
* fix lint
* change the logic from fallback hosts to fallback paths
* fix test
* fix fallback endpoint for get product entitlement mapping
* fix backend integration tests not building after changes
* rewrite fallback host functionality knowing that only some specific requests support a fallback host
For those requests that support a fallback host, the path, headers and response structure remain the same
* fix text name
* Initial app creation
* Initial screen
* Installs and configures the SDK
* Creates a full Swift 6 compliant ViewModel
* Adds a demo customer center button
* Fixes icon name
* Updates a bit of the logic to fetch offerings
* Updates initial layout by showing offerings
* Makes more progress with a VERY DRAFT and VERY WIP screen
* Creates a new network path and requests
* Creates a new health API and adds it to the BE class
* Creates a `checkOfferings` method
* Pushes local changes
* Moved viewmodels to separate files
* Prepared some basic UI structure
* Initial shell of the new method for testing
* Initial implementation of `HealthReportOperation` from the SDK
* Passes `appUserId` through to the internal API
* Adds a new path called `appUserID`
* Removes 'old' `appHealth` endpoint
* Creates a new model for the network response
* Report should be the response type
* Updates backend request
* Updates types and documentation for the new internal request
* Moves validation into its own file
* Replaces method for DebugViewModel
* Updates error messages
* Updates the `Status` section
* Adds logic to show failure reason
* Removes Sample App from PR
* Resets `Package.resolved`
* Availability checks
* Check if device is authorized to make payments
* Fixes all SwiftLint errors
* Updates `ProductStatus` to not have a raw value of String
* Minor style fixes
* Removes warning
* Adds more availability checks
* Fixes comments
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
* Simplifies logic
* Updates comment
* Updates the comments to denote that this method is not intended for production use
* Removes android-only invalid SDK version error
* Adds an action button that the user can use to open the dashboard
* Updates copy about non-actionable item
* Prefers `Your RevenueCat Configuration` to `The Dashboard`
* Removes `Sendable`-related warnings
* Fixes unit tests on CI/CD
* Fixes tests
* Adds unit test for offering configuration error
* Adds unit test for no packages in default offering
* Adds unit test for generic offering configuration
* Removes periods from the end of the error messages
* Adds unit tests for all new errors
* Fixes linter errors
* Wrap in a `DEBUG` compiler directive
* Includes all products in the healthy check status to be used in the Sample App
* Updates report to include product and offering information
* Moves logic over to the report rather than being exclusive to the Debug View
* Fixes CI/CD failures
* Updates the asset for the health check status
---------
Co-authored-by: Hidde van der Ploeg <hiddevanderploeg@me.com>
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
* Add X-Is-Backgrounded header
* Add unit test for presence of new header
* [skip ci] Generating new test snapshots (#4939)
* [skip ci] Generating new test snapshots (#4940)
* [skip ci] Generating new test snapshots (#4941)
* [skip ci] Generating new test snapshots (#4942)
* [skip ci] Generating new test snapshots (#4943)
* [skip ci] Generating new test snapshots (#4944)
* [skip ci] Generating new test snapshots (#4945)
* Lock isAppBackgrounded state to false during tests
* one more update
* Revert "Lock isAppBackgrounded state to false during tests"
This reverts commit 6a915a756a.
* Lock isAppBackgrounded state to false during tests
* Update tests
* Fix iOS 15 snapshot tests
* Remove unused isApplicationBackgrounded async method
---------
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
* refactor: create `DiagnosticsEvent.Properties` struct with all possible fields in a diagnostics event
* chore: remove unnecessary `CodingKeys` and values as `JSONEncoder.KeyEncodingStrategy.convertToSnakeCase` is used in the diagnostics requests
* tests: WIP: adapt unit tests to new `DiagnosticsEvent.Properties`
* fix: have values in Diagnostics events be snake case
* tests: adapt unit tests to Diagnostics events changes
* fix: `name` parameter in DiagnosticsEvent
* tests: fix snapshot testing of JSON to use `.iso8601` date format
* tests: have `JSONEncoder` of snapshot tests be a copy of the encoder user in the SDK
* tests: update the dates in the `CustomerInfo` snapshots for decoding tests to have ISO 8601 format
* fix: remove duplicated import
* refactor: centralize some info transformations in `DiagnosticsEvent.Properties`' init
* tests: add snapshot tests for `DiagnosticsEvents` object
* feat: add `is_retry` parameter to Diagnostics `http_request_performed` event
* tests: consider new `isRetry` parameter in existing `DiagnosticsEvent` tests
* fix: make sure that the `http_request_performed` event is always called
The previous implementation would skip sending the event in the case of getting a `networkError`
<!-- Thank you for contributing to Purchases! Before pressing the
"Create Pull Request" button, please provide the following: -->
### Checklist
- [ ] If applicable, unit tests
- [ ] If applicable, create follow-up issues for `purchases-android` and
hybrids
### Motivation
<!-- Why is this change required? What problem does it solve? -->
<!-- Please link to issues following this format: Resolves#999999 -->
### Description
This adds the `backend_error_code` property to diagnostics
`http_request_performed` events.