This package is an in app purchases paywall UI for Flutter

Paywall design for everyone

This simple to use Paywall UI is developed for the flutter community. Fully customizable and easy to integrate.

Design 1: Simple Paywall

The Simple Paywall design is a basic design without any animations

Simple Paywall

Design 2: Moritz Paywall

The second Paywall designed by Moritz is available from version 0.5 and upwards.

Moritz Paywall

Easy integration

Use the SimplePaywall without a Scaffold or wrap the Paywall in a PaywallScaffold

// Use the Scaffold to also show an App Bar
PaywallScaffold(
  appBarTitle: "Premium",
  child: // your Paywall as a child
);

SimplePaywall(
  // ...
)

MoritzPaywall(
  // ...
)

Flutter Navigation 2.0 Page

If you use flutter navigation 2.0 you might want to wrap the scaffold in a page element or use the class PaywallPage:

class PremiumPage extends Page {
  @override
  Route createRoute(BuildContext context) {
    return MaterialPageRoute(
      settings: this,
      builder: (BuildContext context) {
        return PaywallScaffold(
          // ...
        );
      },
    );
  }
}

State control included

Control the State: PURCHASED to show the Success Page or Purchase in Progress to show a fullscreen loading indicator.

SimplePaywall(
  // add your handler -> extend DefaultPurchaseHandler
  callbackInterface: purchaseHandler,
  purchaseStateStreamInterface: purchaseHandler,
  // ...
)

To control the state, extend DefaultPurchaseHandler and implement your own logic.

class PurchaseHandler extends DefaultPurchaseHandler {
  @override
  Future<bool> purchase(SubscriptionData productDetails) async {
    // show loading
    isPendingPurchase = true;
    // your logic
    await Future.delayed(Duration(seconds: 1));
    // show success purchase and end loading
    purchaseState = PurchaseState.PURCHASED;
    isPendingPurchase = false;
    return true;
  }

  @override
  Future<bool> restore() async {
    // show loading
    isPendingPurchase = true;
    // your logic
    await Future.delayed(Duration(seconds: 1));
    // show success purchase and end loading
    purchaseState = PurchaseState.PURCHASED;
    isPendingPurchase = false;
    return true;
  }
}
Simple Paywall Success state

Success Page linked to the subscription page

Starting September 30, 2022, every app must include a deep link to the store's subscription page. We have added the link on the success page.

You can add the link by adding an ActivePlan object to the Paywall:

activePlanList: [
  // links to the subscription overview on Android devices:
  GooglePlayGeneralActivePlan(), 

  // links to the specific subscription on Android devices: 
  GooglePlayActivePlan("yearly_pro", "com.tnx.packed"),

  // links to the subscription overview on iOS devices
  AppleAppStoreActivePlan(), 
],

Here are the specific App Store docs:

Active Plan

Easy Integration with linkfive_purchases library

Since LinkFive is using in_app_purchase package as a dependency, the integration is seamless and effortless.

Pub.dev plugin: linkfive_purchases package


// get subscription data from your provider or from your stream (as described above)
LinkFiveProducts? products = // your products you got through the products Stream

SimplePaywall(
  // ...
  // basically just the linkFivePurchases class
  callbackInterface: LinkFivePurchases.callbackInterface,

  // you can use your own strings or use the intl package to automatically generate the subscription strings
  subscriptionListData: products?.paywallUIHelperData(context: context) ?? [],
  // ...
);
LinkFive and Flutter works perfectly together

Read more about an easy Flutter Paywall Integration

LinkFive Provider Plugin (easiest version)

LinkFive created a provider plugin which you can use out of the box: linkfive_purchases_provider

For a fully working paywall including state management. Register the module:

MultiProvider(
  providers: [
    // ...
    ChangeNotifierProvider(
      create: (context) => LinkFiveProvider("API_KEY"),
      lazy: false,
    ),
  ]
)

And pass the callback and pass the subscriptionData from linkfive_purchases_provider

PaywallScaffold(
  child: SimplePaywall(
    callbackInterface: LinkFivePurchases.callbackInterface,
    subscriptionListData: provider.getSubscriptionListData(context),
    // ...
  )
)

Now you have a fully functional subscription system.


Navigator 2.0 & Provider Example

Check out the following example: Provider and Navigator Example


implement

Example usage Simple Paywall:

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  PurchaseHandler purchaseHandler = PurchaseHandler();

  @override
  Widget build(BuildContext context) {
    return PaywallScaffold(
      // appBarTitle for scaffold
      appBarTitle: "Premium",
      child: SimplePaywall(
        // set a custom header
          headerContainer: Container(
              margin: EdgeInsets.all(16),
              height: 100,
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(8.0)),
                  image: DecorationImage(
                      fit: BoxFit.cover,
                      alignment: FractionalOffset.center,
                      image: AssetImage('assets/images/premium_bg.png'))),
              child: Container()),
          // Title Bar
          title: "Go Premium",
          // SubTitle
          subTitle: "All features at a glance",
          // Add as many bullet points as you like
          bulletPoints: [
            IconAndText(Icons.stop_screen_share_outlined, "No Ads"),
            IconAndText(Icons.hd, "Premium HD"),
            IconAndText(Icons.sort, "Access to All Premium Articles")
          ],
          // Your subscriptions that you want to offer to the user
          subscriptionListData: [
            SubscriptionData(
                durationTitle: "Yearly",
                durationShort: "1 year",
                price: "€14,99€",
                dealPercentage: 69,
                productDetails: "Dynamic purchase data",
                index: 0),
            SubscriptionData(
                durationTitle: "Quarterly",
                durationShort: "3 Months",
                price: "€6,99",
                dealPercentage: 42,
                productDetails: "Dynamic purchase data",
                index: 2),
            SubscriptionData(
                durationTitle: "Monthly",
                durationShort: "1 month",
                price: "3,99€",
                dealPercentage: 0,
                productDetails: "Dynamic purchase data",
                index: 3)
          ],
          // Shown if isPurchaseSuccess == true
          successTitle: "Success!!",
          // Shown if isPurchaseSuccess == true
          successSubTitle: "Thanks for choosing Premium!",
          // Widget can be anything. Shown if isPurchaseSuccess == true
          successWidget: Container(
              padding: EdgeInsets.only(top: 16, bottom: 16),
              child:
              Row(mainAxisAlignment: MainAxisAlignment.center, children: [
                ElevatedButton(
                  child: Text("Let's go!"),
                  onPressed: () {
                    print("let‘s go to the home widget again");
                  },
                )
              ])),
          // set true if subscriptions are loading
          isSubscriptionLoading: false,
          // if purchase is in progress, set to true. this will show a fullscreen progress indicator
          isPurchaseInProgress: false,
          // to show the success widget
          purchaseState: PurchaseState.NOT_PURCHASED,
          // callback Interface for restore and purchase tap events. Extend DefaultPurchaseHandler
          callbackInterface: purchaseHandler,
          purchaseStateStreamInterface: purchaseHandler,
          // provide your TOS
          tosData:
          TextAndUrl("Terms of Service", "https://www.linkfive.io/tos"),
          // provide your PP
          ppData:
          TextAndUrl("Privacy Policy", "https://www.linkfive.io/privacy"),
          // add a custom campaign widget
          campaignWidget: CampaignBanner(
            headline: "🥳 Summer Special Sale",
            subContent: Container(
                padding: EdgeInsets.all(8),
                child: CountdownTimer(
                  endTime: DateTime.now()
                      .add(Duration(days: 2, hours: 7))
                      .millisecondsSinceEpoch,
                )),
          )),
    );
  }
}

Change colours and font

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // custom icon theme
        iconTheme: IconThemeData(color: Colors.green),
        // your colorScheme
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.light),
      ),
      home: MyHomePage(),
    );
  }
}

Example: Moritz Paywall

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(

        // primary color schema
          primarySwatch: Colors.green,

          // custom icon theme
          iconTheme: IconThemeData(color: Colors.lightGreen),

          // color scheme
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.amber, brightness: Brightness.light),
          elevatedButtonTheme: ElevatedButtonThemeData(
              style: ElevatedButton.styleFrom(primary: Colors.green)
          )
      ),
      supportedLocales: [
        const Locale('en'),
        const Locale('de'),
      ],
      localizationsDelegates: [
        PaywallLocalizations.delegate,
        GlobalMaterialLocalizations.delegate
      ],
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  PurchaseHandler purchaseHandler = PurchaseHandler();

  @override
  Widget build(BuildContext context) {
    final translations = PaywallL10NHelper.of(context);
    return PaywallScaffold(
      // appBarTitle for scaffold
      appBarTitle: "YourApp Premium",
      child: MoritzPaywall(
        // Title Bar
        title: "Go Premium",
        // SubTitle
        subTitle:
        "Enjoy all the advantages of YourApp with the Premium subscription.",
        continueText: "Continue",
        // Add as many bullet points as you like
        bulletPoints: [
          IconAndText(Icons.stop_screen_share_outlined, "No Ads"),
          IconAndText(Icons.hd, "Premium HD"),
          IconAndText(Icons.sort, "Access to All Premium Articles")
        ],
        // Your subscriptions that you want to offer to the user
        subscriptionListData: [
          SubscriptionData(
              durationTitle: translations.yearly.toTitleCase(),
              durationShort: translations.nmonth(12),
              price: "€14,99€",
              highlightText: "Most popular",
              dealPercentage: 59,
              productDetails: "Dynamic purchase data",
              currencySymbol: "€",
              rawPrice: 14.99,
              monthText: translations.month,
              duration: "P1Y",
              index: 3),
          SubscriptionData(
              durationTitle: translations.quarterly.toTitleCase(),
              durationShort: translations.nmonth(3),
              price: "€8,99",
              dealPercentage: 42,
              productDetails: "Dynamic purchase data",
              currencySymbol: "€",
              rawPrice: 8.99,
              monthText: translations.month,
              duration: "P3M",
              index: 2),
          SubscriptionData(
              durationTitle: translations.monthly.toTitleCase(),
              durationShort: translations.nmonth(1),
              price: "€2,99",
              dealPercentage: 0,
              productDetails: "Dynamic purchase data",
              currencySymbol: "€",
              rawPrice: 2.99,
              monthText: translations.month,
              duration: "P1M",
              index: 1)
        ],
        // Shown if isPurchaseSuccess == true
        successTitle: "Success!!",
        // Shown if isPurchaseSuccess == true
        successSubTitle: "Thanks for choosing Premium!",
        // Widget can be anything. Shown if isPurchaseSuccess == true
        successWidget: Container(
            padding: EdgeInsets.only(top: 16, bottom: 16),
            child:
            Row(mainAxisAlignment: MainAxisAlignment.center, children: [
              ElevatedButton(
                child: Text("Let's go!"),
                onPressed: () {
                  print("let‘s go to the home widget again");
                },
              )
            ])),
        // set true if subscriptions are loading
        isSubscriptionLoading: false,
        // if purchase is in progress, set to true. this will show a fullscreen progress indicator
        isPurchaseInProgress: false,
        // to show the success widget
        purchaseState: PurchaseState.NOT_PURCHASED,
        // callback Interface for restore and purchase tap events. Extend DefaultPurchaseHandler
        callbackInterface: purchaseHandler,
        purchaseStateStreamInterface: purchaseHandler,
        // provide your TOS
        tosData:
        TextAndUrl("Terms of Service", "https://www.linkfive.io/tos"),
        // provide your PP
        ppData:
        TextAndUrl("Privacy Policy", "https://www.linkfive.io/privacy"),
        // add a custom campaign widget
        /*campaignWidget: CampaignBanner(
            theme: Theme.of(context),
            headline: "🥳 Summer Special Sale",
            subContent: Container(
                padding: EdgeInsets.all(8),
                child: CountdownTimer(
                  endTime: DateTime.now()
                      .add(Duration(days: 2, hours: 7))
                      .millisecondsSinceEpoch,
                )),
          )*/),
    );
  }
}

Libraries

active_plan
active_plan_card
base_paywall
campaign_banner
card_insets
default_purchase_handler
icon_and_text
in_app_purchases_paywall_ui
moritz_bulletpoints
moritz_paywall
moritz_paywall_purchase
moritz_price_box
moritz_restore_row
moritz_sub_row
page_insets
paywall_data_iw
paywall_page
paywall_scaffold
simple_bulletpoints
simple_paywall
simple_paywall_purchase
simple_paywall_success
subscription_callback_iw
subscription_price_box
subscription_row
text_and_url