SubSovereign on Flutter — integration guide
This guide covers the subsovereign Flutter package (sdk-flutter/) — a data client and a
native paywall widget. The paywall you design in the SubSovereign dashboard renders here as a real
Flutter Paywall widget, from the same config as the iOS, Android, web, React and React
Native SDKs.
Before you start
A running SubSovereign server, an app registered in the dashboard (an appId and an
SDK-role API key), access levels (e.g. pro) linked to your store products, and a
published paywall. Handle the purchase itself with your usual billing plugin (e.g.
in_app_purchase); SubSovereign verifies and records it afterwards.
Step 1 — Install
Add the package (path/git dependency while it's pre-pub.dev):
dependencies:
subsovereign:
path: ../sdk-flutter # or a git dependency
import 'package:subsovereign/subsovereign.dart';
Step 2 — Configure once, when your app starts
SubSovereign.instance.configure(SubSovereignConfig(
apiKey: 'YOUR_SDK_KEY',
appId: 'your-app-id',
baseUrl: 'https://subs.yourdomain.com/api/v1', // YOUR self-hosted server
userId: currentUser.id, // your own stable user id
locale: 'en',
));
Use a stable userId — the same value everywhere, so access follows the user across devices.
Step 3 — Check what the user can access
try {
final result = await SubSovereign.instance.checkEntitlements();
if (result.hasAccess) {
showPremiumContent();
} else {
showPaywall();
}
} on SubSovereignException catch (e) {
// Keep the user on their last-known access and retry later.
debugPrint('Entitlement check failed: $e');
}
Step 4 — Show the paywall and sell
final config = await SubSovereign.instance.getPaywallConfig(platform: 'android');
Paywall(
config: config,
onSelectProduct: (productId) => buy(productId), // your billing plugin
)
After the purchase succeeds, have the server verify it, then re-check and unlock:
final granted = await SubSovereign.instance.validateGooglePurchase(
purchaseToken: purchase.verificationData.serverVerificationData,
productId: purchase.productID,
accessLevelId: 'pro',
);
if (granted) {
final result = await SubSovereign.instance.checkEntitlements();
if (result.hasAccess) unlockProFeatures();
}
Privacy (GDPR)
await SubSovereign.instance.recordConsent(purpose: 'analytics', granted: true);
EU withdrawal button (Compliance Passport)
What happens to the money. When a customer completes this screen, we record the withdrawal and stop the subscription renewing at the end of the period they have already paid for — they keep access until then. For a Stripe subscription we tell Stripe directly, using the keys you saved for this app; you can turn that off under When a customer cancels under EU law on the app's page in the dashboard, and if you do, stopping the renewal becomes your job. Apple, Google, Amazon and Roku do not allow a merchant to cancel on a customer's behalf — for those the customer must also cancel in the store, and you should say so in your own cancellation copy.
Confirming receipt is yours to do, and it is a legal duty. EU law requires the seller to send the
customer an acknowledgement of receipt, on a durable medium such as email, without delay, showing the
date and time. We do not send it and cannot: we hold no email address for your customers, by design.
The customer is signed in to your app, and the successful cancel call returns the withdrawal reference
and the exact submission time — send them the letter below from your own system at that moment, and
keep a record of when you sent it. In Flutter the drop-in WithdrawalButton has no completion callback: call SubSovereign.instance.withdraw(subscriptionId: …, channel: Platform.isIOS ? 'ios' : 'android') (with import 'dart:io'; for Platform) from your own control and use the receipt it returns — pass channel, because both the call and the button default it to android, so an iPhone withdrawal would be filed under the wrong platform.
Subject: Your withdrawal has been received
Hello,
We confirm we received your withdrawal from your subscription on
{submittedAt}. Your reference is{withdrawalId}.Your subscription will not auto-renew. This is your acknowledgement of receipt.
submittedAt and withdrawalId are the two fields the successful cancel call returns.
If you sell subscriptions to EU consumers online, Directive (EU) 2023/2673 requires a clearly labelled withdrawal function:
WithdrawalButton(subscriptionId: sub.id)
It shows the prominent button, confirms once, and submits idempotently (a network retry can
never create two withdrawals). For custom UIs use
SubSovereign.instance.withdraw(subscriptionId: …, channel: Platform.isIOS ? 'ios' : 'android') — pass
channel, the default is android — and getWithdrawalConfig().
Quick reference
| You want to… | Call |
|---|---|
| Set the SDK up | SubSovereign.instance.configure(SubSovereignConfig(…)) |
| See what the user unlocked | await checkEntitlements() → EntitlementResult.hasAccess |
| Load the published paywall | await getPaywallConfig(platform: 'android') |
| Draw it natively | Paywall(config: …, onSelectProduct: …) |
| Verify a Google purchase | await validateGooglePurchase(purchaseToken: …, productId: …, accessLevelId: …) |
| Record consent | await recordConsent(purpose: …, granted: …) |
| EU withdrawal | WithdrawalButton(subscriptionId: …) · withdraw() · getWithdrawalConfig() |
Next steps
- A runnable sample lives in
sdk-flutter/example/. - New to the concepts? Read How SubSovereign works.