Last updated:
0 purchases
linkfive purchases
In-app purchases and subscription management for Flutter #
This document provides a comprehensive overview of the LinkFive Purchases Flutter library, allowing you to integrate in-app purchases and subscription management functionalities into your Flutter application.
What is LinkFive Purchases? #
LinkFive Purchases empowers developers to seamlessly implement and manage in-app purchases and subscriptions within their Flutter applications. It simplifies the integration process, taking care of the complexities and streamlining the experience for both developers and users.
Getting Started #
Sign Up and Obtain API Key: Visit the LinkFive website (https://app.linkfive.io/sign-up) to register and acquire your unique API key (it's free!). This key is essential for initializing the LinkFive Purchases plugin within your application.
Installation: Add the linkfive_purchases package to your pubspec.yaml file and run pub get to download and install the necessary dependencies.
Core Functionalities #
Supported Purchase Types:
Subscriptions: Offer recurring billing plans that provide users with ongoing access to premium features or content within your app.
One-Time Purchases: Enable users to purchase a product or service permanently with a single payment.
Initialization:
Employ the init method to initialize the LinkFive Purchases plugin, providing your API key as an argument. You can optionally set the logging level using the logLevel parameter.
await LinkFivePurchases.init("API_KEY");
copied to clipboard
Fetching Products:
Utilize the fetchProducts method to retrieve a list of available products from LinkFive. This method is crucial for populating your paywall or displaying relevant subscription options to users.
await LinkFivePurchases.fetchProducts();
copied to clipboard
Purchasing Products:
Trigger the purchase flow for a specific product using the purchase method. This method takes a ProductDetails object as input, representing the product the user intends to purchase.
await LinkFivePurchases.purchase(productDetails);
copied to clipboard
BETA-function: In version 4.x we also added purchaseFuture which will wait for the purchase to finish and return the ActiveProducts.
await LinkFivePurchases.purchaseFuture(productDetails);
copied to clipboard
Restoring Purchases:
The restore method enables users to restore previously purchased products. This ensures continued access to subscribed features upon app reinstallation or device change.
await LinkFivePurchases.restore();
copied to clipboard
BETA-function: In version 4.x we also added restoreFuture which will wait for the restore to finish and return the ActiveProducts.
await LinkFivePurchases.restoreFuture();
copied to clipboard
Products Stream:
The products stream continuously delivers information about the user's offering. Whenever the user opens the paywall, you want to show the data that is included in this stream.
stream = LinkFivePurchases.products.listen((products) {
// Handle products here
});
copied to clipboard
Active Products Stream:
The activeProducts stream continuously delivers information about the user's active and verified products. This stream proves valuable for managing access to subscription-based features or One-time purchases within your application.
stream = LinkFivePurchases.activeProducts.listen((activeProducts) {
// Handle active products here
});
copied to clipboard
Purchase in Progress Stream #
The purchaseInProgressStream provides real-time updates on the purchase process. This stream is helpful for displaying loading indicators or disabling purchase buttons while a purchase is underway.
An example riverpod Notifier would be:
/// true -> show loading indicator / disable purchase button
/// false -> disable loading indicator / enable purchase Button
class PremiumPurchaseInProgressNotifier extends Notifier<bool> {
@override
bool build() {
final streamSub =
ref.read(billingRepositoryProvider).purchaseInProgressStream().listen((bool isPurchaseInProgress) {
state = isPurchaseInProgress;
});
ref.onDispose(() {
streamSub.cancel();
});
return false;
}
}
copied to clipboard
Subscription and One-Time Purchase Offerings #
A typical riverpod notifier implementation would be:
class PremiumOfferNotifier extends Notifier<LinkFiveProducts?> {
/// fetched once whenever the user enters the paywall
Future<void> fetchOffering() async {
state = await ref.read(billingRepositoryProvider).fetchOffering();
}
void purchase(LinkFiveProductDetails productDetails) {
ref.read(billingRepositoryProvider).purchase(productDetails.productDetails);
}
void restore() {
ref.read(billingRepositoryProvider).restore();
}
@override
LinkFiveProducts? build() {
return null;
}
}
copied to clipboard
And the Widget Implementation:
class _PurchasePaywall extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final premiumOffer = ref.watch(premiumOfferProvider);
if (premiumOffer == null) {
// return Page Loading Widget
}
return ListView(children: [
for (final offer in premiumOffer.productDetailList)
switch (offer.productType) {
LinkFiveProductType.OneTimePurchase => LayoutBuilder(builder: (_, _) {
// build your One Time Purchase Widget
// e.g:
// Text(offer.oneTimePurchasePrice.formattedPrice)
// and later when pressed:
// onPressed: () {
// ref.read(premiumOfferProvider.notifier).purchase(offer);
// }
}),
LinkFiveProductType.Subscription => LayoutBuilder(builder: (_, _) {
// build your Subscription Purchase Widget
// use the pricing Phases:
// for (var pricingPhase in offer.pricingPhases) {
// Text(pricingPhase.formattedPrice);
// Text(pricingPhase.billingPeriod.iso8601); // e.g.: P6M
// }
// and later when pressed:
// onPressed: () {
// ref.read(premiumOfferProvider.notifier).purchase(offer);
// }
}),
}
]
);
}
}
copied to clipboard
Active & Purchased Products #
A typical riverpod notifier implementation would look like this:
class PremiumPurchaseNotifier extends Notifier<bool?> {
Future<void> _initBilling() async {
final activeProducts = await ref.read(billingRepositoryProvider).load();
state = activeProducts.isNotEmpty;
print("Billing initialized $state");
}
@override
bool? build() {
_initBilling();
final purchaseStream = ref.read(billingRepositoryProvider).purchaseStream().listen((LinkFiveActiveProducts event) {
print("Purchase Update $event");
state = event.isNotEmpty;
});
ref.onDispose(() {
purchaseStream.cancel();
});
return null;
}
}
copied to clipboard
LinkFiveActiveProducts holds all active purchases, Subscriptions and One-Time Purchases.
// LinkFiveActiveProducts
activeProducts.planList;
activeProducts.oneTimePurchaseList;
copied to clipboard
Wrap LinkFive into a repository for testing #
You can wrap the Purchases implementation inside a Repository pattern or use it directly.
final billingRepositoryProvider = Provider<BillingRepository>((ref) => LinkFiveBillingRepository());
class LinkFiveBillingRepository extends BillingRepository {
@override
Future<LinkFiveActiveProducts> load() async {
return LinkFivePurchases.init(
LinkFiveKey().apiKey,
logLevel: LinkFiveLogLevel.DEBUG,
);
}
@override
Future<LinkFiveProducts?> fetchOffering() {
return LinkFivePurchases.fetchProducts();
}
@override
Future<LinkFiveActiveProducts> loadActiveProducts() async {
return LinkFivePurchases.reloadActivePlans();
}
@override
void purchase(ProductDetails productDetails) async {
await LinkFivePurchases.purchase(productDetails);
}
@override
Future<void> restore() async {
await LinkFivePurchases.restore();
}
Stream<LinkFiveActiveProducts> listenToPurchases() {
return LinkFivePurchases.activeProducts;
}
@override
Stream<LinkFiveActiveProducts> purchaseStream() {
return LinkFivePurchases.activeProducts;
}
@override
Stream<bool> purchaseInProgressStream() {
return LinkFivePurchases.purchaseInProgressStream;
}
}
copied to clipboard
ProductDetails, Pricing Phase & Google‘s new Base Plans approach #
Google changed how they handle subscriptions and added Base Plans & PricingPhases to it's new data model. Unfortunately, the in_app_purchase library exposes different models depending on the platform.
We decided to combine the ProductDetails class into a simple to use class called LinkFiveProductDetails which holds pricingPhases for both platforms, Android & iOS.
class LinkFiveProductDetails {
/// Platform dependent Product Details such as GooglePlayProductDetails or AppStoreProductDetails
final ProductDetails productDetails;
/// Base64 encoded attributes which you can define on LinkFive
final String? attributes;
/// Converts the new Google Play & AppStore Model to a known list of pricing phases
List<PricingPhase> get pricingPhases;
}
copied to clipboard
Pricing Phase #
The PricingPhase class now holds all information about the product and it's phases. An example would be a FreeTrial phase and a yearly subscription as 2 elements in the PricingPhase list.
Here are all interesting parts of the class:
class PricingPhase {
/// Represents a pricing phase, describing how a user pays at a point in time.
int get billingCycleCount;
/// Billing period for which the given price applies, specified in ISO 8601 format.
Period get billingPeriod;
/// Returns formatted price for the payment cycle, including its currency sign.
String get formattedPrice;
/// Returns the price for the payment cycle in micro-units, where 1,000,000
/// micro-units equal one unit of the currency.
int get priceAmountMicros;
/// ISO 4217 e.g. EUR, USD
String get priceCurrencyCode;
/// Recurrence of the phase
Recurrence get recurrence;
}
copied to clipboard
Period & PeriodUnit class #
The Period class now holds the length of a subscription.
class Period {
final int amount;
final PeriodUnit periodUnit;
}
enum PeriodUnit {
DAYS('D'),
WEEKS('W'),
MONTH('M'),
YEARS('Y');
}
copied to clipboard
A Period of 3 months would be Period(amount: 3, periodUnit: MONTH) and a year would be Period(amount: 1, periodUnit: YEAR),
From the Period class to a readable user-friendly text
We added a intl-localization-package which uses the intl package that can help you translate the Period into a readable text.
Here is the package on pub.dev
You can use the isoCode from the billingPeriod to get a readable String:
final translationClass = pricingPhase.billingPeriod.iso8601.fromIso8601(PaywallL10NHelper.of(context));
copied to clipboard
The translation class will output:
7 days from P7D
1 month from P1M
3 months from P3M (or also quarterly)
1 year from P1Y (or also yearly)
You can submit your own translation to it's github Repository.
Easy Integration with the Paywall UI package #
Integrate linkfive_purchases with package in_app_purchases_paywall_ui.
it's working with just passing the LinkFive client to the UI library
Automatic purchase state management
The UI is fully customizable
You can control the UI on our Website
Purchase Page #
Success Page #
Page State Management #
That‘s it. Now the page will automatically offer the subscriptions to the user or if the user already bought the subscription, the paywall will show the success page.
For personal and professional use. You cannot resell or redistribute these repositories in their original state.
There are no reviews.