Commit Graph
48 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 62d003521e remove storefront from paymentwrapperqueue 2024-10-18 09:55:46 -05:00
Cesar de la Vega c960e5895a Fix hasFeature(RetroactiveAttribute) check in iOS 14 (#4359) 2024-10-08 16:01:13 +02:00
Mark Villacampa 6f8e53ac40 Fix Xcode 16 warnings (#4334)
When compiling with Xcode 16, we started throwing a bunch of new warnings. Here's a list and explanation of each:

- The most common one was needing to explicitly restate the @unchecked Sendable conformance from a parent class.
- There were a couple switches where a new entry was added in tvOS but only for iOS 18, and needed to add it to make it exhaustive.
- There we a couple of places where we were adding a protocol conformance to a class from a different module e.g. extension DispatchTimeInterval: Comparable. This is considered unsafe now because the owners of the module could add the conformance later. Solved by removing the conformance which wasn't being used widely in the codebase, only in tests.
- Finally there was a class where we were capturing SKRequest and SKProductsRequest instances in a sendable block, but only to either log something, or use them as dictionary key, so I just made the classes Sendable (with the new @retroactice attribute because they belong to a different module)
2024-10-02 11:53:45 +02:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
NachoSoto e2f72330a0 ProductFetcherSK1: enable TimingUtil log (#3327)
`ProductFetcherSK1` had 2 public methods for fetching products:
`products(withIdentifiers:completion:)` and
`sk1Products(withIdentifiers:completion:)`.
Only one of them was using `TimingUtil` to log slow requests, and that
one happened to not be used by `ProductsManager`, which means that we
weren't getting these logs.

This is evident in
https://github.com/RevenueCat/react-native-purchases/issues/755. With
this change, we'll now be able to see how long the product request took.
2023-10-25 07:33:58 -07:00
NachoSoto a9ae7bdbb0 StoreKit 1: disabled finishTransactions log on observer mode (#3314) 2023-10-20 08:35:24 -07:00
NachoSoto 1350189e90 StoreKit 2: only listen to StoreKit.Transaction.updates when SK2 is enabled (#3032) 2023-08-16 16:04:42 -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 3266b7a9f1 CachingProductsManager: removed duplicate log and added tests (#2898)
I noticed this was being logged twice when requesting SK1 products,
which makes it seem like we're fetching them twice:
```
[store_kit] DEBUG: ℹ️ No existing products cached, starting store products request for: ["com.revenuecat.monthly_4.99.1_week_intro"]
```

`CachingProductsManager` was introduced in #1907. Explained there is why
`ProductsFetcherSK1` keeps some caching behavior.
But the main caching layer is now `CachingProductsManager`, so it makes
sense to keep the log only there.

This adds 4 basic integration tests. We had independent coverage of
`ProductsManager` and `CachingProductsManager` with a mock
`ProductsManager`, but no unit tests covering both together. This serves
that purpose too.
2023-07-26 17:49:37 +02:00
NachoSoto 6c3b4d493c Always initialize StoreKit2TransactionListener even on SK1 mode (#2612) 2023-06-14 12:47:18 -07:00
NachoSoto 3388bc5437 Refactor: extracted all log strings (#2600)
I'm working on improving our logging setup, which will be made easier if
we don't use hardcoded strings in our logs.
2023-06-09 09:48:26 -07:00
NachoSoto cc0ee6a1e1 ProductsManager: improved display of underlying errors (#2575)
This goes from:
```
ERROR: 🍎‼️ Store products request failed! Error: UNKNOWN_ERROR
```
To:
```
ERROR: 🍎‼️ Store products request failed! Error: Error Domain=SKErrorDomain Code=0 "UNKNOWN_ERROR" UserInfo={NSLocalizedDescription=UNKNOWN_ERROR, NSUnderlyingError=0x600002b04cf0 {Error Domain=ASDErrorDomain Code=950 "Unhandled exception" UserInfo={NSLocalizedDescription=Unhandled exception, NSLocalizedFailureReason=An unknown error occurred}}}
```
2023-06-02 09:47:34 -07:00
NachoSoto 6bc1445e06 StoreKit1Wrapper: added debug log for duplicate finishTransaction calls (#2577) 2023-06-02 08:32:45 -07:00
NachoSoto e9e6d887c2 Offline Entitlements: integration tests (#2501)
## Example tests:
```swift
func testOfferingsAreCachedInMemory() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings) === onlineOfferings
}

func testOfferingsAreCachedOnDisk() async throws {
    let onlineOfferings = try await Purchases.shared.offerings()
    expect(onlineOfferings.all).toNot(beEmpty())

    self.serverDown()
    await self.resetPurchases()

    let offlineOfferings = try await Purchases.shared.offerings()
    expect(offlineOfferings.response) == onlineOfferings.response

    let offering = try XCTUnwrap(offlineOfferings.current)
    expect(offering.availablePackages.count) == onlineOfferings.current?.availablePackages.count
    expect(offering.monthly?.storeProduct.productIdentifier) == "com.revenuecat.monthly_4.99.1_week_intro"
}
```
2023-05-19 10:54:42 -07:00
NachoSoto 22638951b4 StoreKit1Wrapper: added log when adding payment to queue (#2423) 2023-04-21 08:03:18 -07:00
NachoSoto 4a903510cd StoreKit1Wrapper: added debug log when transaction is removed but no callbacks to notify (#2418)
This would make issues like #2415 easier to debug.
2023-04-20 08:32:08 -07:00
NachoSoto 0819eca3e8 Restore purchases: post product data when posting receipts (#2178)
Fixes [CSDK-604].

When restore receipts, we don't post any price for products because we
don't know what the user bought.
As a best-effort approach, this parses the `AppleReceipt` and looks for
products that were purchased.


[CSDK-604]:
https://revenuecats.atlassian.net/browse/CSDK-604?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-12-27 10:13:07 -08:00
NachoSoto 1b13754ca2 Fixed race condition finishing SK1 transactions (#2148)
Fixes [CSDK-504].

`SKPaymentQueue.finishTransaction` is asynchronous, but we were assuming
it wasn't.
This meant that when performing multiple purchases (like consumables) in
quick succession, before the first transaction was finished, the second
purchase was reusing the first transaction.

Example log:
```
[Purchases] - INFO: 💰 Finishing transaction ‘0’ for product ‘consumable.10_coins’
[Purchases] - INFO: 😻💰 Purchased product - ‘consumable.10_coins’
[Purchases] - INFO: 💰 Purchasing Product ‘consumable.10_coins’ from package in Offering ‘coins’
[Purchases] - DEBUG: ℹ️ StoreKit1Wrapper (0x00006000008a9530) removedTransaction: consumable.10_coins 0 1
```

Notice how the "removed transaction" was notified _after_ we had already
started the second purchase.

See also #2148 for an earlier fix for finishing transactions.

[CSDK-504]:
https://revenuecats.atlassian.net/browse/CSDK-504?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-12-21 08:17:07 -08:00
NachoSoto bc66739b52 StoreKitRequestFetcher: removed unnecessary dispatch (#2152)
`finishReceiptRequest` is already running on the worker thread, so
there's no need for the double dispatch.

Also refactored tests.
2022-12-21 07:35:20 -08:00
NachoSoto 30c7e9e168 StoreKit1Wrapper: added warning if receiving too many updated transactions (#2117)
See #2107. We still don't know what caused that, but it leads to
degraded performance (mildly reduced by #2115). To advice of that, in
case users are wondering why the app might potentially take excessive
CPU usage, I added this warning.
2022-12-20 14:08:53 -08:00
NachoSoto b8f300d2c8 StoreKitRequestFetcher: added log when starting/ending requests (#2151)
This would have given more visibility into the need for #2146.
2022-12-15 08:51:00 -08:00
NachoSoto d389d7e27a StoreKit1Wrapper: process transactions in a background thread (#2115) 2022-12-07 14:37:26 -08:00
NachoSoto de84ccdc78 Added verbose logs for Purchases and StoreKit1Wrapper lifetime (#2082)
Depends on #2080.
This will help debug [CSDK-517].

### Examples:
```
- VERBOSE: Purchases.init: created new Purchases instance: Purchases (0x00006000037af800)
StoreKit Wrapper: left(<RevenueCat.StoreKit1Wrapper: 0x600000b6d3a0>)
- VERBOSE: Purchases.deinit: Purchases (0x0000600003f82600)
```

[CSDK-517]:
https://revenuecats.atlassian.net/browse/CSDK-517?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-11-25 17:09:53 -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 69073be68a StoreKit1Wrapper: added instance address when detecting transactions (#2055)
My current theory for what's causing [CSDK-517] is that we have a
leaking `StoreKit1Wrapper` when running integration tests, and that's
why we're posting multiple receipts concurrently.

### Example:
```
PaymentTransactionObserver (0x0000600000e36480) updatedTransaction: com.revenuecat.monthly_4.99.1_week_intro 0
```

[CSDK-517]:
https://revenuecats.atlassian.net/browse/CSDK-517?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-11-22 13:20:05 -08:00
NachoSoto 625f38f395 Fixed Mac Catalyst build (#1981)
I broke this in an earlier PR but we didn't notice because we don't
compile that on every PR. I filed [TRIAGE-82] for that.
This fixes the rest of the release checks in #1980.

[TRIAGE-82]:
https://revenuecats.atlassian.net/browse/TRIAGE-82?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-10-14 09:10:17 -07:00
NachoSoto b746f5f205 PaymentQueueWrapper: improved abstraction for active SKPaymentQueue wrapper (#1968)
This is a follow up to #1901 and #1882. Depends on #1967.
Essentially before this change, `Purchases` had both `StoreKit1Wrapper?`
and a `PaymentQueueWrapper`. The later wasn't actually needed when the
first was not-nil, so it was essentially storing `(a?, b?)`. What we
really wanted to express is that it could have one OR the other.

This introduces `Either` to express this requirement in the type system,
as well as `PaymentQueueWrapperType` that will be used to fix #1964.
2022-10-11 12:55:42 -07:00
NachoSoto 9e4b329f99 Moved SKPaymentQueue.presentCodeRedemptionSheet to StoreKitWorkarounds (#1967)
NFC.

I'll be using this method from 2 different places, and it belongs here
with the other workarounds.
2022-10-10 08:38:59 -07:00
NachoSoto b981e0fa4b Async.call method to convert completion-block call to async (#1969)
This simplifies calling completion-block APIs as `async`. The opposite
of what #1943 introduced.
2022-10-10 08:36:15 -07:00
NachoSoto 6785b6ec09 PaymentQueueWrapper: also implement shouldShowPriceConsent (#1963)
Follow up to #1962. This is also needed when `StoreKit1Wrapper` is not
setup (see #1882).
2022-10-07 11:10:19 -07:00
NachoSoto 7dd98ee15a PaymentQueueWrapper: also conform to SKPaymentTransactionObserver to fix promoted purchases (#1962)
Follow up to #1901. That fix was incomplete. I missed it because the
protocols were mixed up (fixed in #1961). Now `PaymentQueueWrapper`,
when set up as the delegate (only when SK2 is enabled and SK1 is
disabled) will be added as a transaction observer.
2022-10-07 07:54:51 -07:00
NachoSoto 0dda9c4bfa StoreKit1Wrapper: separated SKPaymentTransactionObserver and SKPaymentQueueDelegate implementations (#1961)
This way it's more clear which methods belong to which protocol.
This confusion is what lead to #1901 being wrong (will fix in a separate
PR).
2022-10-07 07:31:19 -07:00
NachoSoto 72a9f57103 PaymentQueueWrapper: handle promotional purchase requests from App Store when SK1 is disabled (#1901)
Follow up to #1882. After that PR, `StoreKit1Wrapper` was no longer
listening to
`SKPaymentQueueDelegate.paymentQueue(:shouldAddStorePayment:for:)`,
which means that purchases initiated from the App Store were being
ignored by the SDK if SK2 is enabled.

To fix this, `PaymentQueueWrapper` now becomes the
`SKPaymentQueueDelegate` only when `StoreKit1Wrapper` is not
initialized, and `PurchasesOrchestrator` initiates an SK2 purchase
instead.

## Other changes:
- `PurchasesOrchestrator` purchase methods no longer need the whole
`PromotionalOffer`, only the `SignedData`. This allows the new
implementation to create that data from only the `SKPaymentDiscount`
provided from the `SKPayment`.
- Added tests for `PromotionalOffer.SignedData`
2022-09-21 12:03:05 -07:00
NachoSoto 7934faf4a6 Renamed StoreKitWrapper to StoreKit1Wrapper (#1886)
Follow up to #1882. Now that `StoreKit1Wrapper` is not initialized for SK2, it makes sense to clarify that this class is only for SK1.
This is just a simple find-replace.
2022-09-06 17:15:54 -05:00
NachoSoto 32c28e16b9 Disable SK1's StoreKitWrapper if SK2 is enabled and available (#1882)
When SK2 is enabled, `StoreKit2TransactionListener` is enough to handle transactions, so it's no longer necessary to have that and `StoreKitWrapper` both listening to them.
2022-09-05 17:32:12 -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
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
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 fc37a3fad4 Purchases.canMakePayments: moved implementation to StoreKitWrapper (#1580)
Fixes [CF-571]
2022-05-13 10:07:45 -07:00
Joshua Liebowitz 9bde89137c Create Configuration object and API to configure Purchases (#1556)
* Resolves [CF-443]
* Adds `configureWithConfiguration` to `RCPurchasesAPI.m`
* Create Configuration object and API to configure Purchases
* Update API doc for new Purchases API
* Add example code to Configuration documentation
* Adds deprecations for various Purchases.configure methods
2022-05-12 15:43:10 -07:00
NachoSoto 1857932e0e SK1StoreProductDiscount: handle SKProductDiscount.priceLocale being nil and created StoreKitWorkarounds (#1545)
Fixes #1521.
Despite `SKProductDiscount.priceLocale` being non-optional, StoreKit might return `nil` `NSLocale`s.
This works around that to make sure the SDK doesn't crash when bridging to `Locale`.
2022-05-12 08:55:02 -07:00
NachoSoto 63414a6997 ProductRequestData: added Storefront for receipt posting (#1505)
Supersedes #1499.
Fixes [CF-434].
Co-authored-by: Joshua Liebowitz <josh@revenuecat.com>
2022-05-06 08:00:20 -07:00
Joshua Liebowitz ffadd011d3 Expose shouldShowPriceConsent on PurchasesDelegate (#1520)
Expose `shouldShowPriceConsent` and `showPriceConsentIfNeeded` on PurchasesDelegate. Now you can manually handle showing the price consent if `shouldShowPriceConsent` is set to `false`

Resolved [CF-69]

Also updated some project settings for the APITesters to produce a couple less warnings
Updated PromotionalOfferAPI.Swift to remove a couple warnings
2022-04-26 14:08:12 -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