Commit Graph
77 Commits
Author SHA1 Message Date
Antonio Pallares a3df5dd229 Prevent duplicate post receipt requests (#5795)
* Prevent duplicate post receipt requests caused by timestamp-only attribute changes in the `consentStatus` SubscriberAttribute

* unit tests

* More tests

* Simplified MockDateProvider

* Add unit test covering edge case

* Fix lint
2025-11-17 09:25:08 +01:00
Antonio PallaresandRevenueCat Git Bot 510f4e73a4 Add internal Offerings source properties (#5749)
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`

Keeps the values also when returned by the ETagManager

* lint

* Add some tests for ETagManager

* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues

* Add tests for actual source of original cached response of ETagManager

* Add tests to HTTPClientTests regarding the new booleans

* lint

* Add `originalSource` to `CustomerInfo`

* Adds `isLoadedFromCache` boolean to CustomerInfo

* Add docs

* Unit tests for new customer info properties

* Make `OriginalSource.CustomerInfo` a `String` enum

* Add decoding tests for Customer Info with new original source field

* Add `.loadShedder` check for Customer Info obtained in Load Shedder integration tests

* Update fixture

* improve internal docs

* Add intermediate object `Offerings.Contents` to wrap `OfferingsResponse` + `OriginalSource` and `loadedFromCache`

* Adapt existing unit tests to changes in `Offerings` underlying objects

* Check Offerings original source in existing integration tests

* Fix StoreKit unit tests

* Fix RevenueCatUI tests

* More Offerings.Contents tests

* Fix test

* Add more tests

* Fix lint

* [skip ci] Generating new test snapshots (#5741)

* [skip ci] Generating new test snapshots (#5742)

* [skip ci] Generating new test snapshots (#5743)

* [skip ci] Generating new test snapshots (#5744)

* [skip ci] Generating new test snapshots (#5745)

* [skip ci] Generating new test snapshots (#5746)

* [skip ci] Generating new test snapshots (#5747)

* [skip ci] Generating new test snapshots (#5748)

* Use enum for `HTTPResponseSource`

* Update tests

* Adapt to changes in `VerifiedHTTPResponse`

* Lint

* Adapt to changes in `VerifiedHTTPResponse`

* update tests

* Add log when a request to the fallback URL was handled by load shedder (which should never happen)

* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`

* Adapt to changes

* fix lint

* Adapt to changes

* Fix CustomerInfo's OriginalSource implementation + fix unit test

* Fix tests compilation

* Fix RCUI compilation

* Move isFromCache boolean from `Offerings.Contents` to `Offerings`

* Fix compilation of `DebugViewSwiftUITests`

* Fix compilation of RevenueCatUI tests

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-11-06 14:12:42 +01:00
Antonio Pallares 8b942f9fd9 Add Internal CustomerInfo source properties (#5737)
* Add `isLoadShedderResponse` and `isFallbackURLResponse` properties to `VerifiedHTTPResponse`

Keeps the values also when returned by the ETagManager

* lint

* Add some tests for ETagManager

* Rename `isFallbackURLResponse` to `isFallbackUrlResponse` to prevent decoding issues

* Add tests for actual source of original cached response of ETagManager

* Add tests to HTTPClientTests regarding the new booleans

* lint

* Add `originalSource` to `CustomerInfo`

* Adds `isLoadedFromCache` boolean to CustomerInfo

* Add docs

* Unit tests for new customer info properties

* Make `OriginalSource.CustomerInfo` a `String` enum

* Add decoding tests for Customer Info with new original source field

* Add `.loadShedder` check for Customer Info obtained in Load Shedder integration tests

* Update fixture

* improve internal docs

* Fix test

* Fix lint

* Use enum for `HTTPResponseSource`

* Update tests

* Adapt to changes in `VerifiedHTTPResponse`

* Lint

* update tests

* Add log when a request to the fallback URL was handled by load shedder (which should never happen)

* rename `HTTPResponseSource` to `HTTPResponseOriginalSource`

* Adapt to changes

* fix lint

* Fix CustomerInfo's OriginalSource implementation + fix unit test
2025-11-06 13:27:46 +01:00
RickandRevenueCat Git Bot 1c24d76d6f Update Xcode 16.0 to 16.4 in tests because of CircleCI's deprecation later this year (#5668)
* Update Xcode 16.0 to 16.4 in tests because of CircleCI's deprecation later this year

* Updated test simulators for Xcode 16.4

* Fixed unused variable warning

* [skip ci] Generating new test snapshots (#5670)

* [skip ci] Generating new test snapshots (#5673)

* [skip ci] Generating new test snapshots (#5672)

* Updated iOS simulator version after rebase

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-10-17 10:24:22 +02:00
Antonio Pallares 2bd289a993 Add GetWebBillingProductsOperation (#5419)
* Add validation logic for Test Store API keys under the TEST_STORE compiler flag

* add unit test for test store api key validation

* Move API key validation to `configure` and store validation result in `SystemInfo`

* start new flow for purchase of `TestStoreProduct`s

* WIP: implement alert for purchase of a Test Store product

* Add file to Xcode workspace

* WIP: implement fake purchase alert in all platforms and OSs

* Enforce only one test purchases at once. Use constants for test purchase alert strings

* Place new test store purchase APIs under the TEST_STORE compiler flag

* fix lint

* fix unit tests after change of internal APIs

* PR comments

* temporarily return hardcoded `CustomerInfo` on confirmed test purchase

* tests: adapt unit tests to new `TestStorePurchaseHandler` dependency of `PurchasesOrchestrator`

* fix lint

* Fix backend integration tests compilation

* Remove deprecation warning

* remove unnecessary OSs checks

Since the minimum supported versions of the SDK are iOS 13 and tvOS 13

* add `GetWebProductsOperation`

* avoid duplicating decoding objects from WebOfferingProductsResponse and WebProductsResponse

* add missing tests snapshots

* Add `WebProductsResponse.ProductType` enum

* Fix tests

* add web products decoding tests

* Rename WebProducts to WebBillingProducts

* (more) rename WebProducts to WebBillingProducts

* (even more) rename WebProducts to WebBillingProducts

* Move `getWebBillingProducts` outside of `OfferingsAPI` into a new `WebBillingAPI` object

* improve test

* remove unnecessary `sorted()` of `Set`

* fix lint

* fix tests

Since the GetWebBilling request path contains an unordered set of productIds, which the tests do not support, we're only doing snapshot tests with 1 product ID

* fix more tests
2025-07-29 18:31:15 +02:00
Antonio Pallares b7c98a0783 Rename internal WebProducts APIs to WebOfferingProducts (#5416)
* Rename existing WebProducts to WebOfferingProducts

* fix lint

* fix wrong file reference
2025-07-23 13:56:20 +02:00
Pol Piella Abadia 5bfbe3b703 Fixes API tests after changes to public API (#5365) 2025-07-07 18:18:53 +01:00
Will Taylor 01a352d9e9 Virtual Currency Support (#5108) 2025-07-07 12:11:27 -05:00
Pol Piella Abadia 39ad5519fd [DX-457] Re-apply Health SDK logging on app launch (#5360)
* Revert "Temporary revert of automatic health reporting in the SDK (#5353)"

This reverts commit dd7086fd98.

* Do not run health check when running integration tests
2025-07-07 17:00:54 +01:00
Pol Piella Abadia dd7086fd98 Temporary revert of automatic health reporting in the SDK (#5353)
* Revert "fix `Purchases` temporary leak when running SDK health check (#5350)"

This reverts commit 26de54308c.

* Revert "[DX-457] Log the SDK configuration report on every `#DEBUG` run (#5317)"

This reverts commit 21f226de00.
2025-07-03 13:22:47 +01:00
Pol Piella Abadia 21f226de00 [DX-457] Log the SDK configuration report on every #DEBUG run (#5317)
* Creates a brand new SDK Manager to reuse logic across different classes

* Updates to use the new manager

* Adds a new method to log the health report to the console only for debug builds

* Run health check for debug builds

* Fixes sendable warnings that got introduced with new manager

* Fixes swiftlint violations

* Adds unit tests for the new SDK Health manager

* Creates a new authorization provider

* Injects the new authorization provider

* Tidies and cleans up unit tests

* Adds a new configuration value

* Updates configuration unit tests

* Makes sure that values are only set on DEBUG configuration

* Reverts state of default value

* Updates wording of output message

* Fixes the wrong wording of error message

* Logs the word RevenueCat along with report title

* Fixes wording issues in error messages

* Addresses PR comments

* Adds extra checks

* Renames configuration value

* Removes unnecessary `Task`

* Renames parameter

* Calls the new endpoint in the BE to check if the app is available or not

* Do not log or perform the report operations if it is not available

* Missing xcodeproj changes

* Adds unit test coverage

* Turns validation on by default

* Creates a new path for the report availability endpoint

* We need to bypass auth but make sure that requests are still signed

* Updates the name of the response field

* Updates unit tests target with new response structure

* Handles no credentials case more gracefully

* Adds new unit tests for the new No ASC credentials warning

* Updates the file's header comment

* Adds documentation

* Wraps calls in necessary compiler directives

* Hide all report-related content behind compiler directives

* Makes more health-related functionality DEBUG only

* Addresses PR comments

* Updates all unit test build failures

* Gets it back to compile on `CUSTOM_ENTITLEMENT_COMPUTATION`

* Removes configuration value

* Updates logging to only include issues

* Adds a mock method for the health report availability request
2025-07-02 14:00:48 +01:00
Toni RicoandRevenueCat Git Bot 46f2cd8c29 Change to use new endpoint to fetch web product info (#5135)
* Change to use new endpoint to fetch web product info

* [skip ci] Generating new test snapshots (#5142)

* [skip ci] Generating new test snapshots (#5141)

* [skip ci] Generating new test snapshots (#5140)

* [skip ci] Generating new test snapshots (#5139)

* [skip ci] Generating new test snapshots (#5138)

* [skip ci] Generating new test snapshots (#5137)

* [skip ci] Generating new test snapshots (#5136)

* Add small utility function to know if offerings has any web biling offering available

---------

Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
2025-05-21 13:37:56 +02:00
Toni Rico c7edb46f31 Add endpoint for fetching web billing products (#5055)
* Add support for fetching web billing products

* Some fixes

* Escape product ids

* Fixes from review

* Support etags

* Change to Int64

* Add method to MockOfferingsAPI
2025-05-06 23:28:59 +02:00
47f40f5abe [DX-345] Update the testSDKHealth to use the new /health_report endpoint (#4979)
* Initial app creation

* Initial screen

* Installs and configures the SDK

* Creates a full Swift 6 compliant ViewModel

* Adds a demo customer center button

* Fixes icon name

* Updates a bit of the logic to fetch offerings

* Updates initial layout by showing offerings

* Makes more progress with a VERY DRAFT and VERY WIP screen

* Creates a new network path and requests

* Creates a new health API and adds it to the BE class

* Creates a `checkOfferings` method

* Pushes local changes

* Moved viewmodels to separate files

* Prepared some basic UI structure

* Initial shell of the new method for testing

* Initial implementation of `HealthReportOperation` from the SDK

* Passes `appUserId` through to the internal API

* Adds a new path called `appUserID`

* Removes 'old' `appHealth` endpoint

* Creates a new model for the network response

* Report should be the response type

* Updates backend request

* Updates types and documentation for the new internal request

* Moves validation into its own file

* Replaces method for DebugViewModel

* Updates error messages

* Updates the `Status` section

* Adds logic to show failure reason

* Removes Sample App from PR

* Resets `Package.resolved`

* Availability checks

* Check if device is authorized to make payments

* Fixes all SwiftLint errors

* Updates `ProductStatus` to not have a raw value of String

* Minor style fixes

* Removes warning

* Adds more availability checks

* Fixes comments

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

* Simplifies logic

* Updates comment

* Updates the comments to denote that this method is not intended for production use

* Removes android-only invalid SDK version error

* Adds an action button that the user can use to open the dashboard

* Updates copy about non-actionable item

* Prefers `Your RevenueCat Configuration` to `The Dashboard`

* Removes `Sendable`-related warnings

* Fixes unit tests on CI/CD

* Fixes tests

* Adds unit test for offering configuration error

* Adds unit test for no packages in default offering

* Adds unit test for generic offering configuration

* Removes periods from the end of the error messages

* Adds unit tests for all new errors

* Fixes linter errors

* Wrap in a `DEBUG` compiler directive

* Includes all products in the healthy check status to be used in the Sample App

* Updates report to include product and offering information

* Moves logic over to the report rather than being exclusive to the Debug View

* Fixes CI/CD failures

* Updates the asset for the health check status

---------

Co-authored-by: Hidde van der Ploeg <hiddevanderploeg@me.com>
Co-authored-by: Antonio Pallares <ajpallares@users.noreply.github.com>
2025-04-24 13:46:21 +00: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
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