Advanced Purchasing
If you need fine-grain control over the purchasing pipeline, use a purchase controller to manually handle purchases and subscription status.
Using a PurchaseController is only recommended for advanced use cases. By default, Superwall handles all
subscription-related logic and purchasing operations for you out of the box.
By default, Superwall handles basic subscription-related logic for you:
- Purchasing: When the user initiates a checkout on a paywall.
- Restoring: When the user restores previously purchased products.
- Subscription Status: When the user's subscription status changes to active or expired (by checking the local receipt).
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.
Step 1: Creating a PurchaseController
A PurchaseController handles purchasing and restoring via protocol methods that you implement.
// MyPurchaseController.dart
class MyPurchaseController extends PurchaseController {
// 1
@override
Future<PurchaseResult> purchaseFromAppStore(String productId) async {
// TODO
// ----
// Purchase via StoreKit, RevenueCat, Qonversion or however
// you like and return a valid PurchaseResult
return PurchaseResult.purchased;
}
@override
Future<PurchaseResult> purchaseFromGooglePlay(
String productId,
String? basePlanId,
String? offerId
) async {
// TODO
// ----
// Purchase via Google Billing, RevenueCat, Qonversion or however
// you like and return a valid PurchaseResult
return PurchaseResult.purchased;
}
// 2
@override
Future<RestorationResult> restorePurchases() async {
// TODO
// ----
// Restore purchases and return true if successful.
return RestorationResult.restored;
}
}Here’s what each method is responsible for:
- Purchasing a given product. In here, enter your code that you use to purchase a product. Then, return the result of the purchase as a
PurchaseResult. For Flutter, this is separated into purchasing from the App Store and Google Play. This is an enum that contains the following cases, all of which must be handled:.cancelled: The purchase was cancelled..purchased: The product was purchased..pending: The purchase is pending/deferred and requires action from the developer..failed(Error): The purchase failed for a reason other than the user cancelling or the payment pending.
- Restoring purchases. Here, you restore purchases and return a
RestorationResultindicating whether the restoration was successful or not. If it was, return.restore, orfailedalong with the error reason.
Step 2: Configuring the SDK With Your PurchaseController
Pass your purchase controller to the configure(apiKey:purchaseController:options:) method:
// main.dart
void initState() {
// Determine Superwall API Key for platform
String apiKey = Platform.isIOS ? "MY_IOS_API_KEY" : "MY_ANDROID_API_KEY";
// Create the purchase controller
MyPurchaseController purchaseController = MyPurchaseController();
Superwall.configure(apiKey, purchaseController);
}Step 3: Keeping subscriptionStatus Up-To-Date
You must set Superwall.shared.subscriptionStatus every time the user's subscription status changes, otherwise the SDK won't know who to show a paywall to. This is an enum that has three possible cases:
.unknown: This is the default value. In this state, paywalls will not show and their presentation will be automatically delayed untilsubscriptionStatuschanges to a different value..active(let entitlements): Indicates that the user has an active entitlement. Paywalls will not show in this state unless you remotely set the paywall to ignore subscription status. A user can have one or more active entitlement..inactive: Indicates that the user doesn't have an active entitlement. Paywalls can show in this state.
Here's how you might do this:
// When a subscription is purchased, restored, validated, expired, etc...
myService.addSubscriptionStatusListener((subscriptionInfo) {
var entitlements = subscriptionInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
var hasActiveSubscription = subscriptionInfo.isActive;
if (hasActiveSubscription) {
Superwall.shared.setSubscriptionStatus(SubscriptionStatusActive(entitlements: entitlements));
} else {
Superwall.shared.setSubscriptionStatus(SubscriptionStatusInactive());
}
});subscriptionStatus is cached between app launches Listening for subscription status changes
If you need a simple way to observe when a user's subscription status changes, on iOS you can use the Publisher for it. Here's an example:
Superwall.shared.subscriptionStatus.listen((status) {
// React to changes
}
//Or use SuperwallBuilder widget which triggers the builder closure when subscription status changes
SuperwallBuilder(
builder: (context, status) => Center(
child: Text('Subscription Status: ${status}'),
)
)You can do similar tasks with the SuperwallDelegate, such as viewing which product was purchased from a paywall.
How is this guide?