Commit Graph
184 Commits
Author SHA1 Message Date
Antonio Pallares f7fa5d2e81 [Diagnostics] Add extra parameters to applePurchaseAttempt (#4835)
* feat: add missing properties to `apple_purchase_attempt` diagnostics event

* tests: add + edit unit tests for new diagnostics parameters for SK1 purchase attempt

* fix implementation of sk2 product purchase to catch promotional offer signature error again

* tests: edit unit tests for new diagnostics parameters for SK2 purchase attempt

* fix failing test

* refactor: separate error handling in purchase SK2 product method into its own method

* address PR #4835 comments

* lint
2025-03-03 14:38:00 +01:00
Facundo Menzella fde4e13cd4 chore: Add integration test for analytics events (#4830) 2025-02-28 14:55:32 +01:00
AJPallares 788b6276dd UI Preview Mode: app user ID (#4725)
* feat: use fix, hardcoded `appUserID` for UI preview mode

* test: add unit tests for user ID in UI preview mode

* fix linter's Trailing Whitespace Violations

* PR #4725 comments

* Change default appUserID for UI Preview mode.
* Add debug log regarding the use of UI Preview mode user ID.
2025-01-30 12:14:30 +01:00
Toni Rico 511ed4ae10 [Trusted Entitlements] Do not clear CustomerInfo upon enabling Trusted Entitlements (#4671)
* [Trusted Entitlements] Do not clear CustomerInfo upon enabling Trusted Entitlements

* improve test

* Improve naming consistency with Android
2025-01-16 16:48:43 +01:00
Cesar de la Vega afa9f446d6 Create CustomerCenterEvent (#4392) 2024-12-02 15:43:53 +01:00
Cesar de la Vega fd4af48853 Fix PaywallEvents failing to deserialize (#4520) 2024-11-27 11:54:54 +01:00
98e4a33225 Support anonymous web purchase redemptions (#4439)
Co-authored-by: RevenueCat Git Bot <72824662+RCGitBot@users.noreply.github.com>
Co-authored-by: andy boedo <andresboedo@gmail.com>
Co-authored-by: Cesar de la Vega <cesarvegaro@gmail.com>
2024-11-25 16:14:32 +01:00
Will Taylor 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
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
Cesar de la Vega ed8fdde00b Refactor Paywall events so it can be used for customer center (#4376) 2024-11-05 15:23:26 +01:00
Josh Holtz 1fa69d42f4 [Paywalls] Improve locale consistency (#4158)
⚠️ Waiting for #4188 to be merged first (because needs all the tests to
pass)

## Motivation

Fix cases where paywall was showing multiple languages at once and
random languages.

## Description

👉 For a demo of the problem and solution, look in Slack in the paywalls
or SDK channel for the latest Loom that I posted

### Flow of paywall localization logic (since it can be confusing)

1.
[PaywallView](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-ea75fe7280803e15b975d702a16c6851ffc2bbed01b7b196bf0fb80d75b4b332)
starts with `Locale.current` but then calls
`offering.validatedPaywall()` which will return the new
`displayedLocale: Locale` variable
2.
[PaywallData+Validation](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-452790cf5287a44f75a4b2260254db2281a066faeea2b3e441a007ec60522b6b)
returns `displayedLocale` if its a valid paywall, otherwise it return
`Locale.current` for fallback paywall
3.
[PaywallData+Localization](https://github.com/RevenueCat/purchases-ios/pull/4158/files#diff-e620baead94f7acad56d931c485d7691c4f909be3fe625e104528a2702cca2a0)
has a lot of the new logic
- New `locale: Locale` computer property that it determines the locale
from the `localizedConfiguration` or `localizedConfigurationsByTier`
- New ` defaultLocalizedConfiguration` that uses the developer set
`defaultLocale` (from `PaywallData`) to find the default localization
- Updated `localizedConfiguration<Value>()` to take a new
`defaultLocalization` to put in its priority (and log it if its used).
If for some reason that's not found, it will use the legacy
`fallbackLocalization` (which is the first localization it finds or
english)
4. After all of that, the `displayedLocale` is passed into the actually
SwiftUI `LoadedOfferingPaywallView` to show this locale everywhere

### Problem 1 - Inconsistent languages displayed at once

The user defined localizations and the pre-localized content in the SDK
(like links in the footer and package duration names) could should
different localizations. The choice of which locale was happening in two
different spots.

Since the SDK has all possible localizations that the dashboard offers,
the locale SDK displays from the dashboard will be chosen for the
pre-localized content.

### Problem 2 - Showing random languages if not supported

We decided early on to not use a `default_locale` but that has hurt us.
The solution to use was use `localizations.first` to show the first
locale in the list and the intention that this was the first locale the
user did in the paywall. That was not the case, however. The order of
the localizations list was indeterministic so it should should a random
language (even to the point where different random languages would show
the the same user).

We are now introducing a `default_locale` that the developer can set in
the paywall. If the locale the user is requesting is not translated by
the developer, it will use the `default_locale`

### Problem 3 - Footer not using correct localization from the bundle

There were times where some parts of the footer itself (mainly the
restore button) would be a different language than the terms of service
and privacy policy buttons. This was due to a code issue where we were
using `Bundle.module`. Instead, we needed to specify a localization
bundle specifically that we wanted to use.
2024-08-20 09:44:27 -05:00
Will Taylor a4055375f6 retry requests with HTTP status 429 2024-07-23 12:41:16 -05:00
NachoSoto 2bcd0ce328 5.0.0 changes: support SK2 by default 2024-07-01 14:37:21 -05:00
Cesar de la Vega bc6ccfc7c3 Remove AdClient framework related code (#3993)
We had a report of a developer that is seeing that users (who are using
iOS 18 beta) are reporting app crashes on launch with 55 unique devices
so far. They are using using iOS SDK 4.17.7.

The line that is crashing is:

```
client = clientClass.perform(#selector(FakeAfficheClient.sharedClient)).takeUnretainedValue()
```

We just couldn’t have a single variable named `adClient` without an app
getting rejected, even if it wasn’t using it at all. We built the
`AfficheClient` so prevent app rejections.

It turns out Apple has removed documentation for `AdClient`, and by
looking into
https://developer.apple.com/documentation/iad/setting-up-apple-search-ads-attribution#overview

> After February 7, 2023, all requests made to the Apple Search Ads iAd
Attribution API will return with a value of "iad-attribution" = false,
or errors. See ADClient/requestAttributionDetails(_:). Use the
AdServices framework for current attribution integration with the Apple
Search Ads Campaign Management API for devices using iOS 14.3 and later.
Attribution isn't available for downloads and redownloads from devices
using iOS 14.2 or earlier.

So this wasn't even working anyways. I removed everything related but
left the public API in place to not break anyone.
2024-06-25 23:16:14 +02:00
Toni Rico ffe5b8b363 Diagnostics: Add logic to retry on server errors (#3930)
### Description
This will make sure that we delete the diagnostics file if there is an
issue syncing it over multiple retries (note these retries only happen
during SDK configuration).
2024-06-03 13:57:59 +02:00
Toni Rico 0a534c8260 Clear diagnostics file if we detect it's too big during event tracking (#3824)
### Description
This makes it so, if we detect the diagnostics file gets too big while
tracking an event, we just clear it and start over. Additionally, we add
an event tracking that this happened to the diagnostics file.
2024-05-10 17:00:28 +02:00
Cesar de la Vega a010104e8e Syncs diagnostics on initialization (#3821)
Adds a call to syncDiagnosticsIfNeeded on initialization
2024-04-19 14:29:00 +02:00
c55c03d678 Add DiagnosticsSynchronizer (#3787)
Adds capability to synchronize diagnostics events from the JSONL file to
the backend

---------

Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
Co-authored-by: James <109382862+jamesrb1@users.noreply.github.com>
2024-04-16 13:18:31 +02:00
Cesar de la VegaandToni Rico 74d3960262 Create DiagnosticsTracker (#3784)
Create `DiagnosticsTracker` and adds it to the `Purchases`
initialization. I don't expose the `diagnosticsEnabled` yet

---------

Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
2024-04-12 11:47:57 +02:00
BurdockandJosh Holtz ff68b8a3e4 New syncAttributesAndOfferingsIfNeeded method (#3709)
### Motivation

Adding new method to manually sync attributes and fetch fresh offerings 

### Description

- New `Purchases.shared.syncAttributesAndOfferingsIfNeeded()` method
- Syncs subscriber attributes and then fetches fresh offerings from
network
- This is designed to be a blocking call since developer will want fresh
offerings after syncing subscriber attributes
- The method is rate limited to 5 calls per minute (new `RateLimiter`
class()

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-02-28 16:13:23 -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
Josh Holtz 67d57a1942 New Package.presentedOfferingContext (#3712)
This was already approved and merged into `5.0-dev` but bringing this
into `4.x` (see #3690)

### Motivation

Allow for storing of other presented offering context on a `Package`

### Description

- Replaced `offeringIdentifier: String` on `Package` with new
`presentedOfferingContext: PresentedOfferingContext`
  - Currently it only has a `offeringIdentifier: String`
- Eventually this will add new optional properties for targeting and
placements
2024-02-28 13:53:42 -06:00
NachoSoto 6ef1e5d521 Paywalls: prioritize Locale.current over Locale.preferredLocales (#3657)
Fixes #3655.
Follow up to #3633.

This improves upon that PR by using `Locale.current.removingRegion` for
each locale **before** trying to look up `Locale.preferredLocales`.

This fixes this scenario:
- Configure your phone with: `en_IN` and `es_ES`
- Launch paywall with localization `en_US` and `es_ES`

Prior to this change, we'd be looking up localizations in this order:
- `en_IN`
- `es_ES`
- `en` (no region)

Because there _is_ a localization for `es_ES` we'd return that one
before attempting to look up `Locale.current.removingRegion`.

Example of the new log:
> VERBOSE: Looking up localized configuration for ["en_IN", "es_ES"],
searching for ["en_IN", "en", "es_ES", "es"]
> VERBOSE: Found localized configuration for 'en'
2024-02-08 08:58:25 -08:00
NachoSoto 62e8f19962 Paywalls: add logs for localization lookup (#3649)
Will make debugging issues like #3633 and
https://github.com/RevenueCat/purchases-ios/pull/3633#issuecomment-1931848279
easier.

```
VERBOSE: Looking up localized configuration for ["de_CH", "de"]
VERBOSE: Found localized configuration for 'de'
```
2024-02-07 11:08:22 -08:00
NachoSoto 1cea9978aa Trusted Entitlements: tests for signature verification without header hash (#3505)
Follow up to #3424. This adds coverage to ensure that the backend
continues to sign correctly for old SDK versions that don't support
this.
2023-12-21 12:47:34 -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
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
NachoSoto 0aa950a99c Integration Tests: new logs for troubleshooting flaky tests (#3328)
This modifies `BaseStoreKitIntegrationTests.printReceipt` to also log
`StoreKit 2` transactions which can be used to debug failures of
`OfflineStoreKitIntegrationTests`.
`PurchasedProductsFetcher` also logs how many `currentEntitlements` it
found since failures are coming from there.
2023-10-24 09:41:25 -07:00
NachoSoto a9ae7bdbb0 StoreKit 1: disabled finishTransactions log on observer mode (#3314) 2023-10-20 08:35:24 -07:00
NachoSoto ebfb19a736 StoreKit 1: improved debug log for finishTransactions invoked outside the SDK (#3300)
See
https://github.com/RevenueCat/purchases-ios/issues/2415#issuecomment-1579324706

This should make it more obvious for anyone running into this issue.
2023-10-16 09:29:10 -07:00
Toni RicoandNachoSoto 773fd84a65 StoreKit In App messages support (#3252)
### Description
StoreKit displays different types of in-app messages on app launch, but
it provides an API to disable that and show these messages at a
different time. This PR wraps those APIs to make them easier to use
through our SDK.

#### New APIs
- Add new configuration parameter to defer StoreKit's automatic in-app
messages
- Add new method in `Purchases` to display a list of specific message
types
- New `StoreKitMessageType` enum mapping to the types used by StoreKit

---------

Co-authored-by: NachoSoto <NachoSoto@users.noreply.github.com>
2023-10-03 16:58:53 +02:00
NachoSoto 7fa0b265f3 Configuration: log warning if attempting to use observer mode with StoreKit 2 (#3066)
Since #3032 developers using observer mode need to configure the SDK
with the correct StoreKit 2 setting.

A future API will allow users to sync transactions made with StoreKit2.
2023-09-19 13:21:09 -07:00
NachoSoto dc628a2aaa Paywalls: events unit and integration tests (#3169)
### Changes:
- Added `PurchasesOrchestrator` tests for paywall data sent through post
receipt
- Fixed and tested state issue with cached paywall data and failed
purchases
- Added `Integration Tests` for tracking and flushing events
- Configured `Integration Tests` with a custom documents directory to
ensure it's empty on every test invocation
- Changed deployment target on `Integration Tests` to iOS 16 to simplify
code
- Setting `Purchases.logLevel` before configuring purchases on
`Integration Tests`
- Added assertion to ensure `FileHandler` never runs on the main thread
2023-09-14 18:28:11 -07: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 24d843e753 Paywalls: created PaywallEventsManager (#3159)
This combines the types created in prior PRs (#3157, #3158) to store and
post events.
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 e93ebf6e9c Paywalls: warm-up image cache (#2978)
This relies on `AsyncImage` using `URLSession.shared`

I've tested this, and the app doesn't even make a request the second
time around because the images are caches on disk.
2023-09-14 18:28:11 -07:00
NachoSoto c353d8dfd4 Paywalls: extracted PaywallCacheWarming (#2977)
This refactors #2860 extracting it to a separate type so we can easily
extend it to pre-warm the image cache as well (PWL-10).
2023-09-14 18:28:11 -07:00
NachoSoto 065fbb8192 Paywalls: pre-fetch intro eligibility for paywalls (#2860) 2023-09-14 18:28:11 -07:00
NachoSoto 341f0d340c Integration Tests: improve flaky tests (#3163)
We get a lot of flaky failures on `OfflineStoreKitIntegrationTests`
(example:
https://app.circleci.com/pipelines/github/RevenueCat/purchases-ios/14843/workflows/a3cffa7c-da25-4811-931d-fc241befee0f/jobs/118214/tests).
I managed to reproduce locally running some of these 100+ times on a
loop until they failed.

I traced the issue to this sequence:
- SK2 purchase
- `StoreKit.Transaction.unfinished` is empty

This is likely due to a race condition. In practice, when computing
offline `CustomerInfo`, we never check it right away, since we need to
wait for the server to return a 500.

I filed a Radar for this: `FB13133387`.

To make tests more realistic, I introduced a delay in `HTTPClient`
returning fake 5xx errors. I haven't been able to reproduce this issue
again, so this should make `OfflineStoreKitIntegrationTests` more
reliable.

I've also added a new debug log that could help debug similar issues,
because `Strings.offline.computed_offline_customer_info` was not very
complete.
2023-09-07 12:25:19 -07:00
NachoSoto 5527f884af Codable: improved decoding errors (#3153) 2023-09-07 07:19:07 -07:00
NachoSoto e8f389a153 PurchasesOrchestrator: fixed callback not invoked regression during downgrades (#3028)
Fixes #3020.
This essentially reverts #2890.

As shown in #3020, downgrades are reported with the same transaction
identifier as the original transaction. Therefore our approach in #2890
is invalid.
We considered checking whether the `SKPayment` in the
`SKPaymentTransaction` matches the one added to the queue as a way to
disambiguate these transactions, but it doesn't. So we have no way of
telling the difference between queue transactions and purchases.

I've left the original tests as a way to at least document this
behavior.
2023-08-16 16:26:47 -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 6b69972bb8 TransactionPoster: added transaction ID and Date to log (#3026)
Looking into #3020, it would have been helpful to see these.
2023-08-16 15:14:58 -07:00
NachoSoto 5d3263c8e6 TransactionPoster: don't finish transactions for non-subscriptions if they're not processed (#2841) 2023-08-15 07:05:00 -07:00
NachoSoto 8dadd768e7 Integration Tests: begin tests with UIApplication.willEnterForegroundNotification to simulate a real app (#3015) 2023-08-14 17:36:46 -07:00
NachoSoto 581a048308 CustomerInfoManager: post transactions in parallel to POST receipts only once (#2954)
The existing implementation was posting them in sequence, which meant
that it was always going to make N requests.
With this new implementation we can ensure that only one request is made
for all transactions.
2023-08-11 15:39:44 -07:00
NachoSoto 833524314d HTTPClient: extracted HTTPRequestPath protocol (#2986)
This paves the way to add a second set of paths with a separate server
URL to be able to shed requests to offerings and product entitlement
mapping to a separate server.

### Changes:
- New `HTTPRequestPath` with the same properties as `HTTPRequest.Path` +
a new `serverHostURL`
- Moved proxy logic from `SystemInfo`/`HTTPClient` to `HTTPRequestPath`
- Simplified `HTTPRequest` implementation
2023-08-10 15:23:13 -07:00