Commit Graph
36 Commits
Author SHA1 Message Date
Antonio Pallares 6c07039715 UI preview mode: disable log in and log out (#4804)
* fail when making log in or log out calls in UI preview mode

* Add unit tests for log in and log out in UI preview mode
2025-02-20 18:27:16 +01:00
Toni Rico c24de73950 WebPurchaseRedemption: Rename alreadyRedeemed result to purchaseBelongsToOtherUser (#4542) 2024-12-05 17:44:08 +01:00
98e4a33225 Support anonymous web purchase redemptions (#4439)
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: andy boedo <andresboedo@gmail.com>
Co-authored-by: Cesar de la Vega <cesarvegaro@gmail.com>
2024-11-25 16:14:32 +01:00
Will Taylor 6de169a23e support fetching eligible win-back offers for a product 2024-11-06 15:46:04 -06:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
Mark Villacampa 679767adf9 Enable explicit_init lint rule and fix issues (#3418)
This rule will warn when calling `init` with the explicit type where it
can be inferred, or where the `.init` part can be omitted.
2023-11-16 00:20:00 +01: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 37f9c005d1 ErrorUtils: handle PurchasesError to avoid creating unknown errors (#2686) 2023-06-22 06:54:02 -07:00
NachoSoto 6986a2f95e NetworkError.signatureVerificationFailed: added status code to error userInfo (#2657) 2023-06-16 07:55:21 -07:00
NachoSoto e2534f0172 ErrorUtils.missingReceiptFileError: added receipt URL userInfo context (#2650)
Added `rc_receipt_url` and `rc_receipt_file_exists`. This is useful when
debugging issues like #2558.
This goes from this:
> [Purchases] - ERROR: 💰 Product purchase for 'rc_1099_1w' failed with
error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is
missing." UserInfo={NSLocalizedDescription=The receipt is missing.,
readable_error_code=MISSING_RECEIPT_FILE,
source_function=handlePurchasedTransaction(_:data:completion:),
source_file=RevenueCat/TransactionPoster.swift:96}

To this:
> [Purchases] - ERROR: 💰 Product purchase for 'rc_1099_1w' failed with
error: Error Domain=RevenueCat.ErrorCode Code=9 "The receipt is
missing." UserInfo={NSLocalizedDescription=The receipt is missing.,
readable_error_code=MISSING_RECEIPT_FILE,
source_function=handlePurchasedTransaction(_:data:completion:),
rc_receipt_url=file:///.../Build/Products/Debug/...app/Contents/_MASReceipt/receipt,
rc_receipt_file_exists=true,
source_file=RevenueCat/TransactionPoster.swift:96}

In this case, we can see the file does exist, so it must be empty.
2023-06-14 12:36:31 -07:00
Andy BoedoandNachoSoto 618ff121d2 Custom Entitlements Computation mode (#2439)
### Changes:

- Added `Purchases.configureInCustomEntitlementsComputationMode`
- Added `Purchases.switchUser(to:)`
- Disabled parts of the SDK in this new mode
- New example app for testing `customEntitlementsComputation` mode

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
2023-04-25 13:21:46 -07:00
NachoSoto dbe93dbbf4 PurchasesDiagnostics: added step to verify signature verification (#2267)
⚠️ 🎉 This also changes integration tests to use
`EntitlementVerificationLevel.enforced` so that integration tests fail
if signatures are invalid.

#### Depends on:
- https://github.com/RevenueCat/khepri/pull/5191
- https://github.com/RevenueCat/khepri/pull/5204
- #2214
- #2215 
- #2216
- #2272

_Marking this as `feat`ure because it contains a new error in
`PurchasesDiagnostics.Error`_
2023-02-16 10:27:04 -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 7c268db1cf NetworkError: added underlying error to description (#2263)
### Before:
> [Purchases] - ERROR: 😿‼️ Error performing request.

### After:
> [Purchases] - ERROR: 😿‼️ Error performing request. The operation
couldn’t be completed. (NSURLErrorDomain error -1.)
2023-02-08 10:38:52 -08:00
NachoSoto 15bd24351e ErrorUtils.purchasesError(withUntypedError:): handle PublicErrors (#2165)
See https://github.com/RevenueCat/purchases-hybrid-common/pull/278.
A valid error ended up as:
> failed: caught error: "Unknown error."

This change ensures that calling this method with an `NSError` does not
return `ErrorCode.unknownError`, and instead we extract the correct
error.
2022-12-21 08:47:47 -08:00
NachoSoto 8e32400c4e Fixed purchasing with PromotionalOffers using StoreKit 2 (#2020)
Fixes [SDKONCALL-160], [SDKONCALL-182], #2114, and
https://github.com/RevenueCat/react-native-purchases/issues/455.
Depends on #2118 and https://github.com/RevenueCat/khepri/pull/4843 /
https://github.com/RevenueCat/khepri/pull/4852.

## Changes:
- Base64decode `signature`
- Fixed signature on backend for SK2:
https://github.com/RevenueCat/khepri/pull/4843 /
https://github.com/RevenueCat/khepri/pull/4852
- Added tests
- Added specific error if signature can't be decoded:
> [Purchases] - ERROR: 😿‼️ The information associated with this
PromotionalOffer is not valid.
See https://rev.cat/ios-subscription-offers for more info. The signature
generated by RevenueCat could not be decoded: signature 914

[SDKONCALL-160]:
https://revenuecats.atlassian.net/browse/SDKONCALL-160?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

[SDKONCALL-182]:
https://revenuecats.atlassian.net/browse/SDKONCALL-182?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-12-09 15:04:29 -08:00
NachoSoto 74c29936b8 ErrorCode.unknownBackendError: include original error code (#2032) 2022-11-11 08:02:28 -08:00
NachoSoto c2a4df78a6 Fixed ErrorUtils.purchasesError(withUntypedError:) on iOS 12 (#1982)
Fixes [CSDK-491].
#1973 introduced an iOS-12 only regression:

<img width="1201" alt="image"
src="https://user-images.githubusercontent.com/685609/195917395-5368485a-acf1-41e2-b236-8bfc4f59e65c.png">

For some reason the Swift runtime on iOS 12 fails to get the value from
the `Error` existential, and gets a null pointer instead.

This workaround works, and it's equivalent on later iOS versions, just a
bit redundant.

[CSDK-491]:
https://revenuecats.atlassian.net/browse/CSDK-491?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-10-14 14:02:36 -07:00
NachoSoto ae22f6e295 ErrorUtils: improved logging and localizedDescription to include underlying errors (#1974)
## Changes:
- `Error.localizedDescription` now matches the message being logged.
Improvements introduced in #1776, for example, now apply to the
description too.
- Logged `BackendError` messages now include underlying error.

## Examples:

The following errors are improved:

### `BackendError`:
- Before:
>  😿‼️ There was an unknown backend error.

- After:
> 😿‼️ There was an unknown backend error. Page not found

### API key error:
- Before:
> 😿‼️ There was a credentials issue. Check the underlying error for more
details.

- After:
> 😿‼️ There was a credentials issue. Check the underlying error for more
details. Invalid API key

### Attribute errors:
- Before:
> ["$email": "invalid"]

- After:
> One or more of the attributes sent could not be saved. ["$email":
"invalid"]
2022-10-11 12:56:15 -07:00
NachoSoto 6a87aa4ca8 ErrorUtils.purchasesError(withUntypedError:) handle PurchasesErrorConvertible (#1973)
This allows methods throwing something like `BackendError` and
converting it to `PurchasesError` automatically.
2022-10-11 12:50:23 -07:00
NachoSoto 12cf786fd6 Purchases.beginRefundRequest: ensured errors are PublicError (#1913)
This was missed in #1879. Without this, we might have been forwarding
some private errors instead of ensuring that we only sent `CodeError`s
2022-09-21 09:36:15 -07: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
NachoSoto 56246ae101 ErrorResponse: don't add attribute errors to message if empty (#1844)
Follow up to #1776. That PR added a lot of tests to cover error logging, and #1778 introduced a small error that wasn't covered: if attribute errors are empty, the `localizedDescription` could end up becoming `"[:]"`. This fixes that.

## Examples:

This was the underlying error returned (the output of `error.localizedDescription`):

#### Before:
> [:]
#### After:
> There was a credentials issue. Check the underlying error for more details.

And this is what was logged (notice that I also fixed `ErrorCode.invalidCredentialsError` to not log as an "Apple Error"):

#### Before:
> 🍎‼️There was a credentials issue. Check the underlying error for more details. [:]
#### After:
> 😿‼️ There was a credentials issue. Check the underlying error for more details.
2022-08-17 11:26:10 -07:00
NachoSoto 233e3a30b6 OfferingsManager: expose underlying error when ProductsManager returns an error (#1792)
This will help figure out the reason behind #1765.
We were currently ignoring the error returned by `ProductsManager`.

In the example below, we can see the underlying issue is `SKErrorDomain` code 5.

### Before:
> Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 1.)

### After:
> Error fetching offerings - The operation couldn’t be completed. (RevenueCat.OfferingsManager.Error error 1.)
_**Underlying error: The operation couldn’t be completed. (SKErrorDomain error 5.)**_
ERROR: 😿‼️ There is an issue with your configuration. Check the underlying error for more details. There's a problem with your configuration. None of the products registered in the RevenueCat dashboard could be fetched from App Store Connect (or the StoreKit Configuration file if one is being used). 
More information: https://rev.cat/why-are-offerings-empty
2022-08-02 11:19:32 -07:00
NachoSoto e58a00b4be CustomerInfoResponseHandler: return CustomerInfo instead of error if the response was successful (#1778)
Fixes [SDKONCALL-62].
The behavior in version 3.x was that we would return a `CustomerInfo` AND the attribute errors. [The test in that version](https://github.com/RevenueCat/purchases-ios/blob/3.14.2/PurchasesTests/SubscriberAttributes/BackendSubscriberAttributesTests.swift#L370-L404) however only verified that the error was returned.

When we converted `(Value, Error)` callbacks to using `Result` (#1387), I assumed this was an error. Because of that incomplete test, we didn't notice this wasn't the desired behavior.

If the backend returns a 200, it's assumed that the errors aren't more important than the correct `CustomerInfo`.

The new implementation simply logs the attribute errors (which is now tested), and returns the `CustomerInfo`:
```
😿‼️ One or more of the attributes sent could not be saved. ["$email": "email is not in valid format"]
```

[SDKONCALL-62]: https://revenuecats.atlassian.net/browse/SDKONCALL-62?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-07-29 10:04:26 -07:00
NachoSoto f8113a7f14 Error logging: logErrorIfNeeded no longer prints message if it's the same as the error description (#1776)
Working on [SDKONCALL-62] I realized that there are many instances where we end up logging the same message twice.

This PR not only improves the logic, but adds much needed test coverage to `ErrorUtils.logErrorIfNeeded` and error logging in general.

[SDKONCALL-62]: https://revenuecats.atlassian.net/browse/SDKONCALL-62?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-07-28 08:11:57 -07:00
NachoSoto 829b69041d logErrorIfNeeded: also log message if present (#1754)
### Before:

For errors with a custom `message` it turns out that we were never logging it.
This was extra confusing for `OfferingsManager.Error.configurationError`, which ended up displaying this:

> 😿‼️ There is an issue with your configuration. Check the underlying error for more details.

With no underlying details.

### After:

> 😿‼️ There is an issue with your configuration. Check the underlying error for more details.
> There's a problem with your configuration. None of the products registered in the RevenueCat dashboard could be fetched from App Store Connect (or the StoreKit Configuration file if one is being used).
> More information: https://rev.cat/why-are-offerings-empty
2022-07-11 10:43:58 -07:00
Joshua Liebowitz 31f611aaa0 Remove create alias (#1695)
* Remove create alias
Resolves [CSDK-152]
2022-06-14 12:39:41 -07:00
NachoSoto 29c034dfc8 Converted CustomerInfo and related types to use Codable (#1496)
### Depends on:
- #1537
- #1540
- #1541
- #1543
- #1546
- #1547
- #1550
- #1551,
- #1565

### Changes

- Removed all custom deserialization code thanks to #1537, #1541, and #1543.
- The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere.
- `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`).
- Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`.
- `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test.
- Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file.

### Other changes:
- `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance.
- Simplified `schemaVersion` handling
- All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`).
- Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code.
- `RawDataContainer` implementation is changed in #1565.

### TODO:
- [x] Fix all tests
- [x] Update `RawValueContainer`
- [x] Consider recovering `CustomerInfoError`
- [x] Look at all previously logged errors
- [x] Finish `CustomerInfoResponseTests`
- [x] Handle all TODOs
- [x] Make `RawDataContainer` actually store all the original data: #1565
2022-05-24 14:22:11 -07:00
NachoSoto 3a03d147c8 Added new ErrorCode.offlineConnectionError (#1507)
This is a special enough error that it's worth distinguishing from other `ErrorCode.networkError`s.

I see a couple benefits:
- When opening an app that has the SDK, this used to print lots of errors that just said "Error performing request". Now it's more clear what's happening
- Users can respond to failed calls to the SDK more appropriately, whereas before it might not be clear why a request failed.

Also fixed `logErrorIfNeeded` not passing the file/function/line parameters to the log functions.
2022-04-15 15:54:18 +02:00
NachoSoto ea7230de38 NetworkError.errorResponse: added status code to userInfo (#1480)
This could help debug these errors.

Thanks to the error refactor this only had to be put in one place.
2022-04-12 15:14:15 -07:00
NachoSoto cf0a1bdb98 SKError, StoreKitError, and Product.PurchaseError conform to ErrorCodeConvertible and added tests (#1473)
Follow up to #1437.
Fixes [CF-323]
2022-04-12 15:13:28 -07:00
NachoSoto 23f63c5ef7 HTTPClient now handles response deserialization + Introduced NetworkError and BackendError (#1425)
Closes #695 and finishes [CF-195]. 
Depends on ~~#1431, #1432, #1433, #1437~~.

## Goals:

- [x] Handle all requests / response / deserialization errors in `HTTPClient`. Clients of `HTTPClient` will only have to handle the "happy" path: #1431
- [x] `HTTPClient` shouldn't return `Result.success` with failed responses, forcing clients to verify the response is actually a failure: https://github.com/RevenueCat/purchases-ios/pull/1431
- [x] Abstract error response deserialization / error creation: #1427
- [x] Abstract attribute error parsing: #1427 
- [x] Move response deserialization to `HTTPClient` based on a `ResponseType: Decodable` type, so the completion block will simply return a `Result<HTTPResponse<ResponseType>, Error>`
- [x] `ETagManager` should store response `Data` instead of a deserialized `[String: Any]`
- [x] Improved error types in `HTTPClient` to fix tests: #1437
- [x] Dealt with non-backwards compatible `ETagManager: #1438

## Changes:
- Replaced `HTTPResponse`'s body from `[String: Any]` to a generic `HTTPResponseBody`.
- Created `HTTPResponseBody` to abstract `Decodable` and provide some default implementations for types like `Data,` `[String: Any]` (for backwards compatibility to types that aren't `Decodable` yet), and `Decodable` itself.
- New `HTTPResponse.Result` typealias (`Result<HTTPResponse<HTTPResponseBody>, Error>`) used everywhere. This will allow replacing `Error` with a more specific `Error` so we can forward known typed errors, and make sure that we don't end up with the wrong error type, or with a very complex error hierarchy and the details buried in `underlyingError`.
- Each layer (only a few for now) has its own error type: `NetworkError`, `BackendError`, `OfferingsManager.Error`.
- `HTTPClient` for example has to produce `NetworkError`, operations produce `BackendError`
- The parent `BackendError` can have specific errors like `.missingAppUserID`, but also be simply a child error `case networkError(NetworkError)`
- All of these conform to a `ErrorCodeConvertible`, so there is a single point of code that converts from simple and readable errors (like `BackendError.emptySubscriberAttributes`, `.unexpectedBackendResponse(.loginResponseDecoding)`) into errors with all the context using `ErrorUtils`
- Converted `DNSError` into `NetworkError.dnsError`. Its functionality remains unchanged.
- Removed `Backend.RCSuccessfullySyncedKey` and `ErrorDetails.finishableKey` in favor of tested properties on `NetworkError`

### Leftovers

There's a few things I've decided to not finish for now:
- [ ] `CustomerInfo` still does't conform to `Decodable` (manual deserialization is still supported by the current system): #1496
- `SubscriberAttributeDict` could also be `Codable`
- [x] Replace `OfferingsFactory` with `Decodable`: #1435

[CF-195]: https://revenuecats.atlassian.net/browse/CF-195?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-04-12 15:11:22 -07:00
NachoSoto a6de3a054c SKError.toPurchasesError: fixed mapping to make it consistent with StoreKitError.toPurchasesError (#1451)
Our mapping between SK1/SK2 errors and `ErrorCode` was inconsistent, so that test was only passing for SK2.

This PR addresses that. It also makes use of `ErrorUtils` methods to ensure that the underlying error is added to the returned errors.
2022-04-05 11:54:01 -07:00
aboedo f5697de730 renamed Purchases folder to Sources (#1416)
* renamed `Purchases` folder to `Sources`

* temporarily enabling installation test jobs

* re-disabling installation test jobs
2022-03-28 11:53:24 -03:00