While Superwall is known for helping you grow your revenue with our extensive paywall testing suite, experiments and more — the Superwall SDK also supports consumable products, too. Things like coins for a game, credits for A.I. based apps and other similar products all work easily with Superwall.
The important thing to remember is that, by default, Superwall treats these purchases as an active "subscription" when they are purchased:
// This will be true when a consumable, or non-consumable, is purchased
if Superwall.shared.subscriptionStatus == .active {
showSubscribedAccountView()
}
swift
And, if that's fine with you — then you're done here! There's nothing else you need to do. But, in some cases, you may not want this behavior. In those situations, there are two ways to think about it:
If you always want a paywall to show no matter if they've bought a consumable, non-consumable or even have a subscription — then there are no code changes needed. Simply set your paywall's "Present Paywall" option (under Sidebar -> Settings) to "Always":
If you have more involved logic for determining if someone is subscribed, or you don’t want consumable or non-consumable purchases to mark them as subscribed, and you’d rather not update the paywall presentation settings individually — you can manually set the subscription status in Superwall.
This post covers the second case. In that scenario, we'll want to implement a purchase controller to:
Purchase products
Handle restores
And, most importantly, set the correct subscription status
Creating a purchase controller
A PurchaseController
is created by adopting the PurchasController
protocol and implementing two required functions for purchasing and restoring:
class PurchaseManager: PurchaseController {
func purchase(product: SKProduct) async -> SuperwallKit.PurchaseResult {
// Purchase a product
}
func restorePurchases() async -> SuperwallKit.RestorationResult {
// Restore one
}
}
swift
In these functions, you would use whatever avenue you rely on to purchase products. It could be StoreKit, your own server code or another third-party solution. Here's what it might look like:
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)
}
}
}
swift
Handling subscription states
But, as I mentioned above, the critical component here is setting subscription states. In this case, you'll use the existing code you have (or would need to write) to listen for product purchases, subscription state changes from your own server, or whatever "source of truth" you use for a user's subscription state — and pass that over to Superwall:
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)
}
}
}
swift
The key is that, even if you don't use Superwall's subscriptionStatus
in your code, you want it to always have the accurate state of someone's subscription status since it affects other parts of the Superwall SDK (such as presenting paywalls, for example).
Finally, you will need to pass this controller over to Superwall when you first initialize the Superwall SDK:
@main
struct MyApp: App {
let purchaseUtil: PurchasesManager = .init()
init() {
Superwall.configure(apiKey: "YourAPIKey",
purchaseController: purchaseUtil)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
swift
Wrapping up
Superwall's SDK isn't just for handling subscription-based models; it also supports consumable and non-consumable products, giving you flexibility in how you grow your apps. By using a purchase controller, you can have full control over the entire purchasing pipeline and subscription state management.
If you're new to Superwall, there's no better time to get started. We can help you grow your revenue, no matter your monetization strategy. Get started for free by signing up today.