From 5b0f241314067002c5d7230f901df7d4d3768752 Mon Sep 17 00:00:00 2001 From: Lever Date: Mon, 20 Apr 2026 16:08:02 +0000 Subject: [PATCH 1/2] feat(ios): update for 4.15.0 --- content/docs/ios/changelog.mdx | 12 ++++++++++++ content/docs/ios/index.mdx | 2 +- content/docs/ios/sdk-reference/index.mdx | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/content/docs/ios/changelog.mdx b/content/docs/ios/changelog.mdx index 340dd14..74fd9f8 100644 --- a/content/docs/ios/changelog.mdx +++ b/content/docs/ios/changelog.mdx @@ -3,6 +3,18 @@ title: "Changelog" description: "Release notes for the Superwall iOS SDK" --- +## 4.15.0 + +### Enhancements + +- Adds support for custom store products. This allows you to purchase products that are on stores outside of the App Store using the `PurchaseController`. +- Adds `formUnion` override when unioning sets of `Entitlement` objects. + +### Fixes + +- Fixes issue where test mode products had trial price data missing. +- Fixed computed period prices (`weeklyPrice`, `dailyPrice`, `monthlyPrice`, `yearlyPrice`) displaying incorrectly rounded values on StoreKit 2 in production. For example, a £4.99/week product could show as £5.00/week. This was caused by Apple's `priceFormatStyle` applying storefront-specific rounding to computed values. + ## 4.14.2 ### Enhancements diff --git a/content/docs/ios/index.mdx b/content/docs/ios/index.mdx index 81b152c..e97b936 100644 --- a/content/docs/ios/index.mdx +++ b/content/docs/ios/index.mdx @@ -50,6 +50,6 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-ios/issues). diff --git a/content/docs/ios/sdk-reference/index.mdx b/content/docs/ios/sdk-reference/index.mdx index 0233cc1..967a39a 100644 --- a/content/docs/ios/sdk-reference/index.mdx +++ b/content/docs/ios/sdk-reference/index.mdx @@ -16,6 +16,6 @@ If you have feedback on any of our docs, please leave a rating and message at th If you have any issues with the SDK, please [open an issue on GitHub](https://github.com/superwall/superwall-ios/issues). From 56316edf4df01a9aa5b1c5f989630f0ba271964c Mon Sep 17 00:00:00 2001 From: Duncan Crawbuck Date: Mon, 20 Apr 2026 09:50:14 -0700 Subject: [PATCH 2/2] docs(ios): cover custom product purchase flows --- bunfig.toml | 2 + .../NonSubscriptionTransaction.mdx | 4 +- .../ios/sdk-reference/PurchaseController.mdx | 9 +++- .../sdk-reference/SubscriptionTransaction.mdx | 6 +-- .../docs/ios/sdk-reference/customerInfo.mdx | 4 -- content/shared/advanced-configuration.mdx | 42 ++++++++++++++++--- 6 files changed, 51 insertions(+), 16 deletions(-) create mode 100644 bunfig.toml diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..5c542da --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[test] +pathIgnorePatterns = ["reference/**"] diff --git a/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx b/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx index 28380ce..e43d083 100644 --- a/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx +++ b/content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx @@ -48,8 +48,8 @@ Provides details about one-time purchases in [`CustomerInfo`](/ios/sdk-reference /> -### Store values (4.11.0+) -`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`. +### Store values +`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `custom`, `other`. ## Usage diff --git a/content/docs/ios/sdk-reference/PurchaseController.mdx b/content/docs/ios/sdk-reference/PurchaseController.mdx index 77f5ad9..7512434 100644 --- a/content/docs/ios/sdk-reference/PurchaseController.mdx +++ b/content/docs/ios/sdk-reference/PurchaseController.mdx @@ -12,7 +12,7 @@ When implementing PurchaseController, you must manually update [`subscriptionSta ## Purpose -Use this protocol only if you want complete control over purchase handling, such as when using RevenueCat or other third-party purchase frameworks. +Use this protocol only if you want complete control over purchase handling, such as when using RevenueCat, another third-party purchase framework, or your own external billing flow. ## Signature ```swift @@ -30,7 +30,7 @@ public protocol PurchaseController: AnyObject { type={{ purchase: { type: "product: StoreProduct", - description: "Called when user initiates purchasing. Implement your purchase logic here. Returns `PurchaseResult`.", + description: "Called when user initiates purchasing. The `StoreProduct` may wrap an App Store product or, for custom paywall products in 4.15.0+, an API-backed product that must be purchased by your external billing system. Returns `PurchaseResult`.", required: true, }, restorePurchases: { @@ -48,6 +48,11 @@ public protocol PurchaseController: AnyObject { When using a PurchaseController, you must also manage [`subscriptionStatus`](/ios/sdk-reference/subscriptionStatus) yourself. +## Handling Products +- For App Store-backed products, use `product.sk1Product` or `product.sk2Product`, or pass the product into your existing purchase SDK. +- For custom products introduced in `4.15.0`, Superwall will call your purchase controller with a `StoreProduct` that has no StoreKit backing product. In that case, use `product.productIdentifier` in your external billing system and return the matching `PurchaseResult`. +- Do not call `Superwall.shared.purchase(product)` for custom products. That helper is for StoreKit-backed purchases only. + ## Usage For implementation examples and detailed guidance, see [Using RevenueCat](/ios/guides/using-revenuecat). diff --git a/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx b/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx index 864619a..4059da8 100644 --- a/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx +++ b/content/docs/ios/sdk-reference/SubscriptionTransaction.mdx @@ -75,14 +75,14 @@ Provides details about a single subscription transaction returned from [`Custome /> -### Offer types (4.11.0+) +### Offer types - `trial` — introductory offer. - `code` — offer redeemed with a promo code. - `promotional` — StoreKit promotional offer. - `winback` — win-back offer (iOS 17.2+ only). -### Store values (4.11.0+) -`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `other`. +### Store values +`appStore`, `stripe`, `paddle`, `playStore`, `superwall`, `custom`, `other`. ## Usage diff --git a/content/docs/ios/sdk-reference/customerInfo.mdx b/content/docs/ios/sdk-reference/customerInfo.mdx index fd6d113..15824cb 100644 --- a/content/docs/ios/sdk-reference/customerInfo.mdx +++ b/content/docs/ios/sdk-reference/customerInfo.mdx @@ -53,10 +53,6 @@ public var customerInfo: CustomerInfo { get } /> - -Starting in 4.11.0, transactions include offer metadata (`offerType`), the `subscriptionGroupId`, and the `store` (`ProductStore`) that fulfilled the purchase to help you audit cross-store sales. - - ## Returns / State Returns a `CustomerInfo` object containing the latest customer purchase and subscription data. This object is immutable and does not update automatically—you must access the property again to get the latest data. diff --git a/content/shared/advanced-configuration.mdx b/content/shared/advanced-configuration.mdx index 6bdb91b..a4bea90 100644 --- a/content/shared/advanced-configuration.mdx +++ b/content/shared/advanced-configuration.mdx @@ -16,6 +16,8 @@ By default, Superwall handles basic subscription-related logic for you: However, if you want more control, you can pass in a `PurchaseController` when configuring the SDK via `configure(apiKey:purchaseController:options:)` and manually set `Superwall.shared.subscriptionStatus` to take over this responsibility. +Starting in `4.15.0`, a `PurchaseController` is also how you handle custom products attached to Superwall paywalls. Those products are not purchased with StoreKit, so your controller must route them through your own billing system. + ### Step 1: Creating a `PurchaseController` A `PurchaseController` handles purchasing and restoring via protocol methods that you implement. @@ -34,7 +36,9 @@ final class MyPurchaseController: PurchaseController { // 1 func purchase(product: StoreProduct) async -> PurchaseResult { - // Use StoreKit or some other SDK to purchase... + // Use StoreKit, RevenueCat, or your own billing system to purchase... + // If `product.sk1Product` and `product.sk2Product` are both nil, + // this is a custom product that should be handled externally. // Send Superwall the result. return .purchased // .cancelled, .pending, .failed(Error) } @@ -76,6 +80,10 @@ final class MyPurchaseController: PurchaseController { // ---- // Purchase via StoreKit, RevenueCat, Qonversion or however // you like and return a valid SWKPurchaseResult + // + // Custom products introduced in 4.15.0 are not backed by StoreKit. + // Handle those with your external billing system using + // `product.productIdentifier`. completion(SWKPurchaseResultPurchased, nil); } @@ -101,6 +109,17 @@ final class MyPurchaseController: PurchaseController { import StoreKit import SuperwallKit +enum PurchaseControllerError: LocalizedError { + case customProductNotHandled(productId: String) + + var errorDescription: String? { + switch self { + case .customProductNotHandled(let productId): + return "Custom product \(productId) must be handled by your external billing system." + } + } +} + final class SWPurchaseController: PurchaseController { // MARK: Sync Subscription Status /// Makes sure that Superwall knows the customer's subscription status by @@ -129,11 +148,20 @@ final class SWPurchaseController: PurchaseController { } // MARK: Handle Purchases - /// Makes a purchase with Superwall and returns its result after syncing subscription status. This gets called when - /// someone tries to purchase a product on one of your paywalls. + /// For App Store-backed products, delegate to `Superwall.shared.purchase(...)`. + /// Custom products from Superwall paywalls must be handled in your + /// external billing system using `product.productIdentifier`. func purchase(product: StoreProduct) async -> PurchaseResult { - let result = await Superwall.shared.purchase(product) - return result + if product.sk1Product != nil || product.sk2Product != nil { + return await Superwall.shared.purchase(product) + } + + // Replace this with your own external billing implementation. + return .failed( + PurchaseControllerError.customProductNotHandled( + productId: product.productIdentifier + ) + ) } // MARK: Handle Restores @@ -147,6 +175,10 @@ final class SWPurchaseController: PurchaseController { ``` + + +For custom products in `4.15.0+`, `StoreProduct` does not contain an App Store purchase target. If `sk1Product` and `sk2Product` are unavailable, purchase the product in your own billing system using `product.productIdentifier`, then return `.purchased`, `.pending`, `.cancelled`, or `.failed(error)` from your controller. Do not call `Superwall.shared.purchase(product)` for that case. + ::: :::android ```kotlin Kotlin