关于使用Flutter 编写 IOS 内购的完整流程(Flutter+in_app_purchase)
初始化购买流程
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'dart:async';
import 'package:in_app_purchase_storekit/store_kit_wrappers.dart';
class BuyEngine {
static final BuyEngine _instance = BuyEngine._internal();
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
late StreamSubscription<List<PurchaseDetails>> _subscription;
factory BuyEngine() {
return _instance;
}
BuyEngine._internal() {
// 初始化购买监听
final Stream<List<PurchaseDetails>> purchaseUpdated =
_inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen(
_onPurchaseUpdate,
onDone: _onDone,
onError: _onError,
);
}
Future<void> buyNonConsumable(String productId) async {
await clearPendingPurchases();
// 检查商店是否可用
final bool available = await _inAppPurchase.isAvailable();
if (!available) {
throw Exception('商店不可用');
}
// 加载商品信息
final ProductDetailsResponse response =
await _inAppPurchase.queryProductDetails({productId});
if (response.notFoundIDs.isNotEmpty) {
throw Exception('商品未找到');
}
// 检查是否有待处理的交易
// final List<PurchaseDetails> pending =
// await _inAppPurchase.purchaseStream.first;
// final bool hasPendingTransaction = pending.any((purchase) =>
// purchase.productID == productId &&
// purchase.status == PurchaseStatus.pending);
// if (hasPendingTransaction) {
// throw Exception('已有待处理的购买交易,请等待完成或手动完成交易');
// }
// 发起购买
final PurchaseParam purchaseParam =
PurchaseParam(productDetails: response.productDetails.first);
await _inAppPurchase.buyNonConsumable(purchaseParam: purchaseParam);
}
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
// 处理待处理状态
} else if (purchaseDetails.status == PurchaseStatus.error) {
// 处理错误状态
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
// 验证购买凭证
bool isValid = await _verifyPurchase(purchaseDetails);
if (!isValid) {
// 处理验证失败情况
throw Exception('购买验证失败');
}
// 完成购买
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
}
});
}
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) async {
// 获取购买凭证
String? receipt = purchaseDetails.verificationData.serverVerificationData;
String? localVerificationData =
purchaseDetails.verificationData.localVerificationData;
// TODO: 实现服务器端验证
// 1. 将 receipt 发送到您的服务器
// 2. 服务器与 App Store/Google Play 验证购买
// 3. 返回验证结果
// 示例验证逻辑
return receipt != null && receipt.isNotEmpty;
}
Future<void> clearPendingPurchases() async {
try {
print("clearPendingPurchases");
final transactions = await SKPaymentQueueWrapper().transactions();
print(transactions);
for (final transaction in transactions) {
try {
await SKPaymentQueueWrapper().finishTransaction(transaction);
} catch (e) {
debugPrint("Error clearing pending purchases::in::loop");
debugPrint(e.toString());
rethrow;
}
}
} catch (e) {
debugPrint("Error clearing pending purchases");
debugPrint(e.toString());
rethrow;
}
}
void _onDone() {
_subscription.cancel();
}
void _onError(error) {
// 处理错误
}
void dispose() {
_subscription.cancel();
}
}
请注意,我这里使用的是 throw Exception 抛出对应错误结果,请务必在使用过程中try catch 捕获当前问题
其中包含遇到问题
flutter: PlatformException(storekit_duplicate_product_object, There is a pending transaction for the same product identifier. Please either wait for it to be finished or finish it manually using `completePurchase` to avoid edge cases., {applicationUsername: null, quantity: 1, simulatesAskToBuyInSandbox: false, paymentDiscount: null, productIdentifier: testerweek, requestData: null}, null)
请参阅:
Future<void> clearPendingPurchases() async {
try {
print("clearPendingPurchases");
final transactions = await SKPaymentQueueWrapper().transactions();
print(transactions);
for (final transaction in transactions) {
try {
await SKPaymentQueueWrapper().finishTransaction(transaction);
} catch (e) {
debugPrint("Error clearing pending purchases::in::loop");
debugPrint(e.toString());
rethrow;
}
}
} catch (e) {
debugPrint("Error clearing pending purchases");
debugPrint(e.toString());
rethrow;
}
}
这个是由于,发起购买,然后支付过程中取消支付了。然而,苹果的机制是一直在监听支付的动作,导致如果不流转已取消的状态,那么一直会提示此问题,表示当前 包含等待支付中的任务
调用支付
/// 苹果支付
startApplePay(productId) async {
ToastUtil.showLoading();
try {
final buyEngine = BuyEngine();
await buyEngine.buyNonConsumable('testerweek');
} catch (e) {
print(e);[请输入链接描述][1]
ToastUtil.toast("支付失败,请重试");
} finally {
ToastUtil.dismiss();
}
GlobalUtil.appController.showOrderSuccessModal();
}
testerweek
是我的商品id,你可自由决定
GlobalUtil.appController.showOrderSuccessModal();
是我的支付模态框提示,可忽略
后端验证是否有效
回到之前的代码行,查看 _verifyPurchase 函数
然后调用后端 Api
PHP 后端流程
请先阅读下方参考文章
- https://developer.apple.com/documentation/appstoreservernotifications/app_store_server_notifications_v2
- https://vastzh.com/blog/apple_pay/
- https://developer.apple.com/documentation/appstoreserverapi
https://developer.apple.com/cn/help/app-store-connect/configure-in-app-purchase-settings/generate-a-shared-secret-to-verify-receipts
https://developer.apple.com/cn/help/app-store-connect/configure-in-app-purchase-settings/enter-server-urls-for-app-store-server-notifications
本文由 邓尘锋 创作,采用 知识共享署名4.0 国际许可协议进行许可
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名
最后编辑时间为: Nov 12, 2024 at 09:34 pm