Commit 1d8c2b03 by ethanlamzs

test

1 parent cf893e75
...@@ -77,7 +77,7 @@ class SubscriptionService { ...@@ -77,7 +77,7 @@ class SubscriptionService {
// 有产品未在 App Store Connect 配置或配置错误 // 有产品未在 App Store Connect 配置或配置错误
debugPrint('未找到产品: ${response.notFoundIDs}'); debugPrint('未找到产品: ${response.notFoundIDs}');
} }
_products = response.productDetails; _products = response.productDetails;
debugPrint('获取到 ${_products.length} 个产品'); debugPrint('获取到 ${_products.length} 个产品');
for (var p in _products) { for (var p in _products) {
...@@ -86,8 +86,8 @@ class SubscriptionService { ...@@ -86,8 +86,8 @@ class SubscriptionService {
} }
// ---------- 购买订阅 ---------- // ---------- 购买订阅 ----------
Future<bool> buySubscription(ProductDetails product, String userId , String tbxStuId, String yWOrderId) async { Future<bool> buySubscription(ProductDetails product, String userId , String tbxStuId, String yWOrderId) async {
_pendingContexts[product.id] = {'userId': userId,'tbxStuId': tbxStuId,"orderId":yWOrderId}; _pendingContexts[product.id] = {'userId': userId,'tbxStuId': tbxStuId,"orderId":yWOrderId};
_orderIdMap[product.id] = yWOrderId; // 按产品 ID 存储 _orderIdMap[product.id] = yWOrderId; // 按产品 ID 存储
...@@ -110,126 +110,124 @@ class SubscriptionService { ...@@ -110,126 +110,124 @@ class SubscriptionService {
} }
// ---------- 交易更新回调 ---------- // ---------- 交易更新回调 ----------
void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) { void _onPurchaseUpdate(List<PurchaseDetails> purchaseDetailsList) {
for (final purchaseDetails in purchaseDetailsList) { for (final purchaseDetails in purchaseDetailsList) {
print('Purchase update: ${purchaseDetails.productID} - ${purchaseDetails.status}'); print('Purchase update: ${purchaseDetails.productID} - ${purchaseDetails.status}');
// 只处理订阅类型产品 // 只处理订阅类型产品
if (!_isSubscription(purchaseDetails.productID)){ if (!_isSubscription(purchaseDetails.productID)){
print("Purchase update !_isSubscription "); print("Purchase update !_isSubscription ");
continue; continue;
} }
// 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理 // 🔁 去重措施 1:根据原始交易 ID 去重,防止同一笔交易被重复处理
final transactionId = purchaseDetails.verificationData.serverVerificationData.hashCode.toString(); final transactionId = purchaseDetails.verificationData.serverVerificationData.hashCode.toString();
// 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识 // 如果能获取到 purchaseID 更可靠,这里用 purchaseID 作为唯一标识
final uniqueId = purchaseDetails.purchaseID; final uniqueId = purchaseDetails.purchaseID;
if (_processedTransactionIds.contains(uniqueId)) { if (_processedTransactionIds.contains(uniqueId)) {
// 已经处理过,直接结束交易并跳过 // 已经处理过,直接结束交易并跳过
if (purchaseDetails.status == PurchaseStatus.purchased || if (purchaseDetails.status == PurchaseStatus.purchased ||
purchaseDetails.status == PurchaseStatus.restored) { purchaseDetails.status == PurchaseStatus.restored) {
_iap.completePurchase(purchaseDetails); _iap.completePurchase(purchaseDetails);
}
continue;
}
// 标记为已处理
if (uniqueId != null) {
_processedTransactionIds.add(uniqueId);
} }
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);
if (ctx == null) { if (ctx == null) {
// 打印错误日志,防止崩溃 // 打印错误日志,防止崩溃
print('错误:未找到 productID 对应的上下文缓存'); print('错误:未找到 productID 对应的上下文缓存');
return; return;
} }
final orderId = ctx['orderId'] as String?; final orderId = ctx['orderId'] as String?;
final userId = ctx['userId'] as String?; final userId = ctx['userId'] as String?;
final tbxStuId = ctx['tbxStuId'] as String?; final tbxStuId = ctx['tbxStuId'] as String?;
print("orderId ==> "+orderId.toString()+" "+ userId.toString() +". "+ tbxStuId.toString()); print("orderId ==> "+orderId.toString()+" "+ userId.toString() +". "+ tbxStuId.toString());
// 根据交易状态处理 // 根据交易状态处理
switch (purchaseDetails.status) { switch (purchaseDetails.status) {
case PurchaseStatus.purchased: case PurchaseStatus.purchased:
// 购买成功或续期成功 // 购买成功或续期成功
// 1. 验证收据(必须!) // 1. 验证收据(必须!)
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased"); //_verifyReceipt(purchaseDetails,orderId??'',userId??'',"purchased");
_handlePurchaseSuccess(purchaseDetails,orderId??'',userId??''); _handlePurchaseSuccess(purchaseDetails,orderId??'',userId??'');
break; break;
case PurchaseStatus.error: case PurchaseStatus.error:
// 处理错误 // 处理错误
_handlePurchaseError(purchaseDetails.error!); _handlePurchaseError(purchaseDetails.error!);
break; break;
case PurchaseStatus.restored: case PurchaseStatus.restored:
// 恢复购买成功 // 恢复购买成功
// 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理 // 🔁 去重措施 2:恢复事件只处理最后一个有效的,或者收集后统一处理
// 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重 // 这里演示:仍然逐个验证,但后端会基于 original_transaction_id 去重
//_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored"); //_verifyReceipt(purchaseDetails,orderId??'',userId??'',"restored");
_handleRestoreSuccess(purchaseDetails,orderId??'',userId??''); _handleRestoreSuccess(purchaseDetails,orderId??'',userId??'');
break; break;
case PurchaseStatus.canceled: case PurchaseStatus.canceled:
// 用户取消支付 // 用户取消支付
//_iap.completePurchase(purchaseDetails); //_iap.completePurchase(purchaseDetails);
break; break;
default: default:
break; 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:所有恢复交易处理完后,统一刷新一次状态(防多次通知) // 🔁 去重措施 3:所有恢复交易处理完后,统一刷新一次状态(防多次通知)
final hasRestored = purchaseDetailsList.any((d) => d.status == PurchaseStatus.restored); final hasRestored = purchaseDetailsList.any((d) => d.status == PurchaseStatus.restored);
if (hasRestored) { if (hasRestored) {
// 统一通知 UI 更新,避免多次刷新 // 统一通知 UI 更新,避免多次刷新
onSuccess?.call('购买已恢复'); onSuccess?.call('购买已恢复');
onStateChanged?.call(); onStateChanged?.call();
}
} }
} }
}
// ---------- 服务端验证收据 ---------- // ---------- 服务端验证收据 ----------
Future<void> _verifyReceipt(PurchaseDetails purchaseDetails,String orderid,String userid,String scence) async { Future<bool> _verifyReceipt(PurchaseDetails purchaseDetails,String orderid,String userid,String scence) async {
debugPrint("[info] _verifyReceipt is checking 3 ["+scence+"] "+purchaseDetails.productID+" orderid:"+orderid+" userid:"+userid); debugPrint("[info] _verifyReceipt is checking 3 ["+scence+"] "+purchaseDetails.productID+" orderid:"+orderid+" userid:"+userid);
if (orderid != null && orderid.length > 6) { if (orderid != null && orderid.length > 6) {
debugPrint("[info] _verifyReceipt is start scence ["+scence+"] "+purchaseDetails.productID+" orderid:"+orderid); debugPrint("[info] _verifyReceipt is start scence ["+scence+"] "+purchaseDetails.productID+" orderid:"+orderid);
// 将收据发送到你的后端进行验证 // 将收据发送到你的后端进行验证
final String receipt = purchaseDetails.verificationData.serverVerificationData; final String receipt = purchaseDetails.verificationData.serverVerificationData;
// 把 base64Receipt 作为 receipt-data 发给你的服务端验证 // 把 base64Receipt 作为 receipt-data 发给你的服务端验证
final bool isValid = await _sendReceiptToBackend(userid,receipt,orderid); final bool isValid = await _sendReceiptToBackend(userid,receipt,orderid);
if (isValid) { if (isValid) {
// 更新本地订阅状态 // 更新本地订阅状态
_hasActiveSubscription = true; _hasActiveSubscription = true;
// 保存到期时间等信息 // 保存到期时间等信息
} }
return isValid;
}
return false;
} }
}
// 示例:发送收据到自己的服务器进行二次验证 // 示例:发送收据到自己的服务器进行二次验证
Future<bool> _sendReceiptToBackend(String userid,String receipt,String orderid) async { Future<bool> _sendReceiptToBackend(String userid,String receipt,String orderid) async {
var result = await _userAuthRepository.orderCheck(userid,receipt,orderid,"ios",1);
var result = _userAuthRepository.orderCheck(userid,receipt,orderid,"ios",1);
//var data = result['data'] as Map<String, dynamic>?;
//debugPrint(result);
debugPrint("[info] _sendReceiptToBackend is done"); debugPrint("[info] _sendReceiptToBackend is done");
return true; return result!=null;
} }
// ---------- 辅助方法 ---------- // ---------- 辅助方法 ----------
bool _isSubscription(String productId) { bool _isSubscription(String productId) {
...@@ -242,18 +240,19 @@ class SubscriptionService { ...@@ -242,18 +240,19 @@ class SubscriptionService {
} }
// ---------- 购买成功处理 ---------- // ---------- 购买成功处理 ----------
void _handlePurchaseSuccess(PurchaseDetails detail,String orderId,String userId) { Future<void> _handlePurchaseSuccess(PurchaseDetails detail,String orderId,String userId) async {
// 发送收据到后端验证,携带 userId
// 发送收据到后端验证,携带 userId final ok = await _verifyReceipt(detail,orderId??'',userId??'',"purchased");
_verifyReceipt(detail,orderId??'',userId??'',"purchased"); if(ok){
_hasActiveSubscription = true;
// 完成交易 if(detail.pendingCompletePurchase){
_iap.completePurchase(detail); await _iap.completePurchase(detail);
}
// 乐观更新本地状态(等后端最终确认再真正解锁功能) }else{
_hasActiveSubscription = true;
onSuccess?.call('订阅成功'); // 不complete
onStateChanged?.call(); // 等待下次重新验证
}
} }
// ---------- 恢复成功处理 ---------- // ---------- 恢复成功处理 ----------
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!