Commit Graph
36 Commits
Author SHA1 Message Date
Will Taylor f638ae7952 support redeeming win-back offers with streamlined purchasing disabled 2024-11-08 10:43:59 -06: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
NachoSoto 53fe9cd7bd Xcode 15.3 beta 1: fix compilation errors (#3599)
### Changes:
- `InternalAPI` is now `Sendable` to deal with new compilation error
- `PaywallEventStoreType` is now `Sendable`
- Extracted `SK2BeginRefundRequestHelperType` to make
`SK2BeginRefundRequestHelper` `Sendable`
- Updated `MockSK2BeginRefundRequestHelper` to be thread-safe and
`Sendable`
- Fixed compilation error in `DebugContentViews`
- Fixed compilation error in `DebugViewModel`
- Fixed compilation error in `StoreMessagesHelper`
2024-01-25 15:29:31 -08:00
32c63cdb46 [SK2] Add support for StoreKit Config files in SK2 (#3365)
When using StoreKit Config file, we do not have access to the
transaction history or subscription status server-side.

To work around this, we introduce a new `StoreKit2Receipt` type that
closely resembles the structure of an SK1 receipt and contains all the
transaction history and subscription status for the customer. It can be
sent in a single request to `/receipt`

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Distiller <distiller@static.38.39.183.137.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.38.23.40.89.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.38.39.185.65.cyberlynk.net>
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
2023-12-13 17:00:37 +01:00
235a7dcdc1 StoreKit 2: Optionally send JWS tokens instead of receipts to the backend (#3227)
- Added a new option `usesStoreKit2JWS` under `DangerousSettings`. If
enabled, the SDK will send a JWS token instead of a receipt to the
applicable backend endpoints.
- The option must be used in conjunction with `usesStoreKit2IfAvailable`
configuration option.

### ToDo

- [x] Send JWS token when making a purchase in SK2 mode
- [x] Send JWS token when calculating promo offer eligibility
- [x] Update `syncTransactions` to send the latest transaction JWS.
- [x] Add tests

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Distiller <distiller@static.38.39.183.118.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.198.206.135.125.macminivault.com>
Co-authored-by: Distiller <distiller@static.38.23.39.112.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.38.39.183.58.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.38.39.185.87.cyberlynk.net>
2023-10-16 22:45:29 +02:00
Toni Rico e100bd36f9 Fix runtime crash in SK2TransactionListener in iOS < 15 (#3206)
### Description
We found that there was another runtime crash in Capacitor in iOS < 15
similar to https://github.com/RevenueCat/purchases-ios/pull/3139. This
applies the same fix to this new crash.
2023-09-14 18:19:18 +02:00
NachoSoto 82435936d9 Xcode 13: remove conditional code (#3147)
Clean up thanks to #2421
2023-09-06 10:59:22 -07:00
NachoSoto 7d8f0e0db6 CircleCI: change all jobs to use Xcode 14.x and replace xcode-install with xcodes (#2421)
`CircleCI` is deprecating `Xcode 13` soon.

[`CircleCI` still recommends using
`xcode-install`](https://support.circleci.com/hc/en-us/articles/360011880714-How-do-I-add-different-simulators-to-my-iOS-builds-),
but [it breaks with Xcode
14](https://github.com/xcpretty/xcode-install/issues/466), so [it's been
deprecated
now](https://github.com/xcpretty/xcode-install/blob/master/MIGRATION.md#known-limitations).
This replaces the runtime installation with
[`xcodes`](https://github.com/RobotsAndPencils/xcodes).
2023-09-06 09:32:40 -07:00
NachoSoto dab5035a19 visionOS: support for Xcode 15 beta 6 (#2989)
### Context:

`#if os(iOS)` no longer evaluates to `true` when compiling for
`visionOS`
Which means we need `|| os(xrOS)`.
But to support older Xcode we need `#if swift(>=5.9) && os(xrOS)`.
But, fun fact, short-circuiting doesn’t work in `Xcode 13`.
And we still need that to run iOS <15 tests (`FB12223404` /
`FB12805891`)

💩 

This works around that by defining our own `VISION_OS` flag.

### Changes:
- Added `VISION_OS` compile time flag to determine SDK
- Replaced `#if os(xrOS)` with `#if VISION_OS`
- Added flag to `Package.swift` (thanks @neonichu for helping with this)
2023-08-09 11:19:40 -07:00
NachoSoto ac185188ae StoreKit2TransactionListener: handle transactions asynchronously (#2910)
Currently when launching an app in SK1 mode, if there are multiple
transactions in the queue, we handle them all and begin operations in
parallel. If the cache keys are the same, those end up being a combined
POST receipt request.

However with `StoreKit 2`, because we were waiting to handle each one of
the `StoreKit.Transaction.updates` using `await`, we ended up posting
them all sequentially, instead of relying on `CallbackCache`.

This also adds a lot more coverage for `StoreKit2TransactionListener` to
verify this behavior.
2023-08-07 16:43:50 -07:00
NachoSoto 069f3c0938 StoreKit2TransactionListener: converted into an actor (#2909)
This is a preparatory refactor for an upcoming fix.
This improves the thread-safety of this class, and also refactors its
mock to use a `protocol` instead.
2023-08-07 16:10:35 -07:00
NachoSoto 6c3b4d493c Always initialize StoreKit2TransactionListener even on SK1 mode (#2612) 2023-06-14 12:47:18 -07:00
NachoSoto 18c10b4c67 StoreKit2TransactionListener: added log when receiving Transactions.Updates (#2588)
I realized we currently don't log anything, and we go straight to
posting the receipt. It would be useful for debugging if we know why
receipts are being posted.
2023-06-06 13:50:02 -07:00
NachoSoto de47c5771b GetCustomerInfo posts receipts if there are pending transactions (#2533)
This fixes the last edge-case for offline entitlements. Solves the
following scenario:
```swift
func testPurchaseWhileServerIsDownPostsReceiptWhenForegroundingApp() async throws {
    let logger = TestLogHandler()

    // 1. Purchase while server is down
    self.serverDown()
    try await self.purchaseMonthlyProduct()

    logger.verifyMessageWasNotLogged("Finishing transaction")

    // 2. Server is back
    self.serverUp()

    // 3. Request current CustomerInfo
    let info1 = try await Purchases.shared.customerInfo()
    try await self.verifyEntitlementWentThrough(info1)

    // 4. Ensure transaction is finished
    logger.verifyMessageWasLogged("Finishing transaction", level: .info)

    // 5. Restart app
    Purchases.shared.invalidateCustomerInfoCache()
    await self.resetSingleton()

    // 6. To ensure (with a clean cache) that the receipt was posted
    let info2 = try await Purchases.shared.customerInfo()
    try await self.verifyEntitlementWentThrough(info2)
}
```

This race condition happened when fetching `CustomerInfo` after we had
computed an offline `CustomerInfo` with a purchase: if the server is
still not aware of the purchase, then we lose that until some arbitrary
amount of time when `StoreKit` might decide to send the pending
transactions in the queue.
For that reason, instead of waiting, we proactively check now whenever
`CustomerInfoManager` fetches new info.

The refactors #2540 and #2542 where required to break a retain cycle.
Also thanks to #2534 we know for sure that there aren't any cycles.

Note that this is an iOS 15.0+ only feature. It would be useful to
better ensure consistency in older versions, but it's not strictly
required, as those versions don't support offline entitlements either.
2023-05-30 12:38:18 -07:00
NachoSoto b8fba2e322 Created StoreKit2TransactionFetcher (#2539)
This extracts the logic from `PurchasedProductsFetcher` and adds tests.
It will be used in #2536.

Also exposed and combined the duplicated `underlyingTransaction`
implementations.
2023-05-25 10:18:39 -07:00
NachoSoto 64c8638425 Create separate SPM library to enable custom entitlement computation (#2440)
Fixes SDK-3084

![screenshot_2023-04-25_at_10 02
44_480](https://user-images.githubusercontent.com/685609/234360670-bd2628a1-0684-490d-a9eb-33846a60f72a.png)

## Changes:

- Added new `ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION` compile time flag
- Hidden `configureInCustomEntitlementsModeWithApiKey` behind
`ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION`
- Added new `RevenueCat_CustomEntitlementComputation` SPM target and
library
- Added CircleCI job for `swift build --target
RevenueCat_CustomEntitlementComputation`
- Added new `SPMCustomEntitlementComputationInstallation` using the new
`configureInCustomEntitlementsComputationMode`
- Added CircleCI job for `SPMCustomEntitlementComputationInstallation`
- Added `FrameworkDisambiguation.swift` to avoid using `RevenueCat.`
disambiguation for files that
- Changed API testers to only use the new methods if
`ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION` is defined
- Updated `DangerousSettings` to not expose
`customEntitlementComputation` publicly. The only way to use that now
with `public` API is through
`Purchases.configureInCustomEntitlementsModeWithApiKey`
- Updated sample app to use new library
2023-04-25 15:11:58 -07:00
Chris Vasselli 80f76d7a13 ProductsFetcherSK2: add underlying error to description
### Motivation
I am seeing errors in my logs that are not very helpful:
`2023-02-14 04:20:35.356 Nihongo[96412:1082124] [Purchases] - DEBUG: ℹ️
Store products request failed! Error: The operation couldn’t be
completed. (RevenueCat.ProductsFetcherSK2.Error error 0.)`

It looks like there is an error being thrown by
`StoreKit.Product.products(for: identifiers)`, but it is wrapped in
`productsRequestError` and so no information from the actual underlying
error is being printed to the logs.

### Description
I tried to follow the pattern I saw in other parts of the framework
(specifically, `PurchasesDiagnostics.swift`) to update the localized
description of the error to print the localized description of the
underlying error.

I don't think there's any unit testing needed for this change, since it
should just update the debug logging, but let me know if you disagree.
2023-02-21 14:51:26 -08:00
NachoSoto 84eed758c1 Introduced Configuration.EntitlementVerificationMode and VerificationResult (#2277)
Fixes [CSDK-629].
Depends on #2274 and #2267.

Entitlement verification is accomplished by preventing MiTM attacks
between the SDK and the RevenueCat server.
With verification enabled, the SDK ensures that the response created by
the server was not modified by a third-party, and the entitlements
received are exactly what was sent.

Entitlements are only verified if enabled using
`Configuration.Builder.with(entitlementVerificationMode:)`, which is
disabled by default.

### Changes:
- Added new `VerificationResult`, which replaces
`HTTPResponseVerificationResult`
- Renamed `Configuration.EntitlementVerificationLevel` to
`Configuration.EntitlementVerificationMode`
- `Configuration.with(entitlementVerificationMode:)` is now `public`
- Added `CustomerInfo.entitlementVerification`
- Added `EntitlementInfos.verification` and
`EntitlementInfo.verification`
- Added tests to cover serialization of these new values
- `CustomerInfoResponseHandler` copies the `ValidationResult` to
`CustomerInfo` (all operations that fetch `CustomerInfo` use this)
- Renamed "validation" to "verification" everywhere for consistency
- Finalized documentation

### TODO:
- [x] Figure out whether to use a separate type from
`HTTPResponseValidationResult` or rename that one
- [x] Figure out final API type and property names
- [x] Add tests for `CustomerInfo.copy(with:)`
- [x] Ensure validation result is encoded and decoded (for caching)
- [x] Add missing snapshots

[CSDK-629]:
https://revenuecats.atlassian.net/browse/CSDK-629?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2023-02-17 16:49:21 -08:00
NachoSoto 32d10a9fbd StoreKit2StorefrontListener: added tests to fix flaky code coverage (#2265)
Similarly to #2262, test execution ordering was leading to [flaky code
coverage
results](https://app.codecov.io/gh/RevenueCat/purchases-ios/pull/2262)
with this.

![Screenshot 2023-02-06 at 10 48
45](https://user-images.githubusercontent.com/685609/217058841-33a07262-7959-4732-9706-7426d8f4efb7.png)

Because we didn't have any test coverage, only if some other test
changed the `Storefront`, this test was calling the delegate code.

This adds explicit test coverage using a `MockAsyncSequence`.

### Other changes:
- Added `StorefrontType` to `StoreKit2StorefrontListenerDelegate` method
- `Storefront` initializers are no longer `Optional` `init?`
- Simplified `StoreKit2StorefrontListenerTests` by moving
`AvailabilityChecks` to `setUp`
2023-02-08 12:45:08 -08:00
NachoSoto 5e0873288c StoreKit2 listeners: set Task priority to .utility (#2070) 2022-12-07 14:42:06 -08:00
NachoSoto b91a0ecf2f StoreKit: added logs when purchasing and product requests are too slow (#2061)
Using #2059 in a few places for now.
We can add this to more places, and tweak the thresholds.

Fixes [CSDK-512].

[CSDK-512]:
https://revenuecats.atlassian.net/browse/CSDK-512?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-11-25 17:00:54 -08:00
NachoSoto 5f77821b1d StoreKit 2: don't finish transactions in observer mode (#2053) 2022-11-17 06:50:55 -08:00
NachoSoto 5666eedb39 ProductsFetcherSK2: removed now redundant caching logic (#1908)
Follow up to #1907.

This logic is now implemented and shared in `CachingProductsManager`.

I've decided to keep the logic in `ProductsFetcherSK1` because it's
intertwined with the retry mechanism, and it wasn't trivial to remove
caching while leaving that logic.
2022-09-28 09:44: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 0bff3f65fe Sendable support (#1795)
Fixes [CSDK-379] and https://github.com/RevenueCat/purchases-ios/issues/1041#issuecomment-1206445939

[CSDK-379]: https://revenuecats.atlassian.net/browse/CSDK-379?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

### Main changes:
- `Sendable` conformances to all types that are thread-safe
- `@unchecked Sendable` for types that the compiler can't enforce, but with documentation as to why
- Made classes that aren't mocked `final` (note that this doesn't change the API, because none of these were `open` to begin with)
- Made some classes thread-safe that weren't (like `DeviceCache`)

For the non-`final` `class`es, I've actually managed to compile the SDK with all of them as `final` and `Sendable`, to make sure that making them `@unchecked Sendable` didn't hide any other issues.

### Future improvements:
- https://twitter.com/nachosoto/status/1557139992056500224?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557141944777592832?s=21&t=arWdvEzTIFANBvwqQ0vPiA
- https://twitter.com/nachosoto/status/1557143374922059776?s=21&t=arWdvEzTIFANBvwqQ0vPiA

### Depends on:
- #1794
- #1804
- #1806
- #1807
- #1808
- #1813
- #1822
- #1823
- #1824
- #1825
- #1826
2022-08-31 13:30:38 -07:00
beylmk 33840f605f Add missing logs to ProductsFetcherSK2 (#1780) 2022-08-01 17:15:24 -05:00
Juanpe Catalán 9f498b83a1 Re-fetch cached offerings and products after Storefront changes (3/4) (#1743)
Follow up to #1583. Subtask of #1382.

### Description

This PR aims to invalidate and re-fetch if appropriate the stuff in the cache when the Storefront changes. I've used both `OfferingsManager` and `ProductsManager` to manage the logic of invalidate and re-fetch the data.

Changes:
- Add `invalidateAndReFetchCachedOfferingsIfAppropiate` method to `OfferingsManager` class.
- Rename `clearCachedProducts` method to `invalidateAndReFetchCachedProductsIfAppropiate` in `ProductsManager` class.
- Add `OfferingsManager` as a dependency of `PurchasesOrchestrator`.
- Clear cache methods returns the identifiers of the elements that have been removed or an empty list if there was no stuff in the cache.
- Add tests to cover some scenarios.
2022-07-06 11:20:35 -07:00
NachoSoto 32bf9c4180 Unify SK1/SK2 calls to finish transactions (#1704)
Fixes [CSDK-122].

Before this change, transactions were finished separately for SK1 and SK2. Now this is combined through a common method in `StoreTransactionType`.
2022-06-21 13:45:06 -07:00
Juanpe Catalán 9482e8db82 Clear cached offerings and products after Storefront changes (#1583)
Follow up to #1557. Subtask of #1382 

### Description

This PR aims to clear the stuff in the cache when the Storefront changes.

Changes:
- Add `clearCachedOfferings` method to `DeviceCache`.
- Add `clearCache` method to `ProductFetcherSK1`.
- Make `internal` the `clearCache` method in `ProductsFetcherSK2`.
- Handle the **Storefront** changes in the `PurchasesOrchestator`.
- Add tests to cover some scenarios.
2022-06-16 10:29:07 -07:00
NachoSoto a726fa6524 PurchasesOrchestrator: replaced calls to syncPurchases with posting receipt for an individual product during SK2 purchases (#1666)
Fixes [SDKONCALL-19].
SK2 purchases were always calling `syncPurchases`, which called `Backend.post(receiptData:...)` with no `productData`, which meant that after every SK2 purchase we never posted the price.

This refactor makes a lot of methods in `PurchasesOrchestrator` use `StoreTransaction` so they can be used during SK2 purchases as well. Specifically, `handlePurchasedTransaction` is now called after both SK1 and SK2 purchases.

### Other changes:

- `StoreKit2TransactionListenerDelegate` no longer needs to return `CustomerInfo`
- `StoreKit2TransactionListenerDelegate.handle(purchaseResult:)` no longer notifies the delegate (only done by `StoreKit.Transaction.updates`), which was leading to duplicate `syncPurchases` calls.
- If the purchase is cancelled, `syncPurchases` isn't called. Instead, the `CustomerInfo` is fetched from `CustomerInfoManager`

[SDKONCALL-19]: https://revenuecats.atlassian.net/browse/SDKONCALL-19?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-06-09 14:06:09 -07:00
Juanpe CatalánandNachoSoto 3a4093d228 Prepare the codebase to listen to the Storefront changes (1/4) (#1557)
* Listen changes via `paymentQueueDidChangeStorefront` method
* Create `StoreKit2StorefrontListener.swift` to listen to Storefront changes for SK2
* Update `PurchasesOrchestrator` to listen to the Storefront changes
* Include `StoreKit2StorefrontListenerTests` in StoreKitTests
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>

* Remove force unwrap

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>

* Add required spaces

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>

* Remove useless cancel method when the listener is released
* Notify Storefront changes in the main thread
* Add cancel task when the Listener is released

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
2022-05-16 15:51:04 -07:00
NachoSoto 313fffb204 StoreKit 2 purchases: removed double request to syncPurchases (#1497)
`StoreKit2TransactionListener` was triggering an asynchronous call to `syncPurchases` during transaction verification, and at the end of every purchase, another `syncPurchases` call was being made.
This refactor combines both calls into one by making `finish(transaction:)` wait for that call to finish.
2022-04-22 11:20:22 -07:00
NachoSoto f774692845 Added Storefront.currentStorefront (#1502)
Follow up to #1052
2022-04-13 17:30:48 -04:00
NachoSoto c7018e5e5b Introduced Storefront to abstract SK1 SKStorefront and SK2 StoreKit.Storefront (#1500)
To simplify #1499.
2022-04-13 15:26:43 -04: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