Consumable Purchases with the Superwall SDK

Learn about how consumable purchases, like in-app currency and credits, work with the Superwall SDK for more flexible monetization beyond subscriptions.

Jordan Morgan
Jordan MorganDeveloper Advocate

Overview

Superwall's SDK extends beyond subscription models to support consumable products such as game coins and app credits. By default, the SDK treats consumable purchases identically to subscriptions when purchased:

// This will be true when a consumable, or non-consumable, is purchased
if Superwall.shared.subscriptionStatus == .active {
    showSubscribedAccountView()
}

If this behavior suits your needs, no additional implementation is required.

Customizing Paywall Presentation

There are two approaches when you need different behavior:

Option 1: Always Display Paywalls

Configure your paywall to always display regardless of purchase history. Navigate to Sidebar -> Settings and set the "Present Paywall" option to "Always".

Setting the presentation option for a paywall
Setting the presentation option for a paywall

Option 2: Manual Subscription Status Management

For more complex logic, implement a purchase controller to manage subscription states manually. This approach requires creating aPurchaseController that handles purchasing, restoration, and subscription status updates.

Creating a Purchase Controller

Adopt the PurchaseController protocol and implement required functions:

class PurchaseManager: PurchaseController {
    func purchase(product: SKProduct) async -> SuperwallKit.PurchaseResult {
        // Purchase a product
    }

    func restorePurchases() async -> SuperwallKit.RestorationResult {
        // Restore one
    }
}

Sample Implementation

class PurchasesManager: PurchaseController {
    func purchase(product: SKProduct) async -> SuperwallKit.PurchaseResult {
        let result = await SomeProductPurchaseUtil.purchase(product)

        switch result {
        case .success:
            return .purchased
        case .userCancelled:
            return .cancelled
        case .pendingPurchase:
            return .pending
        case .alreadyPurchased:
            return .restored
        case .failed(let error):
            return .failed(error)
        }
    }

    func restorePurchases() async -> SuperwallKit.RestorationResult {
        do {
            try await AppStore.sync()
            try await updateUserPurchases()
            return .restored
        } catch {
            return .failed(error)
        }
    }
}

Handling Subscription States

Monitor purchase and subscription changes from your source of truth, then update Superwall accordingly:

class PurchasesManager: PurchaseController {
    let purchaseListener = PurchaseListener()

    init() {
        purchaseListener.subscriptionStatusChanged { status in
            switch status {
            case .onlyConsumables:
                Superwall.shared.subscriptionStatus = .inactive
            case .purchaseSubscription, purchasedBoth:
                Superwall.shared.subscriptionStatus = .active
            }
        }
    }

    func purchase(product: SKProduct) async -> SuperwallKit.PurchaseResult {
        let result = await ProductPurchase.purchase(product)

        switch result {
        case .success(let result):
            return .purchased
        case .userCancelled:
            return .cancelled
        case .pending:
            return .pending
        @unknown default:
            fatalError()
        }
    }

    func restorePurchases() async -> SuperwallKit.RestorationResult {
        do {
            try await AppStore.sync()
            try await updateUserPurchases()
            return .restored
        } catch {
            return .failed(error)
        }
    }
}

Maintaining accurate subscription status is critical, as it affects paywall presentation and other SDK functionality.

SDK Initialization

Pass your purchase controller during Superwall initialization:

@main
struct MyApp: App {
    let purchaseUtil: PurchasesManager = .init()

    init() {
        Superwall.configure(apiKey: "YourAPIKey",
                            purchaseController: purchaseUtil)
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Summary

Superwall supports flexible monetization strategies beyond subscriptions, including consumable and non-consumable products. By implementing a purchase controller, you maintain complete control over purchasing and subscription state management, enabling diverse revenue models tailored to your app's needs.

Get the Superwall newsletter

Paywall strategy and monetization tactics, straight to your inbox.

Book a demo

Tell us a little about your app and we'll match you with the right person.

  • A personalized walkthrough of the paywall builder
  • Guidance on your monetization and pricing strategy
  • Custom implementation recommendations for your stack
  • A look at proven paywall templates
  • Revenue-optimization best practices

Get started

Build, test, and optimize your paywalls

Join 10,000+ apps using Superwall to grow subscription revenue without shipping an app update.

Start for free

No credit card required