Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
pathIgnorePatterns = ["reference/**"]
12 changes: 12 additions & 0 deletions content/docs/ios/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion content/docs/ios/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<SdkLatestVersion
version="4.14.2"
version="4.15.0"
repoUrl="https://github.com/superwall/Superwall-iOS"
/>
4 changes: 2 additions & 2 deletions content/docs/ios/sdk-reference/NonSubscriptionTransaction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 7 additions & 2 deletions content/docs/ios/sdk-reference/PurchaseController.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ When implementing PurchaseController, you must manually update [`subscriptionSta
</Warning>

## 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
Expand All @@ -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: {
Expand All @@ -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).

Expand Down
6 changes: 3 additions & 3 deletions content/docs/ios/sdk-reference/SubscriptionTransaction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 0 additions & 4 deletions content/docs/ios/sdk-reference/customerInfo.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ public var customerInfo: CustomerInfo { get }
/>


<Note>
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.
</Note>

## 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.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/ios/sdk-reference/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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).

<SdkLatestVersion
version="4.14.2"
version="4.15.0"
repoUrl="https://github.com/superwall/Superwall-iOS"
/>
42 changes: 37 additions & 5 deletions content/shared/advanced-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}
Expand Down Expand Up @@ -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);
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -147,6 +175,10 @@ final class SWPurchaseController: PurchaseController {
```
</Accordion>
</AccordionGroup>

<Note>
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.
</Note>
:::
:::android
```kotlin Kotlin
Expand Down
Loading