in_app_purchaser
DELEGATES
ADAPTY DELEGATE
import 'dart:io';
import 'package:adapty_flutter/adapty_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:in_app_purchaser/in_app_purchaser.dart';
class AdaptyInAppPurchaseDelegate extends InAppPurchaseDelegate {
const AdaptyInAppPurchaseDelegate();
Adapty get instance => Adapty();
@override
Set<String> get placements => {"default"};
@override
Future<void> init() async {
await instance.setLogLevel(AdaptyLogLevel.debug);
bool isActivated = kDebugMode ? await instance.isActivated() : false;
if (isActivated) return instance.setupAfterHotRestart();
await instance.activate(
configuration: AdaptyConfiguration(apiKey: "API_KEY")
..withLogLevel(AdaptyLogLevel.debug)
..withObserverMode(false)
..withCustomerUserId("USER_ID")
..withIpAddressCollectionDisabled(false)
..withAppleIdfaCollectionDisabled(false)
..withGoogleAdvertisingIdCollectionDisabled(false),
);
await _fallbacks();
}
Future<void> _fallbacks() async {
try {
final path = Platform.isIOS
? 'assets/fallbacks/ios.json'
: 'assets/fallbacks/android.json';
await instance.setFallback(path);
} catch (_) {}
}
@override
Stream<InAppPurchaseProfile> get stream {
return instance.didUpdateProfileStream.asyncMap(profile);
}
@override
Future<void> login(String uid) => instance.identify(uid);
@override
Future<void> initAdjustSdk() async {}
@override
Future<void> initFacebookSdk() async {}
@override
Future<void> logout() => instance.logout();
@override
Future<InAppPurchaseOffering> offering(String placement) async {
final paywall = await instance.getPaywall(placementId: placement);
final configs = paywall.remoteConfig?.dictionary ?? {};
final products = await instance.getPaywallProducts(paywall: paywall).then((
products,
) {
return products.map((e) {
return InAppPurchaseProduct(
id: e.vendorProductId,
plan: e.localizedTitle,
description: e.localizedDescription,
currency: e.price.currencySymbol ?? e.price.currencyCode ?? "USD",
price: e.price.amount,
priceString: e.price.localizedString ?? "0.0\$",
raw: e,
);
});
});
return InAppPurchaseOffering(
id: paywall.placement.id,
products: List.of(products),
configs: configs,
);
}
@override
Future<InAppPurchaseProfile> profile(Object? raw) async {
if (raw is! AdaptyProfile) return instance.getProfile().then(profile);
return InAppPurchaseProfile(
profileId: raw.profileId,
customAttributes: raw.customAttributes,
accessLevels: raw.accessLevels.map((k, v) {
return MapEntry(
k,
InAppPurchaseAccessLevel(
id: v.id,
isActive: v.isActive,
vendorProductId: v.vendorProductId,
store: v.store,
activatedAt: v.activatedAt,
renewedAt: v.renewedAt,
expiresAt: v.expiresAt,
isLifetime: v.isLifetime,
activeIntroductoryOfferType: v.activeIntroductoryOfferType,
activePromotionalOfferType: v.activePromotionalOfferType,
activePromotionalOfferId: v.activePromotionalOfferId,
offerId: v.offerId,
willRenew: v.willRenew,
isInGracePeriod: v.isInGracePeriod,
unsubscribedAt: v.unsubscribedAt,
billingIssueDetectedAt: v.billingIssueDetectedAt,
startsAt: v.startsAt,
cancellationReason: v.cancellationReason,
isRefund: v.isRefund,
isSandbox: raw.isTestUser,
ownershipType: '',
productPlanIdentifier: '',
verification: '',
),
);
}),
subscriptions: raw.subscriptions.map((k, v) {
return MapEntry(
k,
InAppPurchaseSubscription(
store: v.store,
vendorProductId: v.vendorProductId,
vendorTransactionId: v.vendorTransactionId,
vendorOriginalTransactionId: v.vendorOriginalTransactionId,
isActive: v.isActive,
isLifetime: v.isLifetime,
activatedAt: v.activatedAt,
renewedAt: v.renewedAt,
expiresAt: v.expiresAt,
startsAt: v.startsAt,
unsubscribedAt: v.unsubscribedAt,
billingIssueDetectedAt: v.billingIssueDetectedAt,
isInGracePeriod: v.isInGracePeriod,
isSandbox: v.isSandbox,
isRefund: v.isRefund,
willRenew: v.willRenew,
activeIntroductoryOfferType: v.activeIntroductoryOfferType,
activePromotionalOfferType: v.activePromotionalOfferType,
activePromotionalOfferId: v.activePromotionalOfferId,
offerId: v.offerId,
cancellationReason: v.cancellationReason,
),
);
}),
nonSubscriptions: raw.nonSubscriptions.map((k, v) {
return MapEntry(
k,
v.map((e) {
return InAppPurchaseNonSubscription(
purchaseId: e.purchaseId,
store: e.store,
vendorProductId: e.vendorProductId,
vendorTransactionId: e.vendorTransactionId,
purchasedAt: e.purchasedAt,
isSandbox: e.isSandbox,
isRefund: e.isRefund,
isConsumable: e.isConsumable,
);
}).toList(),
);
}),
isTestUser: raw.isTestUser,
);
}
@override
Future<InAppPurchaseResult> purchase(InAppPurchaseProduct product) async {
final raw = product.raw;
if (raw is! AdaptyPaywallProduct) return InAppPurchaseResultInvalid();
return instance.makePurchase(product: raw).then((result) async {
switch (result) {
case AdaptyPurchaseResultPending():
return InAppPurchaseResultPending();
case AdaptyPurchaseResultUserCancelled():
return InAppPurchaseResultUserCancelled();
case AdaptyPurchaseResultSuccess():
return InAppPurchaseResultSuccess(
product: product,
profile: await profile(result.profile),
jwsTransaction: result.jwsTransaction,
);
}
});
}
@override
Future<void> purchased(InAppPurchaseResultSuccess result) async {
// handle purchased
}
@override
Future<InAppPurchaseProfile?> restore() {
return instance.restorePurchases().then(profile);
}
}
REVENUE_CAT DELEGATE
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:in_app_purchaser/in_app_purchaser.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
class RevenueCatInAppPurchaseDelegate extends InAppPurchaseDelegate {
const RevenueCatInAppPurchaseDelegate();
@override
Set<String> get placements => {"default"};
@override
Future<void> init() async {
await Purchases.configure(
PurchasesConfiguration("API_KEY")..appUserID = "USER_ID",
);
await Purchases.collectDeviceIdentifiers();
}
@override
Stream<InAppPurchaseProfile> get stream {
final controller = StreamController();
Purchases.addCustomerInfoUpdateListener(controller.add);
return controller.stream.asyncMap(profile);
}
@override
Future<void> login(String uid) => Purchases.logIn(uid);
@override
Future<void> initAdjustSdk() async {}
@override
Future<void> initFacebookSdk() async {}
@override
Future<void> logout() => Purchases.logOut();
@override
Future<InAppPurchaseOffering> offering(String placement) async {
final offerings = await Purchases.getOfferings();
Offering? offering = offerings.getOffering(placement);
offering ??= offerings.current;
if (offering == null) return const InAppPurchaseOffering.empty();
final products = offering.availablePackages.map((e) {
return InAppPurchaseProduct(
id: e.storeProduct.identifier,
plan: e.storeProduct.title,
description: e.storeProduct.description,
currency: e.storeProduct.currencyCode,
price: e.storeProduct.price,
priceString: e.storeProduct.priceString,
raw: e,
);
});
return InAppPurchaseOffering(
id: offering.identifier,
products: List.of(products),
configs: offering.metadata,
);
}
@override
Future<InAppPurchaseProfile> profile(Object? raw) async {
if (raw is! CustomerInfo) return Purchases.getCustomerInfo().then(profile);
return InAppPurchaseProfile(
profileId: raw.originalAppUserId,
customAttributes: {},
accessLevels: raw.entitlements.all.map((k, v) {
return MapEntry(
k,
InAppPurchaseAccessLevel(
id: v.identifier,
isActive: v.isActive,
isSandbox: v.isSandbox,
vendorProductId: v.productIdentifier,
store: v.store.name,
activatedAt: DateTime.tryParse(v.latestPurchaseDate) ?? DateTime(0),
renewedAt: DateTime.tryParse(v.latestPurchaseDate) ?? DateTime(0),
expiresAt: DateTime.tryParse(v.expirationDate ?? '') ?? DateTime(0),
isLifetime: v.expirationDate == null && v.isActive,
activeIntroductoryOfferType:
[PeriodType.intro, PeriodType.trial].contains(v.periodType)
? v.periodType.name
: null,
activePromotionalOfferType:
[PeriodType.prepaid].contains(v.periodType)
? v.periodType.name
: null,
activePromotionalOfferId: v.periodType.name,
offerId: v.identifier,
willRenew: v.willRenew,
isInGracePeriod: false,
unsubscribedAt:
DateTime.tryParse(v.unsubscribeDetectedAt ?? '') ?? DateTime(0),
billingIssueDetectedAt:
DateTime.tryParse(v.billingIssueDetectedAt ?? '') ??
DateTime(0),
startsAt: DateTime.tryParse(v.originalPurchaseDate) ?? DateTime(0),
cancellationReason: null,
isRefund: false,
productPlanIdentifier: v.productPlanIdentifier,
verification: v.verification.name,
ownershipType: v.ownershipType.name,
),
);
}),
subscriptions: raw.entitlements.active.map((k, v) {
return MapEntry(
k,
InAppPurchaseSubscription(
vendorTransactionId: '',
vendorOriginalTransactionId: '',
isActive: v.isActive,
isSandbox: v.isSandbox,
vendorProductId: v.productIdentifier,
store: v.store.name,
activatedAt: DateTime.tryParse(v.latestPurchaseDate) ?? DateTime(0),
renewedAt: DateTime.tryParse(v.latestPurchaseDate) ?? DateTime(0),
expiresAt: DateTime.tryParse(v.expirationDate ?? '') ?? DateTime(0),
isLifetime: v.expirationDate == null && v.isActive,
activeIntroductoryOfferType:
[PeriodType.intro, PeriodType.trial].contains(v.periodType)
? v.periodType.name
: null,
activePromotionalOfferType:
[PeriodType.prepaid].contains(v.periodType)
? v.periodType.name
: null,
activePromotionalOfferId: v.periodType.name,
offerId: v.identifier,
willRenew: v.willRenew,
isInGracePeriod: false,
unsubscribedAt:
DateTime.tryParse(v.unsubscribeDetectedAt ?? '') ?? DateTime(0),
billingIssueDetectedAt:
DateTime.tryParse(v.billingIssueDetectedAt ?? '') ??
DateTime(0),
startsAt: DateTime.tryParse(v.originalPurchaseDate) ?? DateTime(0),
cancellationReason: null,
isRefund: false,
),
);
}),
nonSubscriptions: {
"purchases": raw.nonSubscriptionTransactions.map((e) {
return InAppPurchaseNonSubscription(
purchaseId: e.transactionIdentifier,
store: "app_store",
vendorProductId: e.productIdentifier,
vendorTransactionId: e.transactionIdentifier,
purchasedAt: DateTime.tryParse(e.purchaseDate) ?? DateTime(0),
isSandbox: false,
isRefund: false,
isConsumable: true,
);
}).toList(),
},
isTestUser: raw.entitlements.all.values.any((e) => e.isSandbox),
);
}
@override
Future<InAppPurchaseResult> purchase(InAppPurchaseProduct product) async {
final raw = product.raw;
if (raw is! StoreProduct) return InAppPurchaseResultInvalid();
try {
final customerInfo = await Purchases.purchase(
PurchaseParams.storeProduct(raw),
);
return InAppPurchaseResultSuccess(
product: product,
profile: await profile(customerInfo.customerInfo),
jwsTransaction: customerInfo.storeTransaction.transactionIdentifier,
);
} on PlatformException catch (e) {
if (e.code == PurchasesErrorCode.purchaseCancelledError.name) {
return InAppPurchaseResultUserCancelled();
} else if ([
PurchasesErrorCode.purchaseNotAllowedError.name,
PurchasesErrorCode.paymentPendingError.name,
].contains(e.code)) {
return InAppPurchaseResultPending();
} else if ([
PurchasesErrorCode.purchaseInvalidError.name,
].contains(e.code)) {
return InAppPurchaseResultInvalid();
} else {
return InAppPurchaseResultFailed();
}
} catch (_) {
return InAppPurchaseResultFailed();
}
}
@override
Future<void> purchased(InAppPurchaseResultSuccess result) async {
// handle purchased
}
@override
Future<InAppPurchaseProfile?> restore() {
return Purchases.restorePurchases().then(profile);
}
}
QONVERSION DELEGATE
import 'package:in_app_purchaser/in_app_purchaser.dart';
import 'package:qonversion_flutter/qonversion_flutter.dart';
class QonversionInAppPurchaseDelegate extends InAppPurchaseDelegate {
const QonversionInAppPurchaseDelegate();
Qonversion get instance => Qonversion.getSharedInstance();
@override
Set<String> get placements => {"default"};
@override
Future<void> init() async {
final config = QonversionConfigBuilder(
"API_KEY",
QLaunchMode.subscriptionManagement,
).build();
Qonversion.initialize(config);
await instance.collectAdvertisingId();
}
@override
Stream<InAppPurchaseProfile> get stream {
return instance.updatedEntitlementsStream.asyncMap(profile);
}
@override
Future<void> login(String uid) => instance.identify(uid);
@override
Future<void> initAdjustSdk() async {}
@override
Future<void> initFacebookSdk() async {}
@override
Future<void> logout() => instance.logout();
@override
Future<InAppPurchaseOffering> offering(String placement) async {
final offerings = await instance.offerings();
QOffering? offering = offerings.offeringForIdentifier(placement);
offering ??= offerings.main;
if (offering == null) return const InAppPurchaseOffering.empty();
final products = offering.products.map((e) {
return InAppPurchaseProduct(
id: e.qonversionId,
plan: e.storeDetails?.title,
description: e.storeDetails?.description,
currency: e.currencyCode,
price: e.price,
priceString: e.prettyPrice,
raw: e,
);
});
final configs = await instance.remoteConfig(contextKey: placement);
return InAppPurchaseOffering(
id: offering.id,
products: List.of(products),
configs: configs.payload,
);
}
@override
Future<InAppPurchaseProfile> profile(Object? raw) async {
if (raw is! Map<String, QEntitlement>) {
return instance.checkEntitlements().then(profile);
}
final pro = await instance.userInfo();
return InAppPurchaseProfile(
profileId: pro.identityId ?? pro.qonversionId,
customAttributes: {},
accessLevels: raw.map((k, v) {
return MapEntry(
k,
InAppPurchaseAccessLevel(
id: v.id,
isActive: v.isActive,
vendorProductId: v.productId,
store: v.source.name,
activatedAt: v.startedDate ?? DateTime(0),
renewedAt: v.lastPurchaseDate,
expiresAt: v.expirationDate,
isLifetime: v.isActive && v.expirationDate == null,
activeIntroductoryOfferType:
v.trialStartDate != null ? "trial" : null,
activePromotionalOfferType:
v.lastActivatedOfferCode != null ? "promo" : null,
activePromotionalOfferId: v.lastActivatedOfferCode,
offerId: "",
willRenew: v.renewState == QEntitlementRenewState.willRenew,
isInGracePeriod: false,
unsubscribedAt: v.autoRenewDisableDate,
billingIssueDetectedAt: null,
startsAt: v.firstPurchaseDate,
cancellationReason: null,
isRefund: false,
isSandbox: v.transactions.any(
(t) => t.environment.name == QEnvironment.sandbox.name,
),
ownershipType: v.grantType.name,
productPlanIdentifier: v.productId,
verification: "",
),
);
}),
subscriptions: raw.map((k, v) {
return MapEntry(
k,
InAppPurchaseSubscription(
store: v.source.name,
vendorProductId: v.productId,
vendorTransactionId: v.transactions.isNotEmpty
? v.transactions.last.transactionId
: "",
vendorOriginalTransactionId: v.transactions.isNotEmpty
? v.transactions.first.transactionId
: "",
isActive: v.isActive,
isLifetime: v.isActive && v.expirationDate == null,
activatedAt: v.startedDate ?? DateTime(0),
renewedAt: v.lastPurchaseDate,
expiresAt: v.expirationDate,
startsAt: v.firstPurchaseDate,
unsubscribedAt: v.autoRenewDisableDate,
billingIssueDetectedAt: null,
isInGracePeriod: false,
isSandbox: v.transactions.any(
(t) => t.environment.name == QEnvironment.sandbox.name,
),
isRefund: false,
willRenew: v.renewState == QEntitlementRenewState.willRenew,
activeIntroductoryOfferType:
v.trialStartDate != null ? "trial" : null,
activePromotionalOfferType:
v.lastActivatedOfferCode != null ? "promo" : null,
activePromotionalOfferId: v.lastActivatedOfferCode,
offerId: "",
cancellationReason: "",
),
);
}),
nonSubscriptions: {
"transactions": raw.values
.where(
(t) =>
t.transactions.any(
(e) => e.type.name == "nonConsumablePurchase",
),
)
.map((t) {
return InAppPurchaseNonSubscription(
purchaseId: t.transactions.lastOrNull?.transactionId ?? '',
store: '',
vendorProductId: t.productId,
vendorTransactionId: t.transactions.lastOrNull?.transactionId,
purchasedAt: t.startedDate ?? DateTime(0),
isSandbox: t.transactions.any(
(e) => e.environment.name == QEnvironment.sandbox.name,
),
isRefund: false,
isConsumable: t.transactions.any(
(e) => e.type.name == "subscriptionStarted",
),
);
}).toList(),
},
isTestUser: raw.values.any(
(e) =>
e.transactions.any(
(e) => e.environment.name == QEnvironment.sandbox.name,
),
),
);
}
@override
Future<InAppPurchaseResult> purchase(InAppPurchaseProduct product) async {
final raw = product.raw;
if (raw is! QProduct) return InAppPurchaseResultInvalid();
return instance.purchaseProduct(raw).then((result) async {
return InAppPurchaseResultSuccess(
product: product,
profile: await profile(result),
jwsTransaction: '',
);
});
}
@override
Future<void> purchased(InAppPurchaseResultSuccess result) async {
// handle purchased
}
@override
Future<InAppPurchaseProfile?> restore() {
return instance.restore().then(profile);
}
}
USE CASE
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:in_app_purchaser/in_app_purchaser.dart';
import 'adapty.dart';
import 'qonversion.dart';
import 'revenue_cat.dart';
void main() {
runApp(
InAppPurchaseProvider(
enabled: !kIsWeb,
delegate: const [
AdaptyInAppPurchaseDelegate(),
RevenueCatInAppPurchaseDelegate(),
QonversionInAppPurchaseDelegate(),
][Platform.isIOS ? 0 : 2],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'In App Purchaser',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const PurchasePaywall(),
);
}
}
class PurchasePaywall extends StatelessWidget {
const PurchasePaywall({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.shade100,
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(32),
child: Stack(
children: [
ListenableBuilder(
listenable: InAppPurchaser.i,
builder: (context, child) {
return const PurchaseButton();
},
),
],
),
),
),
);
}
}
class PurchaseButton extends StatefulWidget {
const PurchaseButton({
super.key,
});
@override
State<PurchaseButton> createState() => _PurchaseButtonState();
}
class _PurchaseButtonState extends State<PurchaseButton> {
int selected = 0;
@override
Widget build(BuildContext context) {
final products = InAppPurchaser.products();
return Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.only(left: 15, right: 15, bottom: 5),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
'Get Unlimited Access',
style: TextStyle(
fontSize: 16,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
Column(
children: List.generate(products.length, (index) {
final product = products.elementAtOrNull(index);
final priceString = product?.priceString ?? '0 BDT';
final monthlyPrice = (product?.price ?? 0) / 12;
return MonthlyPlan(
leftSideString: priceString,
selected: selected == index,
rightSideString: monthlyPrice.toString(),
discount: "25%",
onTap: () => setState(() => selected = index),
);
}),
),
const SizedBox(height: 5),
CupertinoButton(
padding: EdgeInsets.zero,
onPressed: () => InAppPurchaser.purchaseAt(selected),
child: Container(
height: 65,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1000),
color: Colors.black,
),
padding: const EdgeInsets.symmetric(
horizontal: 20,
),
alignment: Alignment.center,
child: Builder(builder: (context) {
if (products.isEmpty) {
return const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(
color: Colors.white,
strokeWidth: 3,
),
);
}
return const FittedBox(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w700,
),
),
);
}),
),
),
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
child: const Text(
'Terms ',
style: TextStyle(
fontSize: 10,
color: Colors.grey,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
decorationColor: Colors.grey,
),
),
),
GestureDetector(
onTap: InAppPurchaser.restore,
child: const Text(
'Restore ',
style: TextStyle(
fontSize: 10,
color: Colors.grey,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
decorationColor: Colors.grey,
),
),
),
GestureDetector(
child: const Text(
'Privacy ',
style: TextStyle(
fontSize: 10,
color: Colors.grey,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
decorationColor: Colors.grey,
),
),
),
GestureDetector(
child: const Text(
'User ID ',
style: TextStyle(
fontSize: 10,
color: Colors.grey,
fontWeight: FontWeight.w600,
decoration: TextDecoration.underline,
decorationColor: Colors.grey,
),
),
),
],
),
)
],
),
),
);
}
}
class MonthlyPlan extends StatelessWidget {
final String leftSideString;
final bool selected;
final String rightSideString;
final String? discount;
final VoidCallback onTap;
const MonthlyPlan({
super.key,
required this.leftSideString,
required this.selected,
required this.rightSideString,
required this.onTap,
this.discount,
});
@override
Widget build(BuildContext context) {
return CupertinoButton(
padding: EdgeInsets.zero,
onPressed: onTap,
child: Container(
height: 76,
margin: const EdgeInsets.only(bottom: 10),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: selected ? Colors.black : Colors.transparent,
width: 3,
),
),
padding: const EdgeInsets.only(
right: 20,
left: 15,
),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerLeft,
children: [
Text(
leftSideString,
style: const TextStyle(
fontSize: 16,
color: Colors.black,
),
),
Align(
alignment: Alignment.centerRight,
child: Text.rich(
textScaler: TextScaler.noScaling,
TextSpan(
children: [
TextSpan(
text: rightSideString, //inchToReadableFt(heightHive),
style: const TextStyle(
fontSize: 15,
color: Colors.black,
fontWeight: FontWeight.w700,
),
),
// TextSpan(
// text: extension,
// style: const TextStyle(
// fontFamily: defaultFont,
// fontSize: Sizex.h6 + 1,
// fontWeight: Weightx.heavy,
// color: Colorx.black,
// ),
// ),
],
),
),
),
if ((discount ?? '').isNotEmpty) ...[
Positioned(
top: -15,
right: -0,
child: Container(
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(105),
),
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
child: Text(
discount!,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
],
],
),
),
);
}
}