Commit Graph
11 Commits
Author SHA1 Message Date
d4f0fb2686 refactor(ads): reuse SDK retry classification for poll errors (#7009)
* refactor(ads): move rewardverification to coresdk

* refactor(ads): move rewardverification to coresdk

* refactor(ads): move rewardverification to coresdk

* refactor(ads): remove unnecessary comment

* fix(ads): linter and behavior fixes

* fix(ads): gate reward-verification VC cache invalidation out of CustomEntitlementComputation

* refactor(ads): rename reward-verification transactionId → clientTransactionID

* fix(ads): remove duplicated SSV payload logic from AdMob adapter

* chore(ads): cover install via injectable

* chore(ads): fix linter

* refactor(ads): introduce RewardVerificationToken

* refactor(ads): add reward_verification_completed log

* Update AdapterSDKs/RevenueCatAdMob/Tests/RevenueCatAdMobTests/PresentRewardVerificationTests.swift

Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>

* fix(ads): linter

* fix(ads): key reward verification poll retries on HTTP status

Retry network/offline transport errors and any HTTP 5xx during reward
verification polling, keying on the HTTP status code rather than the
mapped ErrorCode. Empty/unparseable 5xx responses are now retried, and
4xx client errors fail fast as .backendError instead of being retried to
a misleading .timeout.

* refactor(ads): drop redundant comments on poll retry classifier

* refactor(ads): read poll HTTP status via typed NSError accessor

* refactor(ads): reuse SDK retry classification for poll errors

Classify reward-verification poll retries from the structured BackendError
instead of re-deriving the HTTP status from a flattened public NSError.

Adds a reusable NetworkError.isTransient (5xx and connection-level failures
are transient, 4xx are terminal) and forwards it from BackendError; the
HTTPClient fallback-host retry check now delegates to it. The poll path keeps
the structured BackendError via an internal fetchRewardVerificationStatus,
letting the poller reuse BackendError.isTransient and dropping the bespoke
NSError.httpStatusCode accessor.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(ads): switch from experimental to internal

* docs(ads): reference BackendError in Poller doc comment

---------

Co-authored-by: Peter Porfy <peter.porfy@revenuecat.com>
Co-authored-by: Pol Miro <polmiro@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-18 03:45:52 -06:00
f98f57f8e9 fix(workflows): Don't serve stale workflows when the backend rejects the request (#6946)
* fix(workflows): don't restore stale workflows from disk on a 4xx

The getWorkflowsList failure branch restored the cached list + details
on any error. That's right for transient errors (5xx / offline) but
wrong for a 4xx, where the backend is authoritatively rejecting the
request (workflows disabled, unauthorized), so we shouldn't serve stale
prefetched data.

Generalize the existing offerings classification into
BackendError/NetworkError.shouldFallBackToCache (the offerings property
now delegates to it, no behavior change) and gate the workflows disk
restore on it. 4xx skips the restore; 5xx and connection failures still
restore.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(workflows): fill gaps found in code review of 4xx stale-restore fix (#6948)

- testGetWorkflowsListCallsOnCompleteOnClientError: verifies onComplete()
  fires on 4xx even though disk restore is skipped (callers must not block)
- testGetWorkflowsListRestoresDetailsFromDiskOnServerError: verifies that
  on 5xx both list mapping AND workflow details are restored from disk, not
  just the list mapping

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: drop redundant shouldFallBackToCachedOfferings alias

Per review: shouldFallBackToCachedOfferings was only a thin delegate to
shouldFallBackToCache after the prior refactor. Remove it on NetworkError
and BackendError and point callers (OfferingsManager, tests) at
shouldFallBackToCache directly. Behavior-preserving.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Cesar de la Vega <664544+vegaro@users.noreply.github.com>
2026-06-08 19:41:52 +00:00
d11c054407 Use cached offerings on network errors (#5707)
* 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>
2025-11-07 10:22:16 +00:00
NachoSoto ea952f0108 ErrorUtils.purchasesError(withSKError:): handle URLErrors (#3346)
Fixes #3343.

`SKPaymentTransaction.error` docs say:
> The error property is undefined except when `transactionState` is set
to `SKPaymentTransactionState.failed`. Your application can read the
error property to determine why the transaction failed. For a list of
error constants, see `SKErrorDomain` in StoreKit Constants.

Which is why [`PurchasesOrchestrator` only considered that
domain](https://github.com/RevenueCat/purchases-ios/blob/4.28.1/Sources/Purchasing/Purchases/PurchasesOrchestrator.swift#L792).

This expands this handling to consider any other network error,
therefore providing more precise errors.

Since offline connection error detection was only in `HTTPClient`, I
also moved that out and removed `NetworkError.offlineConnection`, so
that we detect this when converting `NetworkError.networkError` instead.
2023-10-25 20:25:09 -07:00
NachoSoto 833524314d HTTPClient: extracted HTTPRequestPath protocol (#2986)
This paves the way to add a second set of paths with a separate server
URL to be able to shed requests to offerings and product entitlement
mapping to a separate server.

### Changes:
- New `HTTPRequestPath` with the same properties as `HTTPRequest.Path` +
a new `serverHostURL`
- Moved proxy logic from `SystemInfo`/`HTTPClient` to `HTTPRequestPath`
- Simplified `HTTPRequest` implementation
2023-08-10 15:23:13 -07:00
NachoSoto 6d1cf97a41 ErrorResponse: drastically improved error messages, no more "unknown error"s (#2660)
## Description:

We've gotten several reports with error messages that aren't very
helpful, like #2390.

Turns out that whenever the API returned a non-successful response but
without an error code, we always produced _"unknown error"_.
This improves the behavior to default to showing the status code. One
example that was very obvious is setting up the SDK with an invalid API
key.

## Examples:

### `OfferingsManager.Error`:

- Before:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: The operation couldn’t be completed. (RevenueCat.NetworkError error 6.)
```

- After:
```
[Purchases] - ERROR: 🍎‼️ Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 0.)
Underlying error: Unknown error. API request failed with status code 401
```

### `BackendError`:
- Before:
```
[Purchases] - ERROR: 😿‼️ Unknown error.
```

- After:
```
[Purchases] - ERROR: 😿‼️ Unknown error. API request failed with status code 401
```

### Request debug logs:
- Before:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error.
```
- After:
```
[Purchases] - DEBUG: ℹ️ API request failed: GET /v1/subscribers/$RCAnonymousID:988f9b85609b4a57af255750bec1fbb2/offerings: Unknown error. API request failed with status code 401
```

### `CustomerInfoManager`:
- Before:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error
```

- After:
```
[Purchases] - WARN: ⚠️ Attempt to update CustomerInfo from network failed.
The operation couldn’t be completed. (RevenueCat.BackendError error 0.)
Underlying error: Unknown error. API request failed with status code 401
```
2023-06-16 09:19:16 -07:00
NachoSoto 6986a2f95e NetworkError.signatureVerificationFailed: added status code to error userInfo (#2657) 2023-06-16 07:55:21 -07:00
NachoSoto a6623640e8 Implemented NetworkError.isServerDown (#2367) 2023-03-23 08:15:57 -07:00
NachoSoto f05aabd176 HTTPClient: added signature validation and introduced ErrorCode.signatureVerificationFailed (#2272)
Fixes [CSDK-634].

## Changes:
- Created `HTTPResponseValidationResult`
- Added `HTTPResponseValidationResult` and `HTTPClient.ResponseHeaders`
to `HTTPResponse`
- Added new `ErrorCode.signatureVerificationFailed`
- Extracted `SigningType` to create `MockSigning` to test `HTTPClient`
without bothering with signatures
- Refactored `ETagManager` to use `HTTPResponse`, making it easier to
pass through response headers and verification results
- Stored `HTTPResponseValidationResult` in `ETagManager` cache
- `HTTPClient` handles signatures from response based on
`SystemInfo.responseVerificationLevel`
- Renamed `HTTPRequest.createIntegrityEnforcedRequestRequest` to
`HTTPRequest.createWithResponseVerification`
- Added lots of tests for this new behavior in `HTTPClient`
- Added lots of new tests for `ETagManager`

[CSDK-634]:
https://revenuecats.atlassian.net/browse/CSDK-634?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2023-02-16 03:06:59 -08:00
NachoSoto d4ea390970 Introduced type-safe PurchasesError errors and fixed some incorrect returned error types (#1879)
This is the last change from what was started with #1437. That PR introduced a few private error types, but for the errors returned in the public `APIs` we were still passing around `Error`s everywhere, which didn't provide any compile-time guarantees.

### Goals:
- Ensure that public APIs *only* return `ErrorCode`s wrapped with the additional `userInfo`, and not accidentally return other types like `SKError`, `BackendError`, `ErrorCode` directly, etc.
- Guarantee type-safety when passing errors around within our internal implementations instead of assuming certain thrown errors are of a particular type.

### Changes:
- Created `PurchasesError` for wrapping an `ErrorCode` with the additional `userInfo`.
- Changed returned errors from `Error` to `NSError`. This is a change in the API, but the new type is a more specific `Error` so it shouldn't lead to any changes in client apps.
- Because `PurchasesError` isn't implicitly convertible to `NSError`, this ensures that we don't return those values directly, and instead use `PurchasesError.asPublicError`.
- Changed `ErrorCodeConvertible` to a now more precise `PurchasesErrorConvertible`.

Thanks to this new type-safety this PR fixes at least 3 bugs where we were returning private errors instead of `ErrorCode`s.

### Testing:

Thanks to #1871 we know that the returned errors are still convertible to `ErrorCode` so users can `switch` over them.
2022-09-05 13:46:07 -07:00
Joshua Liebowitz 858bef3f9b Extract Offerings-related things into OfferingsAPI (#1706)
Extract Offerings-related things into OfferingsAPI
2022-06-16 14:00:40 -07:00