Commit Graph
49 Commits
Author SHA1 Message Date
0dc4f55593 Add default refundWindowDuration to HelpPath.init (#4826)
* add default refundWindowDuration to HelpPath.init

* nil by default

* tweaked refund backend decoder

* tweak initialiser

---------

Co-authored-by: Facundo Menzella <facumenzella@gmail.com>
Co-authored-by: Facundo Menzella <facumenzella@users.noreply.github.com>
2025-02-28 09:57:55 +01:00
Facundo Menzella 6b5c430082 chore: Enable force_unwrapping for SwiftLint (#4820)
* enable force_unwrapping

* avoid force unwrap

* fixed all errors

* remove extra disable
2025-02-26 12:40:49 +01:00
Facundo Menzella 75471965de feat: Introduce refund window to control if a refund is offered for a purchase (#4784) 2025-02-25 13:13:49 +01:00
Josh Holtz b4c81d1ff6 [Paywalls V2] Carousel component (#4722)
* This mostly works

* Some cleanup and todos

* Fixed

* BROKEN but need to rebase

* The rework happened

* This is feeling pretty good

* This works

* Fix lint

* Oops

* Or this

* Removed size and implemented top and bottom pager

* Added a todo and compile error

* Made page control optional and added optional size that defaults to fit fit

* Fixes from PR review

* Fix podlint
2025-02-17 09:34:15 +01:00
Antonio Pallares 24ada6079b Add Internal support for draft paywall previews (#4761)
* feat: add support for presenting draft paywalls under `@_spi(Internal)` attribute

* fix: initialize `useDraftPaywall` property of `PaywallView`

* feat: have `PaywallView` actually show draft paywall when `useDraftPaywall` is `true`

* remove lint warning

* silence linter warning

* tests: add unit tests for `paywallComponents` and `draftPaywallComponents` in `OfferingsResponse`

* tests: move test offerings with v2 paywalls into a new file not to affect existing decode + encode test

* remove unnecessary property wrapper

* silence lint warning
2025-02-13 18:37:04 +01:00
Antonio Pallares 12068ccbb5 UI Preview Mode: mock products (#4735)
* feat: mock fetched `StoreProducts` when in UI Preview mode

* feat: improve mocking of products in UI Preview mode to have consistent paywall previews

* fix: mock of products where packages are not unique

* test: add unit tests for OfferingsManager in UI preview mode

* address PR #4735 comments
2025-01-30 17:26:40 +01:00
Josh Holtz 3d86d7dbbb [Paywalls V2] Remove/replace PAYWALL_COMPONENTS compiler flag and fix OS/platform compile issues (#4727)
* We can't just remove PAYWALL_COMPONENT because we don't support macos and tv and things were failing

* Move PartialComponent outside of PaywallComponent extension

* Fix some lint errors

* Wrong thing

* Undid things i shouldn't have done

* Removed more

* Added comment

* Removed/fix some places for the check thingies

* RevenueCat module compiles on all the things

* RevenueCatUI compiles in all the platforms now

* Make all the things compile again

* Make work for visionOS

* Fix some platform and xcode version issues

* Fix failing spm ios 15 test

* Undo circleci changes

* Replace smooth with easeInOut

* Only run the thing i want

* Attempt to remove circular

* Explicit returns

* Does this pass

* This is it

* Swift 5.9 for this

* All tests again

* Undid public testing processor stuff

* Undo this too

* Swift compiler check out pill style circular

* Fixes from PR review

* Fix compile for mac
2025-01-29 11:53:25 -06:00
Facundo Menzella 373151b293 feat: [CustomerCenter] Use config response for displayPurchaseHistoryLink (#4690) 2025-01-21 16:59:15 +01:00
Facundo Menzella 475a72b470 feat: [CustomerCenter] Introduce PurchaseHistory (#4686) 2025-01-21 10:06:06 +01:00
Josh Holtz 652aa67547 [Paywalls V2] Updating UIConfig aliased colors to contain both light and dark (#4650)
* This compiled... somehow

* Fixed lint

* Some cleanup

* Fix when forground alias color has error

* Fixed when foreground color has an error

* Commented out code removed
2025-01-13 06:23:24 -06:00
Josh Holtz 9ef833b35c [Paywalls V2] Add UIConfig to OfferingsResponse (#4628)
* Added tests

* Removed compiler flags because extra work to handle this on CI unit tests when we are releasing this soon and this isn't really that dangerous

* Fix lint

* Some naming improvements

* Passing UIConfig into Offering

* Reorged some data structures

* Fixed some lint

* This
2025-01-06 14:33:48 -06:00
Will Taylor f87bd03aed Support toggling update warnings & show update in restore flow 2024-12-11 15:16:15 -06:00
Cesar de la VegaandAndy Boedo 658782b68d Adds subscriptions to CustomerInfo (#4508)
* adds SubscriptionInfo

* fix nullabilities

* purchaseDate is not nullable

* add isActive

* add store

* add the product identifier

* add willRenew

* private extension

* made storeTransactionId nil

* fix tests and docs

* fix tests

* fix more tests

* fix compilation

* fix test

* API tests and other fixes

* Update Sources/Purchasing/NonSubscriptionTransaction.swift

Co-authored-by: Andy Boedo <andresboedo@gmail.com>

* use ProductIdentifier and subscriptionsByProductIdentifier

* fix lint

* disable file_length

---------

Co-authored-by: Andy Boedo <andresboedo@gmail.com>
2024-12-04 12:00:27 +01:00
Josh Holtz 91e695869a [Paywalls V2] Add root paywall background (color and image) (#4502)
* Added root paywall background

* Added some previews

* Undo comment about fallback

* Fix some lint

* Fit lint... again

* Added backgroud style to text and stack and fixed preview
2024-11-21 10:46:15 -06:00
Will Taylor 6a363db8df make PurchaseParams & Win-Back APIs public 2024-11-19 13:18:21 -06:00
Cesar de la Vega 5f98c3bd8c Add support for product_mapping in promotional offers (#4489) 2024-11-15 18:19:53 +01:00
Josh Holtz 9ab56917b7 [Paywalls V2] Store decoding errors in individual paywall (instead of failing entire offerings response) (#4473)
* Better strat logic around flex and lazy stacks

* Add Package.resolved again

* Store decoding errors in individual paywall

* Fix lint
2024-11-13 06:52:46 -06:00
Josh Holtz 319fde3af6 Fixes for paywalls v2 renderer after testing some real life paywalls (#4436)
* Fixes for paywall tester

* Some previewer fixes when testing

* Frozen and lint

* Updated some spec values

* Remove @frozen
2024-11-07 09:07:19 -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
JayShortway 714d7eabe5 Adds scaffolding for StickyFooterComponent (#4409) 2024-10-28 18:02:28 +01:00
Toni Rico 95aaf74888 Support custom URL paths in ManageSubscriptionsView (#4382)
* Support custom URL paths in ManageSubscriptionsView

* Fix tests

* More test fixes

* Add parsing and processing tests

* Consolidate SafariView

* Rename method

* Another rename

* Fix import issue with SafariView not being compatible to some platforms

* Style fix
2024-10-21 15:47:16 +02:00
Cesar de la Vega e7d37dfa70 Add default values to enums in Customer Center config response (#4386) 2024-10-21 12:59:47 +02:00
Josh Holtz 17529fa0e9 Paywall component containers are all stacks (#4380)
* Root paywalls is/has a stack

* Stacks on stacks

* Cleaned up some left over

* Removed commented out thing
2024-10-18 11:52:02 -05:00
Josh Holtz cc12dd93f8 [Paywall Components] Match properties and behaviors from dashboard (#4261)
* A mixture of changes while putting the demo together

* Doing some manual fixes for max image height

* Removed unused file and renamed struct

* Fixed lint

* Fix tests

* Image default value on component and not localization

* Fixed sample paywall

* A few more small fixes

* Fix lint
2024-09-26 14:43:11 -05:00
James Borthwick edeff27548 Paywall Components Localized Strings (#4237)
- Adds localized string support to paywall components
-This is primarily done in `TemplateComponentsView`'s static function
`chooseLocalization`.

- Validates that promised localized strings are actually available.
-This is done as component view models are created (see
`TextComponentViewModel`), by throwing if an expected key is not
present:
`self.text = try localizedStrings.string(key: component.textLid)`
-This pattern should be followed for every property that represents a
localized string id, which ensures a non-nil value for every value.
2024-09-06 09:35:05 -07:00
James BorthwickandJosh Holtz cad537e109 Paywalls Components Viewmodels + partial localization support (#4230)
This PR makes all paywall component views rely on intermediate view
models for better separation of concerns. The Component is decoded from
the server response, the ComponentViewModel organizes the component's
data into a view-friendly format, including translating "raw" types into
SwiftUI types when needed, and the ComponentView renders the component.

[ComponentView] --owns---> [ComponentViewModel]  --owns---> [Component].

There has been file re-organization, including grouping all of our model
type extensions and the code that transforms them into SwiftUI
equivalents into their own files. Colors are now decoded without relying
on template paywall code.

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-09-04 12:32:42 -07:00
James BorthwickandJosh Holtz d40eb1157b Paywall Components Initial Commit (#4224)
Initial components merge. Notes:

- Linting enabled except for public documentation which will come as the
API matures and stabilizes.
- Took out unused files (tier-related ones), which will come back in a
future version. The comments were useful and will be referenced when
they come back so thank you for the comments on these.
- Left in tvOS and watchOS availability checks for now - may revisit
this later.
- Everything is codable.
- Features added:
- Layout components allow other components to be laid out along x/y/z
axes.
- Text component supports font weight. (Changing actual font will come
later.)
  - LinkButton component links out to external website of choice.
- RemoteImage allows the image to be arbitrarily styled by the calling
code by passing the image back in a closure.
- Image component supports corner rounding an overlaid gradient. (These
will likely be pulled out into common code somewhere in a future PR.)
- Support for OfferingResponse that includes a separate
`PaywallComponentsData` structure, which is separate from `PaywallData`
used for template-based paywalls.

---------

Co-authored-by: Josh Holtz <me@joshholtz.com>
2024-08-28 17:11:42 -07:00
JayShortway 2a6def5239 [Customer Center] Open App Store when the user wants to update their app (#4199) 2024-08-27 13:43:48 +02:00
JayShortway 081b6535b1 [Customer Center] CustomerCenterViewModel checks whether the app is the latest version (#4169) 2024-08-16 12:02:21 +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
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
NachoSoto b97ac295b0 Paywalls: initial configuration types (#2780)
- Added `Offering.paywall`
- Decoding `PaywallData` in `OfferingsResponse`, using
`IgnoreDecodeErrors` (this will be better after #2778)
- New `PaywallData` `struct`
- Added new APIs to testers
- Testing paywall deserialization from `Offerings`
- Testing paywall deserialization separately to check edge cases
2023-09-14 18:28:11 -07:00
NachoSoto 022cdead1d Added internal NonSubscriptionTransaction.storeTransactionIdentifier (#3009)
This will be used for #2841.
2023-08-14 16:13:54 -07:00
Toni Rico 48127ff7d1 Fix google play purchases missing purchase date (#2703)
### Description 
Reported in https://github.com/RevenueCat/purchases-flutter/issues/738. 

After https://github.com/RevenueCat/purchases-ios/pull/2654, we were
using the new Google Play identifiers that include the plan id in
expiration dates, but we didn't change the system that parses purchase
dates. Later on, when we map purchases, we iterate over the expiration
dates map, and since that id wasn't found in the purchase dates, it was
null, causing the issue in flutter.

This makes the purchase date calculation use the same algorithm that we
use for expiration dates to get the new google play product identifiers.
2023-06-26 11:17:22 +02:00
Josh Holtz dececcba93 Support product_plan_identifier for purchased subscriptions from Google Play (#2654)
### Motivation

Addresses https://github.com/RevenueCat/purchases-flutter/issues/692

### Description

Shows the product id as `<product>:<base_plan>` if the subscription was
purchased from Google Play. This uses the `product_plan_identifier` that
is returned with the subscription.
2023-06-15 17:54:36 -05:00
Josh Holtz 7fe13aa443 Offering metadata (#2498)
### Motivation

Added `metadata` to `Offering` 😊 

### Description

- Added `metadata` to `Offering`
- Had to use a private internal `Metadata` struct to make Swift compiler
happy
- Updated unit tests
- Updated backend integration tests
- Updated load shedder tests
2023-05-24 13:06:35 -04:00
NachoSoto aeb87adbe3 OfferingsManager: return Offerings from new disk cache when server is down (#2495)
This new cache is used only when the server is down, so paywalls can
continue to work in that case.

### Example:
```
- DEBUG: ℹ️ Configuring SDK using RevenueCat's UserDefaults suite.
- DEBUG: ℹ️ No cached Offerings, fetching from network
- DEBUG: ℹ️ Offerings cache is stale, updating from network in foreground
- WARN: ⚠️ Error fetching offerings: server appears down
- DEBUG: ℹ️ MockProductsManager: no stubbed products, returning fake products for ["monthly_freetrial"]
- DEBUG: ℹ️ Vending Offerings from disk cache
```
2023-05-17 19:21:17 -04:00
NachoSoto 9c1bb9dbaf ProductEntitlementMapping: enabled entitlement mapping fetching (#2425) 2023-04-24 06:04:30 -07:00
339e13d5f1 Offline Entitlements: create CustomerInfo from offline entitlements (#2358)
Based on #2352.
Depends on #2351 and #2365.

---------

Co-authored-by: Andy Boedo <andresboedo@gmail.com>
Co-authored-by: Toni Rico <antonio.rico.diez@revenuecat.com>
2023-03-24 10:41:10 -07:00
NachoSoto 6d4c169711 Offline Entitlements: created ProductEntitlementMapping (#2365)
This will help decouple the format of
`ProductEntitlementMappingResponse` with the internal handling and
caching.
2023-03-23 09:03:42 -07:00
NachoSoto 947e100c2f Offline Entitlements: created ProductEntitlementMappingResponse (#2351) 2023-03-21 17:36:35 -07:00
NachoSoto 9679af3bc1 CustomerInfoDecodingTests: added test to check decoding invalid data behavior (#1659)
See also #1658.
This updates the behavior of `CustomerInfoResponse` decoding so that having invalid content inside of one of the structures fails more loudly instead of getting ignored.

For that, `@DefaultValue` no longer ignores errors, and instead `@IgnoreDecodeErrors` must be used.
For some of the values in `CustomerInfoResponse` this makes sense (like `Store`), but not for things like subscriptions where we would be losing data.
2022-06-07 16:56:04 -07:00
Josh Holtz 5e527162e6 Fix decoding issue caused by LossyDictionary for subscriptions and active entitlements (#1651) 2022-06-03 13:05:55 -03:00
NachoSoto 59d492df88 GetIntroEligibilityOperation: replaced response parsing with Decodable (#1576)
The new response is now represented by `GetIntroEligibilityResponse`, and has dedicated tests (`GetIntroEligibilityDecodingTests`) using a JSON fixture.
The operation becomes much simpler now, plus I've done a lot of code removing that was redundant.

The actual response handling is now 3 lines:
```swift
let result: [String: IntroEligibility] = Set(productIdentifiers)
  .dictionaryWithValues { eligibilities?[$0] ?? .unknown }
  .mapValues(IntroEligibility.init)
```

All the tests in `BackendGetIntroEligibilityTests` pass with no changes.
2022-05-26 13:54:45 -07:00
NachoSoto 438e74e6cf PostOfferForSigningOperation: changed response parsing to using Decodable (#1573)
## Changes:
- Added `PostOfferResponse`
- Added `PostOfferDecodingTests` tests
- Using the automatic deserialization by `HTTPClient` by simply changing the callback to `HTTPResponse<PostOfferResponse>.Result`
- All tests in `BackendPostOfferForSigningTests` (which are very exhaustive) pass with no changes!
2022-05-26 13:45:31 -07:00
NachoSoto 395322ac7e Added tests and fix to ensure RawDataContainer includes all data (#1565)
Follow up to #1496. Depends on ~~#1567,  #1568, #1604~~.

The solution implemented in #1496 was very simple, but [as pointed out by @joshdholtz](https://github.com/RevenueCat/purchases-ios/pull/1496#discussion_r869463028), it was only encoding the data that was formally decoded by the `struct`s.
We had no test coverage for this, so it went unnoticed.

The new solution adds a new field:
```swift
@IgnoreEncodable @IgnoreHashable
var rawData: [String: Any]
```

Which, together with a new helper method `decodeRawData()`, allows storing all the data to be used by `CustomerInfo`'s `RawDataContainer` implementation.

Unfortunately, despite many attempts, I realized that this can't be abstracted into a property wrapper, because a `KeyedDecodingContainer` can't access the parent `Decoder`.
That's why the `CustomerInfoResponse` and `CustomerInfoResponse.Entitlement` must manually call this method.
2022-05-24 14:23:07 -07:00
NachoSoto 29c034dfc8 Converted CustomerInfo and related types to use Codable (#1496)
### Depends on:
- #1537
- #1540
- #1541
- #1543
- #1546
- #1547
- #1550
- #1551,
- #1565

### Changes

- Removed all custom deserialization code thanks to #1537, #1541, and #1543.
- The format of the customer info response is now very concise and only in one place (`CustomerInfoResponse`) instead of being passed through dictionaries and magic strings everywhere.
- `CustomerInfo` was combining the dictionaries inside of `"subscriptions"` and `"non_subscriptions"`, despite them having different formats. This is now made explicit through type conversions between the two, (see `CustomerInfoResponse.allTransactionsByProductId`).
- Removed all custom `CustomerInfo` errors since deserialization is automatic, and the underlying `Error` information will be provided thanks to `ErrorUtils.logDecodingError`.
- `CustomerInfo` deserialization always wraps errors into `ErrorCode.customerInfoError`, which is now also covered in a new test.
- Added a new snapshot test that covers `CustomerInfo` serialization by storing the result in a JSON file.

### Other changes:
- `CustomerInfo` equality is now automatic through the `CustomerInfoResponse` `Equatable` conformance.
- Simplified `schemaVersion` handling
- All tests still use `CustomerInfo(data: [String: Any])`, but that method is only visible for tests now, and it uses the underlying `Decodable` implementation (see `CustomerInfo+TestExtensions.swift`).
- Added extra tests for `CustomerInfoResponse` that use a fixture `CustomerInfo.json`, which is easier to maintain than having JSON in code.
- `RawDataContainer` implementation is changed in #1565.

### TODO:
- [x] Fix all tests
- [x] Update `RawValueContainer`
- [x] Consider recovering `CustomerInfoError`
- [x] Look at all previously logged errors
- [x] Finish `CustomerInfoResponseTests`
- [x] Handle all TODOs
- [x] Make `RawDataContainer` actually store all the original data: #1565
2022-05-24 14:22:11 -07:00
NachoSoto 7530a6771d OfferingsManager/OfferingsFactory: using OfferingsResponse (#1435)
This takes full advantage of `Decodable` when decoding and creating `Offerings`.
2022-04-22 11:19:40 -07:00