Skip to main content

Paywalls & Checkout UI

A Paywall is the customer-facing interface where users discover premium offerings, compare subscription tiers, and complete purchases.

SolydFlow decouples paywall presentation from core application logic. This allows product teams to adjust pricing, launch seasonal promotions, or test copy from the SolydFlow Console without submitting new builds to the Apple App Store or Google Play Store.


Choosing Your Paywall Strategy​

FeatureNo-Code Drop-In PaywallCustom UI Paywall
Best ForFast implementation, rapid experimentation, non-technical updates.Bespoke design systems, custom animations, tightly integrated brand flows.
Managed FromSolydFlow Console (Drag-and-drop designer).Your application codebase (Flutter widgets, React components).
OTA UpdatesInstant. Copy, colors, and tier order update without app releases.Requires app update if changing layout/widgets.
Localization & PPPHandled automatically by the SDK.Handled via SDK data attributes (pkg.currency, amountMinor).

Option A: No-Code Drop-In Paywall​

The fastest way to monetize. SolydFlow renders a high-converting, responsive paywall synchronized with your Console settings.

lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import 'package:solydflow_flutter/solydflow_flutter.dart';

void launchPaywall(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (ctx) => SolydPaywall(
onPurchaseSuccess: (CustomerInfo info) {
Navigator.pop(ctx);
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(content: Text("🚀 Subscription Active!")),
);
},
onClose: () => Navigator.pop(ctx),
),
);
}

Option B: Custom UI Paywall​

If your app requires a custom design system, fetch the raw offerings from SolydFlow and bind them to your own widgets.

lib/screens/custom_paywall.dart
import 'package:flutter/material.dart';
import 'package:solydflow_flutter/solydflow_flutter.dart';

class CustomPaywall extends StatefulWidget {
const CustomPaywall({super.key});


State<CustomPaywall> createState() => _CustomPaywallState();
}

class _CustomPaywallState extends State<CustomPaywall> {
List<SolydPackage> _packages = [];
bool _loading = true;
String? _activePackageId;


void initState() {
super.initState();
_loadOfferings();
}

Future<void> _loadOfferings() async {
try {
final offerings = await SolydFlow.getOfferings();
setState(() {
_packages = offerings;
_loading = false;
});
} catch (e) {
setState(() => _loading = false);
}
}

Future<void> _handleBuy(String packageId) async {
setState(() => _activePackageId = packageId);
try {
final info = await SolydFlow.purchasePackage(context, packageId);
if (info?.activeEntitlements["pro_access"] == true && mounted) {
Navigator.pop(context);
}
} finally {
if (mounted) setState(() => _activePackageId = null);
}
}


Widget build(BuildContext context) {
if (_loading) return const Center(child: CircularProgressIndicator());

return Scaffold(
appBar: AppBar(title: const Text("Unlock Pro")),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
const Text("🔥 Pro Features", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const ListTile(leading: Icon(Icons.check), title: Text("Unlimited HD Downloads")),
const ListTile(leading: Icon(Icons.check), title: Text("Offline Reading Mode")),
const Divider(height: 32),
..._packages.map((pkg) {
final double price = (pkg.isUpgrade ? pkg.calculatedAmountMinor : pkg.amountMinor);
final isBuying = _activePackageId == pkg.identifier;

return Card(
margin: const EdgeInsets.only(bottom: 12),
child: ListTile(
title: Text(pkg.name, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text("${pkg.currency} $price / ${pkg.duration}"),
trailing: isBuying
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: _activePackageId == null ? () => _handleBuy(pkg.identifier) : null,
child: const Text("Subscribe"),
),
),
);
}),
],
),
);
}
}