контроллер Superwall < /p>
Код: Выделить всё
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:superwallkit_flutter/superwallkit_flutter.dart' hide LogLevel;
class RCPurchaseController extends PurchaseController {
// MARK: Configure and sync subscription Status
/// Makes sure that Superwall knows the customers subscription status by
/// changing `Superwall.shared.subscriptionStatus`
Future configureAndSyncSubscriptionStatus() async {
// Configure RevenueCat
await Purchases.setLogLevel(LogLevel.debug);
final configuration = Platform.isIOS
? PurchasesConfiguration('ios_rc_key')
: PurchasesConfiguration('android_rc_key');
await Purchases.configure(configuration);
// Listen for changes
Purchases.addCustomerInfoUpdateListener((customerInfo) async {
// Gets called whenever new CustomerInfo is available
final entitlements = customerInfo.entitlements.active.keys
.map((id) => Entitlement(id: id))
.toSet();
final hasActiveEntitlementOrSubscription = customerInfo
.hasActiveEntitlementOrSubscription(); // Why? -> https://www.revenuecat.com/docs/entitlements#entitlements
if (hasActiveEntitlementOrSubscription) {
await Superwall.shared.setSubscriptionStatus(
SubscriptionStatusActive(entitlements: entitlements));
} else {
await Superwall.shared
.setSubscriptionStatus(SubscriptionStatusInactive());
}
});
}
// MARK: Handle Purchases
/// Makes a purchase from App Store with RevenueCat and returns its
/// result. This gets called when someone tries to purchase a product on
/// one of your paywalls from iOS.
@override
Future purchaseFromAppStore(String productId) async {
// Find products matching productId from RevenueCat
final products = await PurchasesAdditions.getAllProducts([productId]);
// Get first product for product ID (this will properly throw if empty)
final storeProduct = products.firstOrNull;
if (storeProduct == null) {
return PurchaseResult.failed(
'Failed to find store product for $productId');
}
final purchaseResult = await _purchaseStoreProduct(storeProduct);
return purchaseResult;
}
/// Makes a purchase from Google Play with RevenueCat and returns its
/// result. This gets called when someone tries to purchase a product on
/// one of your paywalls from Android.
@override
Future purchaseFromGooglePlay(
String productId, String? basePlanId, String? offerId) async {
// Find products matching productId from RevenueCat
List products =
await PurchasesAdditions.getAllProducts([productId]);
// Choose the product which matches the given base plan.
// If no base plan set, select first product or fail.
String storeProductId = "$productId:$basePlanId";
// Try to find the first product where the googleProduct's basePlanId matches the given basePlanId.
StoreProduct? matchingProduct;
// Loop through each product in the products list.
for (final product in products) {
// Check if the current product's basePlanId matches the given basePlanId.
if (product.identifier == storeProductId) {
// If a match is found, assign this product to matchingProduct.
matchingProduct = product;
// Break the loop as we found our matching product.
break;
}
}
// If a matching product is not found, then try to get the first product from the list.
StoreProduct? storeProduct =
matchingProduct ?? (products.isNotEmpty ? products.first : null);
// If no product is found (either matching or the first one), return a failed purchase result.
if (storeProduct == null) {
return PurchaseResult.failed("Product not found");
}
switch (storeProduct.productCategory) {
case ProductCategory.subscription:
SubscriptionOption? subscriptionOption =
await _fetchGooglePlaySubscriptionOption(
storeProduct, basePlanId, offerId);
if (subscriptionOption == null) {
return PurchaseResult.failed(
"Valid subscription option not found for product.");
}
return await _purchaseSubscriptionOption(subscriptionOption);
case ProductCategory.nonSubscription:
return await _purchaseStoreProduct(storeProduct);
case null:
return PurchaseResult.failed("Unable to determine product category");
}
}
Future _fetchGooglePlaySubscriptionOption(
StoreProduct storeProduct,
String? basePlanId,
String? offerId,
) async {
final subscriptionOptions = storeProduct.subscriptionOptions;
if (subscriptionOptions != null && subscriptionOptions.isNotEmpty) {
// Concatenate base + offer ID
final subscriptionOptionId =
_buildSubscriptionOptionId(basePlanId, offerId);
// Find first subscription option that matches the subscription option ID or use the default offer
SubscriptionOption? subscriptionOption;
// Search for the subscription option with the matching ID
for (final option in subscriptionOptions) {
if (option.id == subscriptionOptionId) {
subscriptionOption = option;
break;
}
}
// If no matching subscription option is found, use the default option
subscriptionOption ??= storeProduct.defaultOption;
// Return the subscription option
return subscriptionOption;
}
return null;
}
Future _purchaseSubscriptionOption(
SubscriptionOption subscriptionOption) async {
// Define the async perform purchase function
Future performPurchase() async {
// Attempt to purchase product
CustomerInfo customerInfo =
await Purchases.purchaseSubscriptionOption(subscriptionOption);
return customerInfo;
}
PurchaseResult purchaseResult =
await _handleSharedPurchase(performPurchase);
return purchaseResult;
}
Future _purchaseStoreProduct(
StoreProduct storeProduct) async {
// Define the async perform purchase function
Future performPurchase() async {
// Attempt to purchase product
CustomerInfo customerInfo =
await Purchases.purchaseStoreProduct(storeProduct);
return customerInfo;
}
PurchaseResult purchaseResult =
await _handleSharedPurchase(performPurchase);
return purchaseResult;
}
// MARK: Shared purchase
Future _handleSharedPurchase(
Future Function() performPurchase) async {
try {
// Perform the purchase using the function provided
CustomerInfo customerInfo = await performPurchase();
print(customerInfo.activeSubscriptions.length);
print(customerInfo.entitlements.all.length);
print(customerInfo.entitlements.active);
// Handle the results
if (customerInfo.hasActiveEntitlementOrSubscription()) {
return PurchaseResult.purchased;
} else {
return PurchaseResult.failed("No active subscriptions found.");
}
} on PlatformException catch (e) {
var errorCode = PurchasesErrorHelper.getErrorCode(e);
if (errorCode == PurchasesErrorCode.paymentPendingError) {
return PurchaseResult.pending;
} else if (errorCode == PurchasesErrorCode.purchaseCancelledError) {
return PurchaseResult.cancelled;
} else {
return PurchaseResult.failed(
e.message ?? "Purchase failed in RCPurchaseController");
}
}
}
// MARK: Handle Restores
/// Makes a restore with RevenueCat and returns `.restored`, unless an error is thrown.
/// This gets called when someone tries to restore purchases on one of your paywalls.
@override
Future restorePurchases() async {
try {
CustomerInfo customerInfo = await Purchases.restorePurchases();
await configureAndSyncSubscriptionStatus();
Superwall.shared.setUserAttributes({
'entitlements': customerInfo.entitlements.active.keys.join(','),
'isSubscribed': customerInfo.hasActiveEntitlementOrSubscription(),
});
return RestorationResult.restored;
} on PlatformException catch (e) {
// Error restoring purchases
return RestorationResult.failed(
e.message ?? "Restore failed in RCPurchaseController");
}
}
}
// MARK: Helpers
String _buildSubscriptionOptionId(String? basePlanId, String? offerId) {
String result = '';
if (basePlanId != null) {
result += basePlanId;
}
if (offerId != null) {
if (basePlanId != null) {
result += ':';
}
result += offerId;
}
return result;
}
extension CustomerInfoAdditions on CustomerInfo {
bool hasActiveEntitlementOrSubscription() {
return (activeSubscriptions.isNotEmpty || entitlements.active.isNotEmpty);
}
}
extension PurchasesAdditions on Purchases {
static Future getAllProducts(
List productIdentifiers) async {
final subscriptionProducts = await Purchases.getProducts(productIdentifiers,
productCategory: ProductCategory.subscription);
final nonSubscriptionProducts = await Purchases.getProducts(
productIdentifiers,
productCategory: ProductCategory.nonSubscription);
final combinedProducts = [
...subscriptionProducts,
...nonSubscriptionProducts
];
return combinedProducts;
}
}
< /code>
Конфигурация в main.dart < /p>
RCPurchaseController purchaseController = RCPurchaseController();
await purchaseController.configureAndSyncSubscriptionStatus();
SuperwallOptions options = SuperwallOptions();
options.logging.level = LogLevel.warn;
options.logging.scopes = {
LogScope.paywallPresentation,
LogScope.paywallEvents,
LogScope.transactions,
};
final apiKey = Platform.isAndroid
? ''
: '';
Superwall.configure(
apiKey,
purchaseController: purchaseController,
options: options,
);
final revenueApiKey = Platform.isAndroid
? ''
: '';
await Purchases.setDebugLogsEnabled(true);
await Purchases.configure(PurchasesConfiguration(revenueApiKey));
< /code>
Ошибки
при покупке у Googleplay < /p>
Тело может завершать нормально, вызывая «null», который будет возвращен, но возвращаемый тип, «Futureor», является потенциально не нулевым типом. /> String ProductId,
String?The name 'StoreProduct' is defined in the libraries 'package:purchases_flutter/models/store_product_wrapper.dart (via package:purchases_flutter/purchases_flutter.dart)' and 'package:superwallkit_flutter/src/public/StoreProduct.dart (via package:superwallkit_flutter/superwallkit_flutter.dart)'.
Подробнее здесь: https://stackoverflow.com/questions/796 ... ts-flutter