Commit Graph
63 Commits
Author SHA1 Message Date
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
Mark VillacampaandToni Rico 72b06f6943 Introduce PurchaseParams to allow passing extra configuration info when making a purchase (#4400)
* Introduce PurchaseParams to allow passing extra configuration info when making a purchase

* simplify naming

* fallback to old method when flag is not enabled

* Add $(inherited) to the test targets so they pick up compiler flags from Local.xcconfig

* Include product/package inside PurchaseParams

* fix tests

* add compiler flag check

* update purchase tester

* fix docs

* add tests

* Update Sources/Networking/Operations/PostReceiptDataOperation.swift

Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>

---------

Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
2024-11-06 16:05:37 +07: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
dd0ee262ea [Customer Center] Initial implementation (#3967)
Alpha of the Customer Center. In this branch, it's been disabled behind
a compilation flag.

---------

Co-authored-by: Will Taylor <wtaylor151@gmail.com>
Co-authored-by: James Borthwick <109382862+jamesrb1@users.noreply.github.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: JayShortway <29483617+JayShortway@users.noreply.github.com>
Co-authored-by: Toni Rico <toni.rico.diez@revenuecat.com>
Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
Co-authored-by: Cody Kerns <44073103+codykerns@users.noreply.github.com>
Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2024-08-09 15:26:55 +02:00
Will Taylor 348258a41f only retry post receipt for 429 status codes 2024-07-25 16:46:31 -05:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
Josh HoltzandRevenueCat Git Bot a9737aa7fc Add targeting to PresentedOfferingContext (#3730)
### Motivation

Get more context with targeting information 

### Description

- Parse targeting information into `Offerings`
- Add targeting context into `Offerings.current`
- Add targeting context into `getCurrentOffering(forPlacement: String)`
- Post targeting context in post receipts

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2024-02-28 16:10:52 -06:00
61d66bbc32 Add currentOffering(forPlacement: String) to Offerings (#3707)
### Motivation

New function to get `Offering` by a placement identifier

### Description

New `currentOffering(forPlacement: String): Offering?` method on
`Offerings`
- Added demo button for this in purchase tester app

```swift
// Example usage
if let offering = offerings.currentOffering(forPlacement: "onboarding") {
  showPaywall(offering)
}
```

#### Logic flow

- If a placement identifier explicitly exists within the
`placements.offeringIdsByPlacement` dictionary, the method attempts to
fetch the corresponding `Offering` without resorting to a fallback
- Example: when there is a `nil` / null value for a placement, we don't
want to use the fallback
- If the placement identifier does not exist, the method then attempts
to fetch a fallback offering defined by `placements.fallbackOfferingId`

#### New placements object in offerings response

```js
{
    // other offerings stuff
    "placements": {
        "fallback_offering_id": "offering_a", // can be null
        "offering_ids_by_placement": {
            "onboarding_start": null,
            "onboarding_end": "offering_b",
        }
  }
}
```

---------

Co-authored-by: Guido Torres <guido@revenuecat.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2024-02-28 15:11:48 -06: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
NachoSoto ed80980d08 HTTPClient: verbose logs for request IDs (#3320)
See https://github.com/RevenueCat/khepri/pull/6798.
This can help debug issues as it helps us cross-reference requests on
our end.

### Changes:
- Logging `X-Request-ID` and `X-Amzn-Trace-ID` headers if present (only
in verbose mode)
- Logging these even for failed requests
- Also logging `request_handled_by_load_shedder` for failed requests
- To accomplish that. `HTTPURLResponse` now conforms to our
`HTTPResponseType`, this allows us to extract headers in an uniform way

### Example:
```
[network] DEBUG: ℹ️ API request completed: GET '/v1/subscribers/$RCAnonymousID%3Ad9dc4f3b33054cf28c0f2ec9da0f48d5' (304)
Request-ID: 'be534c50-5231-403b-be2f-7c077861751d'; Amzn-Trace-ID: 'Root=1-6532c6ca-7ef80f961ec9c4145484df13'
```
2023-10-25 20:24:23 -07:00
38b4e3b411 Paywalls: add identifier to events (#3332)
### Motivation

Send a unique `id` on each paywall event

### Description

- Added `id: ID?` to `PaywallEvent.Data`
- Optional so its backwards compatible with events recorded before this
change (see actual tests from below)
- Add `withNewID()` function that will mutate the `id` when tracked for
cancel and close

#### Events recored before `id` changes but posted with `id` changes

<img width="433" alt="Screenshot 2023-10-25 at 9 33 27 AM"
src="https://github.com/RevenueCat/purchases-ios/assets/401294/b6c77f0c-7429-4638-a4ba-7d6068c80cb7">

#### Events recorded and posted with `id` changes

<img width="479" alt="Screenshot 2023-10-25 at 9 37 53 AM"
src="https://github.com/RevenueCat/purchases-ios/assets/401294/8cae5329-2f31-4f03-8997-69dcee609e1f">

---------

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: Distiller <distiller@static.38.39.183.26.cyberlynk.net>
Co-authored-by: Distiller <distiller@static.38.39.184.242.cyberlynk.net>
Co-authored-by: distiller <distiller@static.38.23.41.34.cyberlynk.net>
2023-10-25 20:26:50 -05: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
NachoSoto cc307399c3 Paywalls: added Purchases.track(paywallEvent:) (#3160)
This uses `PaywallEventsManager` (#3159) to track events, and saves the
presented `PaywallEvent` to be send along with
`PostReceiptDataOperation`.
2023-09-14 18:28:11 -07:00
NachoSoto 56de917d02 Paywalls: removed presentedPaywallViewMode (#3109)
We're going to track this in a different way in the future.
2023-09-14 18:28:11 -07:00
NachoSoto 8ba761a23f Paywalls: send presented PaywallViewMode with purchases (#2859)
I've moved `PaywallViewMode` from `RevenueCatUI` to `RevenueCat`. This
uses the same approach as #2645. This method is `public` so it can be
used from `RevenueCatUI`, but the documentation stays its purpose.

The behavior is covered with integration tests and a snapshot test.

Depends on https://github.com/RevenueCat/khepri/pull/6364.
2023-09-14 18:28:11 -07:00
NachoSoto 0ea37c5dd8 Refactor: extract HealthOperation (#3154) 2023-09-07 07:18:18 -07:00
NachoSoto 216f1e13c3 PurchasesOrchestrator: disambiguate transactions from the queue (#2890)
This adds tests and a fix for a scenario discovered by @aboedo and
@mcastany.

### Timeline:
- Make a purchase of product_1
- Close app
- Renewals happen in the background
- Subscription expires
- Open app, renewals are in the queue but not processed yet
- Start a purchase of `product_1` (`SKPaymentQueue.add`)
- StoreKit queue shows renewal from step 3 as finished, it's expired
- SDK sees finished transaction in the queue for `product_1`
- SDK sees completion block for `product_1`, considers it a successful
`.purchase`
- SDK posts receipt, `initiation_source: .purchase` 💥
- Backend replies with 422 because StoreKit hasn't processed the
transaction from 6

### Fix:
The fix looks at the date when the completion block was added (the time
`.purchase()` is called).
If the transaction was created _before_ this, we know it didn't come
from the actual purchase, and therefore it should be identified as
`.queue`.
2023-08-07 15:57:10 -07:00
NachoSoto cad8328a60 IntroEligibility: changed products to Set<String> (#2976)
Order is irrelevant when requesting intro eligibility.

Note that I haven't changed the public API because that wouldn't be
backwards compatible.
2023-08-07 13:30:26 -07:00
NachoSoto f7e36fb993 Trusted Entitlements: add support for signing POST body (#2753)
### Changes:
- `HTTPClient` has a new `X-Post-Params-Hash` request header
- Replaced `HTTPRequest.Method.post`'s `Encodable` value with a more
specific `HTTPRequestBody` (like `HTTPResponseBody`)
- `HTTPRequestBody` allows overriding `contentForSignature` (it defaults
to `[]`) which then `Signing` uses for the hash
- Fixed `LogInOperation` not passing the `VerificationResult` to the
result `CustomerInfo`. This is captured in unit and integration tests
now.
- Add support for signing `POST` requests in `Path.logIn` and
`Path.postReceiptData`

Depends on https://github.com/RevenueCat/khepri/pull/6246
2023-07-13 07:45:16 -07:00
NachoSoto 3da1c50925 HTTPRequest.Path: escape appUserID (#2747)
We were escaping users for every (hopefully) request. This refactor
moves that responsibility to the path creation in `HTTPRequest.Path` to
ensure it's done consistently.

Additionally, this is required so that `SigningTests` produce a
consistent path (see #2746).
2023-07-05 15:41:51 -07:00
NachoSoto 01ca7ae635 PostReceiptDataOperation: add new testReceiptIdentifier parameter (#2749) 2023-07-05 10:37:43 -07:00
NachoSoto 3f538fc6f9 Trusted Entitlements: update handling of 304 responses (#2698)
### Changes:
- `ETagManager` no longer stores the verification mode
- Created new `VerifiedHTTPResponse`, which has the
`VerificationResult`, extracted from `HTTPResponse`
- Removed `VerificationResult.from(cache:response:)`
- Changed `SignatureVerificationHTTPClientTests` to test using the real
`ETagManager`, so they become more "end to end" unit tests. A lot of
them were relying on implementation details of the mock instead of the
real `ETagManager`.
- Removed several `HTTPClient` tests that were checking a behavior that
was impossible (for example, no verification result despite it being
enabled), or that checked behavior based on the cached
`VerificationResult`
2023-06-30 12:24:53 -07:00
NachoSoto 2fc7725827 PostReceiptDataOperation: clean up cache key (#2628) 2023-06-13 06:33:38 -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 25c78f862a Removed unnecessary Strings.trimmedOrError (#2601) 2023-06-09 07:43:41 -07:00
NachoSoto a18d3b4fca PostReceiptOperation: added ability to also post AdServices token (#2566)
Cherry-picked #2549 post refactors (#2540 and #2542). This becomes a lot
simpler since #2542, thanks to the fact that we can put this new
parameter in `PurchasedTransactionData`.

### Changes:
- Added `aadAttributionToken` to `PurchasedTransactionData`
- Exposed `AttributionPoster.adServicesTokenToPostIfNeeded`
- Added snapshot test to verify it's sent
- Added `PurchasesOrchestrator` tests (SK1/SK2) for sending the
attribution token
- Added missing `PurchasesOrchestrator` tests for sending attributes
- Added log when marking `AdServices` token as synced
- Exposed `Purchases.attribution` for custom entitlement computation
framework (and added to API tester)
- Exposed `Purchases.enableAdServicesAttributionTokenCollection` for
custom entitlement computation framework (and added to API tester)
2023-05-31 10:03:55 -07:00
NachoSoto 4b2138367b Refactored TransactionPoster: removed 2 dependencies and abstracted parameters (#2542)
Follow up to #2540.

This was necessary to ensure that we can implement #2533 without
reference cycles.
This was the hierarchy before this change:

![IMG_B25ECE86C2F9-1](https://github.com/RevenueCat/purchases-ios/assets/685609/70e1d939-8fa5-424c-8673-bb519f386118)

The red arrows are what this PR removes.

## Changes:
- `TransactionPoster` no longer has references to `IdentityManager` and
`Attribution`
- `markSyncedIfNeeded` is no longer in `TransactionPoster`, moved back
to `PurchasesOrchestrator`
- Changed `TransactionPoster`'s callback to return `Result<CustomerInfo,
BackendError>`, providing greater flexibility for users to be able to
read the specific errors.
- Extracted `PurchasedTransactionData` to be reduce the number of
parameters passed around. This removed the need for 2
`swiftlint:disable:next function_parameter_count`. This is the majority
of this diff. It was helpful to combine in this PR because I needed to
add another parameter to the flow: `unsyncedAttributes`, since
`TransactionPoster` could no longer read these directly.
2023-05-30 12:16:24 -07:00
NachoSoto 50ffbfb3e9 Performance: new check to ensure serialization / deserialization is done from background thread (#2496) 2023-05-19 11:58:12 -07:00
NachoSoto 99c8a34e62 Offline Entitlements: use offline-computed CustomerInfo when server is down (#2368)
### Changes:
- Created `PurchasedProductsFetcherType` to be able to mock and hold
fetchers, even on older devices
- Created `ProductEntitlementMappingFetcher` to abstract injecting
`ProductEntitlementMapping`
- Added logic to `CustomerInfoResponseHandler` to compute offline
`CustomerInfo` after a server error (see #2367). **This is used by all
`CustomerInfo` requests as well as `PostReceiptDataOperation`**.
- Wrote comprehensive `CustomerInfoResponseHandler` tests

### TODO:
- [x] Don't cache offline computed `CustomerInfo`: #2378
- [x] Integration tests: #2501.
2023-05-19 10:27:44 -07:00
NachoSoto 04d7a91024 PostReceiptDataOperation / GetCustomerInfoOperation: only invoke response handlers once (#2377) 2023-03-24 15:50:42 -07:00
NachoSoto d1c6fa23ee Offline Entitlements: added support for fetching ProductEntitlementMappingResponse in OfflineEntitlementsAPI (#2353) 2023-03-23 09:18:51 -07:00
NachoSoto da9ba397a3 Update CustomerInfo.requestDate from 304 responses (#2310)
### Purpose:

This is a prerequisite for #2288. The new grace period means that if we
didn't update the date from the cached responses, entitlements would
become stale and expired after 3 days.

### Changes:
- Added `HTTPResponseBody.copy(with newRequestDate:)` to be able to
modify responses from the header request date
- `CustomerInfo` implements this method to modify its request date
- Added `HTTPResponse.requestDate` to be able to keep track of the
request date from the server
- Added `VerificationResult.from(cache:response:)` to determine the most
restrictive verification result based on what's cached or checked from a
response
- Added `HTTPResponse.copy(with:)` to modify the verification result of
a response using the previous method
- `HTTPClient` now uses the most restrictive verification result
- `HTTPClient` updates request date from server responses or cached
responses (unless verification failed). This was the missing piece for
#2288.

### Tests:
- Verify that `ETagManager` does not use an ETag if verification was
previously not enabled
- Verify that `ETagManager` returns the request date from the server
when returning a cached response
- Verify that `HTTPClient` updates the request date from the server or
from a cached response
- Verify that `HTTPClient` does not update request date if verification
failed
- Tests for `HTTPResponse` request date parsing
- Test for `CustomerInfoResponseHandler` updating request date
- Tests for `CustomerInfo.copy(with newRequestDate:)`


### Other smaller changes:
- Moved `CustomerInfo.asData()` into `Encodable.asJSONEncodedData()`
- Renamed `requestTime` to `requestDate` everywhere for consistency
2023-03-01 09:24:32 -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 9a289e554f PostReceiptDataOperation: replaced receipt base64 with hash for cache key (#2199)
Should fix #2198 and [SDKONCALL-200]. This is a big performance
optimization in time and memory, as we no longer need to convert the
entire receipt to `base64` and concatenate it to the string, which is
what was likely causing #2198.
This now uses `Data.hashString`, which avoids any data copies and
efficiently creates a hash with the receipt content.

Apart from the new tests for this property,
`BackendPostReceiptDataTests.testDoesntCacheForDifferentReceipts`
ensures that different receipts have different cache keys, and therefore
this new implementation still avoids collisions (other than the
extremely unlikely hash collisions).


[SDKONCALL-200]:
https://revenuecats.atlassian.net/browse/SDKONCALL-200?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2023-01-12 11:48:32 -08:00
NachoSoto b3875a9380 Exposed PurchasesReceiptParser and AppleReceipt (#2153)
Initial step for [CSDK-17]. Fixes [CSDK-523].

### Changes:
- Renamed `ReceiptParser` to `PurchasesReceiptParser` to avoid name
collision with future SPM `ReceiptParser`
- Exposed `ReceiptParser`, `AppleReceipt`,
`PurchasesReceiptParser.Error`
- Added new types to `SwiftAPITester`
- Added docs to these types
- Moved `LoggerType` to be available to `ReceiptParser`
- Moved `DateFormatter+Extensions` to be available to `ReceiptParser`
- Moved `Codable+Extensions` to be available to `ReceiptParser`
- Created `ReceiptParserLogger` for `ReceiptParser`


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

[CSDK-523]:
https://revenuecats.atlassian.net/browse/CSDK-523?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-12-22 10:09:12 -08:00
NachoSoto c10af1c0d6 NetworkOperation: added assertion to ensure that subclasses call completion (#2138)
Looking into the leak in #2135, I realized that we have an operation
which doesn't call `completion`, we would be leaking the completion
block in the cache.
This ensures that it doesn't happen in tests. In production, we won't
risk crashing.
2022-12-21 08:15:52 -08:00
NachoSoto 1234c4ea91 CacheableNetworkOperation: avoid unnecessarily creating operations for cache hits (#2135)
### Changes:
- Created `CacheableNetworkOperationFactory` to abstract operation and
cache key creation
- Instead of always creating an operation, we create a factory first,
which pre-computes the cache key
- This cache key is used to determine if it's a cache hit, as well as
for the subsequently created operation

I'm still investigating a leak (#2105), and I suspect it's coming from
us creating and storing completion blocks, and those staying in memory
beyond the lifetime of the operation.
This refactor and performance improvement helps us avoid get more things
in memory than we need to.

See #2138 for how this is tested.
2022-12-21 08:13:41 -08:00
NachoSoto 8b50df4ba9 NetworkOperation: made Atomic references immutable (#2139) 2022-12-13 08:24:36 -08:00
NachoSoto 583e2a3a8a PostReceiptDataOperation: also print receipt data with verbose logs (#2127)
This was fine when we only had `debug` logs, but since #2080 we need to
check for `verbose` too.
Thanks to #2102 we can just use `<=`, because `LogLevel` conforms to
`Comparable`.
2022-12-13 08:23:12 -08:00
NachoSoto 8b4a9ecbca PostReceiptDataOperation: log Apple error when purchase equals expiration date (#2038)
A large amount of flaky integration tests are due to this bug, so
detecting it early in the logs will help debug this.
Radar: `FB11767559`.

`productType` is missing in `StoreKitTest` purchases, so I changed
`isSubscription` to default to checking if there is an expiration when
the type is `.unknown`.
To simplify the code, I changed `ProductType?` to be non-optional, since
we already have `.unknown` as a case.

### Example:
```
[00:27:56]: ▸ 2022-11-09 00:27:56.000533+0000 BackendIntegrationTestsHostApp[3942:17000] [Purchases] - DEBUG: ℹ️ PostReceiptDataOperation: Posting receipt: {
[00:27:56]: ▸   "opaque_value" : "1n+XXw8AAAA=",
[00:27:56]: ▸   "sha1_hash" : "unR7vAzPaUlB0TtZ09z5jMyqqAs=",
[00:27:56]: ▸   "bundle_id" : "com.revenuecat.StoreKitTestApp",
[00:27:56]: ▸   "in_app_purchases" : [
[00:27:56]: ▸     {
[00:27:56]: ▸       "quantity" : 1,
[00:27:56]: ▸       "product_id" : "com.revenuecat.weekly_1.99.3_day_intro",
[00:27:56]: ▸       "purchase_date" : "2022-11-09T00:27:50Z",
[00:27:56]: ▸       "transaction_id" : "0",
[00:27:56]: ▸       "is_in_intro_offer_period" : true,
[00:27:56]: ▸       "expires_date" : "2022-11-09T00:27:50Z"
[00:27:56]: ▸     },
[00:27:56]: ▸     {
[00:27:56]: ▸       "product_id" : "com.revenuecat.weekly_1.99.3_day_intro",
[00:27:56]: ▸       "quantity" : 1,
[00:27:56]: ▸       "transaction_id" : "1",
[00:27:56]: ▸       "is_in_intro_offer_period" : false,
[00:27:56]: ▸       "expires_date" : "2022-11-09T00:28:00Z",
[00:27:56]: ▸       "original_purchase_date" : "2022-11-09T00:27:50Z",
[00:27:56]: ▸       "original_transaction_id" : "0",
[00:27:56]: ▸       "purchase_date" : "2022-11-09T00:27:50Z"
[00:27:56]: ▸     }
[00:27:56]: ▸   ],
[00:27:56]: ▸   "application_version" : "1",
[00:27:56]: ▸   "creation_date" : "2022-11-09T00:27:55Z",
[00:27:56]: ▸   "expiration_date" : "4001-01-01T00:00:00Z"
[00:27:56]: ▸ }
[00:27:56]: ▸ 2022-11-09 00:27:56.000710+0000 BackendIntegrationTestsHostApp[3942:17000] [Purchases] - ERROR: 🍎‼️ Receipt for product 'com.revenuecat.weekly_1.99.3_day_intro' has the same purchase (2022-11-09 00:27:50 +0000) and expiration (2022-11-09 00:27:50 +0000) dates. This is likely a StoreKit bug.
```
2022-11-09 11:18:52 -08:00
NachoSoto f26bc648b0 NetworkOperation: workaround for iOS 12 crashes (#2008)
This _might_ fix [CSDK-503].

The latest stacktrace provided gave me a clue:
```
Fatal error: Should never be reached: file /BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang_overlay_Foundation_Device/swiftlang-1001.2.63.13/swift/stdlib/public/SDK/Foundation/NSObject.swift, line 46
0xa3d10 @objc static NSObject.__old_unswizzled_keyPathsForValuesAffectingValue(forKey:) + 84
53  libswiftFoundation.dylib       0xa436c static __KVOKeyPathBridgeMachinery.keyPathsForValuesAffectingValue(forKey:) + 272
64  libswiftFoundation.dylib       0xa4864 @objc static __KVOKeyPathBridgeMachinery.keyPathsForValuesAffectingValue(forKey:) + 88
```

That crash is coming from
[here](https://github.com/apple/swift-corelibs-foundation/blob/swift-DEVELOPMENT-SNAPSHOT-2022-10-24-a/Darwin/Foundation-swiftoverlay/NSObject.swift#L47).
KVO shipped broken on Swift at least throughout iOS 12.x (see
https://github.com/apple/swift-corelibs-foundation/issues/3742)

Thanks to @jckarter for [suggesting this
workaround](https://twitter.com/jckarter/status/1586029206646898688?s=61&t=LJyPWVeXcT9TfIL2zMjFng).

[CSDK-503]:
https://revenuecats.atlassian.net/browse/CSDK-503?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-10-28 11:59:19 -07:00
NachoSoto 46d9366fa4 PostReceiptDataOperation: added initiationSource parameter (#1957)
For [TRIAGE-82].

[TRIAGE-82]:
https://revenuecats.atlassian.net/browse/TRIAGE-82?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-10-07 08:21:30 -07:00
NachoSoto b32859d0c8 PostReceiptDataOperation: print receipt data if debug logs are enabled (#1940)
For [CSDK-478].
Follow up to #1929.

### Example:
> DEBUG: ℹ️ PostReceiptDataOperation: Started
INFO: ℹ️ Receipt parsed successfully
DEBUG: ℹ️ PostReceiptDataOperation: Posting receipt: {
  "opaque_value" : "Rn\/39QYAAAA=",
  "sha1_hash" : "Ii8Z7ocZm524eJcWFOvVauNrNHs=",
  "bundle_id" : "com.revenuecat.StoreKitTestApp",
  "in_app_purchases" : [
    {
      "quantity" : 1,
      "product_id" : "com.revenuecat.monthly_4.99.1_week_intro",
      "purchase_date" : "2022-09-27T19:09:47Z",
      "transaction_id" : "0",
      "is_in_intro_offer_period" : true,
      "expires_date" : "2022-09-27T19:09:57Z"
    }
  ],
  "application_version" : "1",
  "creation_date" : "2022-09-27T19:09:47Z",
  "expiration_date" : "4001-01-01T00:00:00Z"
}
DEBUG: ℹ️ There are no requests currently running, starting request POST
receipts
DEBUG: ℹ️ API request started: POST /v1/receipts
DEBUG: ℹ️ API request completed: POST /v1/receipts 200
DEBUG: ℹ️ PostReceiptDataOperation: Finished
DEBUG: ℹ️ Serial request done: POST receipts, 0 requests left in the
queue
DEBUG: ℹ️ Sending updated CustomerInfo to delegate.
INFO: 😻💰 Purchased product - 'com.revenuecat.monthly_4.99.1_week_intro'
INFO: 💰 Finishing transaction com.revenuecat.monthly_4.99.1_week_intro 0
()

[CSDK-478]:
https://revenuecats.atlassian.net/browse/CSDK-478?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-09-27 12:50:34 -07:00
NachoSoto 43d474d813 GetCustomerInfoAPI: avoid making a request if there's any PostReceiptDataOperation in progress (#1911)
Fixes [CSDK-419].

This is an improvement over the fix in #1292. In there, we avoided
appending on an existing `CustomerInfoOperation` if there were any
`PostReceiptDataOperation`s in progress, because those might come back
with outdated entitlements.
This fix removes that cache key hack, and instead *reuses* the entire
response to that `PostReceiptDataOperation` by "stealing" that request's
cache key.

This is perfectly captured by the existing test
`BackendPostReceiptDataTests.testGetsUpdatedSubscriberInfoAfterPost`,
added in #1292. Amazingly enough, that test still passes with one minor
change: the entire process requires one fewer API call 🎉 🐼

[CSDK-419]:
https://revenuecats.atlassian.net/browse/CSDK-419?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-09-21 13:34:43 -07:00
NachoSoto 0f99e94836 CallbackCache: simplified implementation using Atomic (#1834)
We don't actually need to share a single queue to synchronize all the callback caches.
This new implementation also avoids using the risky `DispatchQueue.sync`.

This also fixes potential issues because `cachedCallbacksByKey` was exposed, and it could have been read without synchronizing access with the queue. This is now impossible.
2022-08-15 15:10:50 -07:00
NachoSoto 49c824efc8 Atomic: ExpressibleByBooleanLiteral (#1822)
Equivalent to #1804.
2022-08-15 11:04:01 -07:00
343ba7eaf4 AdServices (#1727)
* [CF-106] Fetch AdServices Token (#1519)

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>

* [CF-553] Post AdServices token (#1534)

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
Co-authored-by: Josh Holtz <me@joshholtz.com>

* Update ios 14 snapshots tets

* Reworked adservices to use new Purchases.shared.attribution API

* Updated with some comments from PR

* Add iAd code back (#1739)

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

* use new mapKeys dictionary extension for device cache

* update for some pr comments

* remove purchasestests

* generate missing snapshots

* fix deprecation messages to clarify method now called on shared instance

* Fix for inserting nonstring into userdefaults

* Add tests for bug

* fix lint

* Added availablility checks for tests and removed unused varialbes

* Whoops... wrong class

Co-authored-by: beylmk <maddie@revenuecat.com>
Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
2022-08-08 07:42:42 -05: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 a2fc04c09e SubscriberAttribute: converted into struct (#1648)
### Motivation:

Initially I wanted to make `SubscriberAttribute.asDictionary` work using `Codable`. However, this isn't the right refactor because we need the actual raw types to be encoded inside `UserDefaults`.
At least this refactor provides several improvements:

### Changes:

- `SubscriberAttribute` now has value semantics
- ⚠️  `SubscriberAttribute.isSynced` used to be mutable, with reference semantics, which was dangerous
- `SubscriberAttribute: Equatable` implementation is now automatic
- Cleaned up `SubscriberAttribute.asDictionary` implementation, extracting keys into an `enum`
- Moved `Dictionary` -> `SubscriberAttribute` creation to the type itself
- ⚠️ `SubscriberAttribute.init(dictionary:)` returns an `Optional` instead of using force-unwrap, avoiding potential crashes
- Added more serialization / deserialization tests
- Removed `SubscriberAttributesMarshaller` and moved method into `SubscriberAttribute` extension
- Moved `SubscriberAttributeDict` into `SubscriberAttribute.Dictionary`
2022-06-17 10:00:51 -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