app购买中的Flutter检查购买是否成功



我正试图在我的flutter应用程序中实现应用内购买,一切都很好,只有一件事我被困是当用户支付完成(成功或错误),它将只是打印,所以我将继续我的下一步,以保存服务器上的数据,但我找不到在哪里我可以得到报酬是成功完成与否。我只是从插件中复制粘贴代码,它是99%相同的,但我被卡在它显示成功提示的行或函数上。

代码
const List<String> _kProductIds = <String>[
'subscription_gold',
];
class paymentScreen extends StatefulWidget {
@override
_paymentScreenState createState() => _paymentScreenState();
}
class _paymentScreenState extends State<paymentScreen> {
final _formKey = GlobalKey<FormState>();
final TextEditingController userLink = new TextEditingController();
String accestoken = accessTokenGlobal;
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _subscription;
List<String> _notFoundIds = [];
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
List<String> _consumables = [];
bool _isAvailable = false;
bool _purchasePending = false;
bool _loading = true;
String? _queryProductError;
@override
void initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
initStoreInfo();
super.initState();
}
Future<void> initStoreInfo() async {
final bool isAvailable = await _inAppPurchase.isAvailable();
if (!isAvailable) {
setState(() {
_isAvailable = isAvailable;
_products = [];
_purchases = [];
_notFoundIds = [];
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
if (Platform.isIOS) {
var iosPlatformAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
await iosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
}
ProductDetailsResponse productDetailResponse =
await _inAppPurchase.queryProductDetails(_kProductIds.toSet());
if (productDetailResponse.error != null) {
setState(() {
_queryProductError = productDetailResponse.error!.message;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
if (productDetailResponse.productDetails.isEmpty) {
setState(() {
_queryProductError = null;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
List<String> consumables = await ConsumableStore.load();
setState(() {
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = consumables;
_purchasePending = false;
_loading = false;
});
}
@override
void dispose() {
if (Platform.isIOS) {
var iosPlatformAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
iosPlatformAddition.setDelegate(null);
}
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
double Width = MediaQuery.of(context).size.width;
double Height = MediaQuery.of(context).size.height;
double Status = MediaQuery.of(context).padding.top;
return Scaffold(
bottomNavigationBar: Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
print(_products);
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(builder: (BuildContext context,
StateSetter setState /*You can rename this!*/) {
return Container(
height: 200,
child: ListView(
children: [
_buildProductList(),
],
));
});
});
},
child: Container(
width: Width * 0.9,
height: Height * 0.06,
decoration: BoxDecoration(
color: kPrimaryColor, borderRadius: BorderRadius.circular(5)),
child: Center(
child: Text(
'Subscribe Now',
style: TextStyle(
color: Colors.white, fontSize: 16, fontFamily: 'SegoeUI'),
),
),
),
),
),
);
}

Card _buildProductList() {
if (_loading) {
return Card(
child: (ListTile(
leading: CircularProgressIndicator(),
title: Text('Fetching products...'))));
}
if (!_isAvailable) {
return Card();
}
final ListTile productHeader = ListTile(title: Text('Subscriptions'));
List<ListTile> productList = <ListTile>[];
if (_notFoundIds.isNotEmpty) {
productList.add(ListTile(
title: Text('[${_notFoundIds.join(", ")}] not found',
style: TextStyle(color: ThemeData.light().errorColor)),
subtitle: Text(
'This app needs special configuration to run. Please see example/README.md for instructions.')));
}
// This loading previous purchases code is just a demo. Please do not use this as it is.
// In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it.
// We recommend that you use your own server to verify the purchase data.
Map<String, PurchaseDetails> purchases =
Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
_inAppPurchase.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
}));
productList.addAll(_products.map(
(ProductDetails productDetails) {
PurchaseDetails? previousPurchase = purchases[productDetails.id];
var date = DateTime.now().toString();
var dateParse = DateTime.parse(date);
var formattedDate =
"${dateParse.day}-${dateParse.month}-${dateParse.year + 1}";
return ListTile(
title: Text(
'Yearly',
),
subtitle: Text(
'Your subscription will expire on ${formattedDate}',
),
trailing: previousPurchase != null
? IconButton(
onPressed: () => confirmPriceChange(context),
icon: Icon(Icons.upgrade))
: TextButton(
child: Text(productDetails.price),
style: TextButton.styleFrom(
backgroundColor: kPrimaryColor,
primary: Colors.white,
),
onPressed: () {
late PurchaseParam purchaseParam;
if (Platform.isAndroid) {
// NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to
// verify the latest status of you your subscription by using server side receipt validation
// and update the UI accordingly. The subscription purchase status shown
// inside the app may not be accurate.
final oldSubscription =
_getOldSubscription(productDetails, purchases);
purchaseParam = GooglePlayPurchaseParam(
productDetails: productDetails,
applicationUserName: null,
changeSubscriptionParam: (oldSubscription != null)
? ChangeSubscriptionParam(
oldPurchaseDetails: oldSubscription,
prorationMode: ProrationMode
.immediateWithTimeProration,
)
: null);
} else {
purchaseParam = PurchaseParam(
productDetails: productDetails,
applicationUserName: null,
);
}
if (productDetails.id == _kConsumableId) {
_inAppPurchase.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: _kAutoConsume || Platform.isIOS);
} else {
_inAppPurchase.buyNonConsumable(
purchaseParam: purchaseParam);
}
},
));
},
));
return Card(
child: Column(
children: <Widget>[
Container(
color: Color(0xfff5f6fb),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
'You can cancel subscription from playstore.',
style: TextStyle(
fontFamily: 'PoppinsRegular',
fontSize: 15,
color: Color(0xff8f9ba8)),
)),
),
),
SizedBox(
height: MediaQuery.of(context).size.height * 0.01,
),
] +
productList));
}
void showPendingUI() {
setState(() {
_purchasePending = true;
});
}
void deliverProduct(PurchaseDetails purchaseDetails) async {
// IMPORTANT!! Always verify purchase details before delivering the product.
if (purchaseDetails.productID == _kConsumableId) {
await ConsumableStore.save(purchaseDetails.purchaseID!);
List<String> consumables = await ConsumableStore.load();
setState(() {
_purchasePending = false;
_consumables = consumables;
});
} else {
setState(() {
_purchases.add(purchaseDetails);
_purchasePending = false;
});
}
}
void handleError(IAPError error) {
setState(() {
_purchasePending = false;
});
}
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
// IMPORTANT!! Always verify a purchase before delivering the product.
// For the purpose of an example, we directly return true.
return Future<bool>.value(true);
}
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
// handle invalid purchase here if  _verifyPurchase` failed.
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
handleError(purchaseDetails.error!);
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
return;
}
}
if (Platform.isAndroid) {
if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) {
final InAppPurchaseAndroidPlatformAddition androidAddition =
_inAppPurchase.getPlatformAddition<
InAppPurchaseAndroidPlatformAddition>();
await androidAddition.consumePurchase(purchaseDetails);
}
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
}
});
}
Future<void> confirmPriceChange(BuildContext context) async {
if (Platform.isAndroid) {
final InAppPurchaseAndroidPlatformAddition androidAddition =
_inAppPurchase
.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
var priceChangeConfirmationResult =
await androidAddition.launchPriceChangeConfirmationFlow(
sku: 'purchaseId',
);
if (priceChangeConfirmationResult.responseCode == BillingResponse.ok) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('Price change accepted'),
));
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(
priceChangeConfirmationResult.debugMessage ??
"Price change failed with code ${priceChangeConfirmationResult.responseCode}",
),
));
}
}
if (Platform.isIOS) {
var iapIosPlatformAddition = _inAppPurchase
.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
await iapIosPlatformAddition.showPriceConsentIfNeeded();
}
}
GooglePlayPurchaseDetails? _getOldSubscription(
ProductDetails productDetails, Map<String, PurchaseDetails> purchases) {
GooglePlayPurchaseDetails? oldSubscription;
if (productDetails.id == _kSilverSubscriptionId &&
purchases[_kGoldSubscriptionId] != null) {
oldSubscription =
purchases[_kGoldSubscriptionId] as GooglePlayPurchaseDetails;
} else if (productDetails.id == _kGoldSubscriptionId &&
purchases[_kSilverSubscriptionId] != null) {
oldSubscription =
purchases[_kSilverSubscriptionId] as GooglePlayPurchaseDetails;
}
return oldSubscription;
}
}
/// Example implementation of the
/// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc).
///
/// The payment queue delegate can be implementated to provide information
/// needed to complete transactions.
class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
@override
bool shouldContinueTransaction(
SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
return true;
}
@override
bool shouldShowPriceConsent() {
return false;
}
}
class ConsumableStore {
static const String _kPrefKey = 'consumables';
static Future<void> _writes = Future.value();
/// Adds a consumable with ID `id` to the store.
///
/// The consumable is only added after the returned Future is complete.
static Future<void> save(String id) {
_writes = _writes.then((void _) => _doSave(id));
return _writes;
}
/// Consumes a consumable with ID `id` from the store.
///
/// The consumable was only consumed after the returned Future is complete.
static Future<void> consume(String id) {
_writes = _writes.then((void _) => _doConsume(id));
return _writes;
}
/// Returns the list of consumables from the store.
static Future<List<String>> load() async {
return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ??
[];
}
static Future<void> _doSave(String id) async {
List<String> cached = await load();
SharedPreferences prefs = await SharedPreferences.getInstance();
cached.add(id);
await prefs.setStringList(_kPrefKey, cached);
}
static Future<void> _doConsume(String id) async {
List<String> cached = await load();
SharedPreferences prefs = await SharedPreferences.getInstance();
cached.remove(id);
await prefs.setStringList(_kPrefKey, cached);
}
}

在您的_listenToPurchaseUpdated函数中,您可以通过以下方式检查项目是否已购买:

if (purchaseDetails.status == PurchaseStatus.purchased) { // your code goes here .. }

我建议创建一个单例类来处理IAP,这将给你更多的可靠性和代码可重用性