Commit f9c0a659 by ethanlamzs

ios订单订购

1 parent 538db001
...@@ -16,7 +16,10 @@ class SubscriptionService { ...@@ -16,7 +16,10 @@ class SubscriptionService {
static const String monthlySubId_12 = 'cn.banxe.appframe.membership.12m'; static const String monthlySubId_12 = 'cn.banxe.appframe.membership.12m';
// 纯粹的数据容器,不继承任何类 // 纯粹的数据容器,不继承任何类
final Map<String, Map<String, dynamic>> _pendingContexts = {}; // ---------- 业务上下文映射(productID → 自定义数据) ----------
final Map<String, Map<String, dynamic>> _pendingContexts = {};
final Map<String, String> _orderIdMap = {}; // key: productID, value: orderId final Map<String, String> _orderIdMap = {}; // key: productID, value: orderId
final InAppPurchase _iap = InAppPurchase.instance; final InAppPurchase _iap = InAppPurchase.instance;
...@@ -31,6 +34,14 @@ class SubscriptionService { ...@@ -31,6 +34,14 @@ class SubscriptionService {
bool _hasActiveSubscription = false; bool _hasActiveSubscription = false;
bool get hasActiveSubscription => _hasActiveSubscription; bool get hasActiveSubscription => _hasActiveSubscription;
// ---------- 🔁 去重措施:记录已处理过的交易 ID ----------
final Set<String> _processedTransactionIds = {};
// ---------- 回调函数,用于通知 UI 状态变化 ----------
void Function(String? errorMessage)? onError;
void Function(String message)? onSuccess;
void Function()? onStateChanged;
// 初始化 // 初始化
Future<void> initialize() async { Future<void> initialize() async {
// 1. 检查是否可用 // 1. 检查是否可用
...@@ -85,10 +96,17 @@ class SubscriptionService { ...@@ -85,10 +96,17 @@ class SubscriptionService {
productDetails: product, productDetails: product,
); );
// 发起购买(App Store 会弹出系统支付界面) try {
await _iap.buyNonConsumable(purchaseParam: purchaseParam); // 发起购买(App Store 会弹出系统支付界面)
await _iap.buyNonConsumable(purchaseParam: purchaseParam);
return true; // 实际结果在 _onPurchaseUpdate 处理
}catch (e){
_pendingContexts.remove(product.id); // 发起失败,清除上下文
onError?.call('购买请求失败: $e');
return false;
}
// 注意:成功或失败会通过 purchaseStream 回调返回 // 注意:成功或失败会通过 purchaseStream 回调返回
return true; // 实际结果在 _onPurchaseUpdate 处理
} }
// ---------- 交易更新回调 ---------- // ---------- 交易更新回调 ----------
...@@ -102,6 +120,23 @@ class SubscriptionService { ...@@ -102,6 +120,23 @@ class SubscriptionService {
continue; continue;
} }
// 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理
final transactionId = purchaseDetails.verificationData.serverVerificationData.hashCode.toString();
// 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识
final uniqueId = purchaseDetails.purchaseID;
if (_processedTransactionIds.contains(uniqueId)) {
// 已经处理过,直接结束交易并跳过
if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) {
_iap.completePurchase(purchaseDetails);
}
continue;
}
// 标记为已处理
if (uniqueId != null) {
_processedTransactionIds.add(uniqueId);
}
//var orderId = _orderIdMap.remove(purchaseDetails.productID); // 取出并删除 //var orderId = _orderIdMap.remove(purchaseDetails.productID); // 取出并删除
final ctx = _pendingContexts[purchaseDetails.productID] ; final ctx = _pendingContexts[purchaseDetails.productID] ;
print(ctx); print(ctx);
...@@ -123,31 +158,43 @@ class SubscriptionService { ...@@ -123,31 +158,43 @@ class SubscriptionService {
case PurchaseStatus.purchased: case PurchaseStatus.purchased:
// 购买成功或续期成功 // 购买成功或续期成功
// 1. 验证收据(必须!) // 1. 验证收据(必须!)
_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased"); //_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased");
// 2. 通知 App Store 交易完成 _handlePurchaseSuccess(purchaseDetails,orderId??'',userId??'');
_iap.completePurchase(purchaseDetails); break;
case PurchaseStatus.error: case PurchaseStatus.error:
// 处理错误 // 处理错误
_handlePurchaseError(purchaseDetails.error!); _handlePurchaseError(purchaseDetails.error!);
_iap.completePurchase(purchaseDetails); // 同样需要结束交易 break;
case PurchaseStatus.restored: case PurchaseStatus.restored:
// 恢复购买成功 // 恢复购买成功
_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored"); // 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理
_iap.completePurchase(purchaseDetails); // 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored");
_handleRestoreSuccess(purchaseDetails,orderId??'',userId??'');
break;
case PurchaseStatus.canceled: case PurchaseStatus.canceled:
// 用户取消支付 // 用户取消支付
_iap.completePurchase(purchaseDetails); //_iap.completePurchase(purchaseDetails);
break;
default: default:
break;
} }
// ✅ 关键:只要不是 pending,都立刻完成交易,清出队列 // // ✅ 关键:只要不是 pending,都立刻完成交易,清出队列
if (purchaseDetails.status != PurchaseStatus.pending) { // if (purchaseDetails.status != PurchaseStatus.pending) {
_iap.completePurchase(purchaseDetails); // _iap.completePurchase(purchaseDetails);
print("[debug] _iap.completePurchase execute "); // print("[debug] _iap.completePurchase execute ");
// }
// 🔁 去重措施 3:所有恢复交易处理完后,统一刷新一次状态(防多次通知)
final hasRestored = purchaseDetailsList.any((d) => d.status == PurchaseStatus.restored);
if (hasRestored) {
// 统一通知 UI 更新,避免多次刷新
onSuccess?.call('购买已恢复');
onStateChanged?.call();
} }
} }
} }
...@@ -170,34 +217,68 @@ class SubscriptionService { ...@@ -170,34 +217,68 @@ class SubscriptionService {
} }
} }
// 示例:发送收据到自己的服务器进行二次验证 // 示例:发送收据到自己的服务器进行二次验证
Future<bool> _sendReceiptToBackend(String userid,String receipt,String orderid) async { Future<bool> _sendReceiptToBackend(String userid,String receipt,String orderid) async {
var result = _userAuthRepository.orderCheck(userid,receipt,orderid,"ios",1); var result = _userAuthRepository.orderCheck(userid,receipt,orderid,"ios",1);
//var data = result['data'] as Map<String, dynamic>?; //var data = result['data'] as Map<String, dynamic>?;
//debugPrint(result); //debugPrint(result);
debugPrint("[info] _sendReceiptToBackend is done"); debugPrint("[info] _sendReceiptToBackend is done");
return true; return true;
} }
// ---------- 恢复购买 ----------
Future<void> restorePurchases() async {
await _iap.restorePurchases();
// 恢复的订阅信息会通过 purchaseStream 以 restored 状态返回
}
// ---------- 辅助方法 ----------
bool _isSubscription(String productId) {
return productId == monthlySubId_1 || productId == monthlySubId_3|| productId == monthlySubId_6|| productId == monthlySubId_12;
}
void _handlePurchaseError(IAPError error) { // ---------- 辅助方法 ----------
// 根据 error.code 显示不同提示 bool _isSubscription(String productId) {
debugPrint('购买失败: ${error.message}'); return productId == monthlySubId_1 || productId == monthlySubId_3|| productId == monthlySubId_6|| productId == monthlySubId_12;
} }
// 释放资源 void _handlePurchaseError(IAPError error) {
void dispose() { // 根据 error.code 显示不同提示
_purchaseUpdatedSubscription.cancel(); debugPrint('购买失败: ${error.message}');
} }
// ---------- 购买成功处理 ----------
void _handlePurchaseSuccess(PurchaseDetails detail,String orderId,String userId) {
// 发送收据到后端验证,携带 userId
_verifyReceipt(detail,orderId??'',userId??'',"purchased");
// 完成交易
_iap.completePurchase(detail);
// 乐观更新本地状态(等后端最终确认再真正解锁功能)
_hasActiveSubscription = true;
onSuccess?.call('订阅成功');
onStateChanged?.call();
}
// ---------- 恢复成功处理 ----------
void _handleRestoreSuccess(PurchaseDetails detail,String orderId,String userId) {
// 注意:这里只是处理单个 restored,但最终 UI 刷新在 _onPurchaseUpdate 统一完成
_verifyReceipt(detail,orderId??'',userId??'',"purchased");
_iap.completePurchase(detail);
}
// ---------- 错误处理 ----------
void _handleError(PurchaseDetails detail) {
_pendingContexts.remove(detail.productID);
_iap.completePurchase(detail);
onError?.call('支付失败: ${detail.error?.message}');
}
// ---------- 取消处理 ----------
void _handleCancel(PurchaseDetails detail) {
_pendingContexts.remove(detail.productID);
_iap.completePurchase(detail);
onError?.call('支付已取消');
}
// 释放资源
void dispose() {
_purchaseUpdatedSubscription.cancel();
}
} }
\ No newline at end of file \ No newline at end of file
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!