diff --git a/lib/features/recharge/google_play_purchase_service.dart b/lib/features/recharge/google_play_purchase_service.dart index f5bf61a..e320a75 100644 --- a/lib/features/recharge/google_play_purchase_service.dart +++ b/lib/features/recharge/google_play_purchase_service.dart @@ -3,22 +3,106 @@ import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/in_app_purchase_android.dart'; import 'package:pets_hero_ai/core/api/api.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import '../../core/api/services/payment_api.dart'; import '../../core/log/app_logger.dart'; +import '../../core/user/account_refresh.dart'; +import '../../core/user/user_state.dart'; +import 'models/google_pay_purchase_result.dart'; import 'models/google_pay_verification_payload.dart'; import 'models/unacknowledged_google_pay_purchase.dart'; +const String _kFederationMapKey = 'google_pay_google_order_to_federation'; + /// 调起 Google Play 内购,所有内购均通过本方法发起并返回凭据用于服务端回调。 abstract final class GooglePlayPurchaseService { static final _log = AppLogger('GooglePlayPurchase'); - /// 获取当前未核销(未确认)的谷歌支付订单。 - /// 使用 Android [InAppPurchaseAndroidPlatformAddition.queryPastPurchases] 查询本地/缓存中的购买, - /// 筛选 [isAcknowledged == false] 的项。仅 Android 有效,非 Android 返回空列表。 - /// 可用于应用启动时补发回调或展示待处理订单。 - static Future> getUnacknowledgedPurchases() async { + /// 未确认的购买在 Android 上可能不会出现在 queryPastPurchases 中,但会在应用启动时通过 + /// purchaseStream 重新下发。此处缓存来自 stream 的 pending 购买,供补单合并使用。 + static final Map _pendingFromStream = {}; + static StreamSubscription>? _pendingStreamSub; + + /// 在应用启动时调用(仅 Android),订阅 purchaseStream 以接收「未 complete 的购买」的重新下发。 + /// 否则 queryPastPurchases 可能查不到未确认订单,导致补单为空。 + static void startPendingPurchaseListener() { + if (defaultTargetPlatform != TargetPlatform.android) return; + if (_pendingStreamSub != null) return; + final iap = InAppPurchase.instance; + _pendingStreamSub = + iap.purchaseStream.listen((List purchases) { + for (final p in purchases) { + if (p is! GooglePlayPurchaseDetails) continue; + if (!p.pendingCompletePurchase) continue; + final orderId = p.billingClientPurchase.orderId; + if (orderId.isEmpty) continue; + _pendingFromStream[orderId] = p; + _log.d('purchaseStream 收到待处理订单 orderId=$orderId,已加入补单候选'); + } + }, onError: (e) { + _log.w('purchaseStream 错误: $e'); + }); + _log.d('已订阅 purchaseStream,用于补单时获取未确认订单'); + } + + /// 保存「Google orderId → 创建订单时的 federation」,供补单时使用。 + static Future saveFederationForGoogleOrderId( + String googleOrderId, String federation) async { + try { + final prefs = await SharedPreferences.getInstance(); + final json = prefs.getString(_kFederationMapKey); + final map = json != null + ? Map.from((jsonDecode(json) as Map) + .map((k, v) => MapEntry(k.toString(), v.toString()))) + : {}; + map[googleOrderId] = federation; + await prefs.setString(_kFederationMapKey, jsonEncode(map)); + } catch (e) { + _log.w('保存 federation 映射失败: $e'); + } + } + + /// 补单时根据 Google orderId 取回创建订单时的 federation,无则返回 null。 + static Future getFederationForGoogleOrderId( + String googleOrderId) async { + try { + final prefs = await SharedPreferences.getInstance(); + final json = prefs.getString(_kFederationMapKey); + if (json == null) return null; + final map = (jsonDecode(json) as Map) + .map((k, v) => MapEntry(k.toString(), v.toString())); + final v = map[googleOrderId]?.toString(); + return (v != null && v.isNotEmpty) ? v : null; + } catch (e) { + _log.w('读取 federation 映射失败: $e'); + return null; + } + } + + /// 回调成功或补单成功后移除映射。 + static Future removeFederationForGoogleOrderId( + String googleOrderId) async { + try { + final prefs = await SharedPreferences.getInstance(); + final json = prefs.getString(_kFederationMapKey); + if (json == null) return; + final map = Map.from((jsonDecode(json) as Map) + .map((k, v) => MapEntry(k.toString(), v.toString()))); + map.remove(googleOrderId); + await prefs.setString(_kFederationMapKey, jsonEncode(map)); + } catch (e) { + _log.w('移除 federation 映射失败: $e'); + } + } + + /// 获取当前未消耗的谷歌支付订单(含已确认未 consume 的,用于解除「已拥有此内容」)。 + /// 使用 [queryPastPurchases] 与 purchaseStream 待处理合并,不区分 isAcknowledged。仅 Android 有效。 + static Future> + getUnacknowledgedPurchases() async { if (defaultTargetPlatform != TargetPlatform.android) { _log.d('非 Android,无未核销订单'); return []; @@ -29,16 +113,74 @@ abstract final class GooglePlayPurchaseService { return []; } try { - final androidAddition = iap.getPlatformAddition(); + // 先订阅 stream,这样在 queryPastPurchases 触发 Billing 连接后,未确认订单若通过 stream 下发能被收集 + startPendingPurchaseListener(); + final androidAddition = + iap.getPlatformAddition(); final response = await androidAddition.queryPastPurchases(); if (response.error != null) { _log.w('queryPastPurchases 错误: ${response.error!.message}'); return []; } + // response 没有实现 toString,这里用手动遍历的方式打印所有内容 + _log.d( + 'queryPastPurchases response contains ${response.pastPurchases.length} pastPurchases.'); + for (var i = 0; i < response.pastPurchases.length; i++) { + final purchase = response.pastPurchases[i]; + final b = purchase.billingClientPurchase; + _log.d('pastPurchase[$i]:'); + _log.d(' productID: ${purchase.productID}'); + _log.d(' purchaseID: ${purchase.purchaseID}'); + _log.d(' transactionDate: ${purchase.transactionDate}'); + _log.d(' status: ${purchase.status}'); + _log.d(' error: ${purchase.error}'); + _log.d( + ' pendingCompletePurchase: ${purchase.pendingCompletePurchase}'); + _log.d(' billingClientPurchase:'); + _log.d(' orderId: ${b.orderId}'); + _log.d(' purchaseToken: ${b.purchaseToken}'); + _log.d(' packageName: ${b.packageName}'); + _log.d(' productID: ${purchase.productID}'); + _log.d(' purchaseState: ${b.purchaseState}'); + _log.d(' isAcknowledged: ${b.isAcknowledged}'); + _log.d(' isAutoRenewing: ${b.isAutoRenewing}'); + _log.d(' originalJson (length): ${b.originalJson.length}'); + _log.d(' signature (length): ${b.signature.length}'); + } + for (final purchase in response.pastPurchases) { + final b = purchase.billingClientPurchase; + final transactionDate = purchase.transactionDate; + String? formattedDate; + if (transactionDate != null && transactionDate.isNotEmpty) { + try { + // transactionDate is milliseconds since epoch (string) + final millis = int.tryParse(transactionDate); + if (millis != null) { + final dt = DateTime.fromMillisecondsSinceEpoch(millis, + isUtc: false); // Android is local? + // Format as yyyy-MM-dd HH:mm:ss + formattedDate = + "${dt.year.toString().padLeft(4, '0')}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} " + "${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}:${dt.second.toString().padLeft(2, '0')}"; + _log.d('订单日期: $formattedDate'); + } else { + _log.d('订单日期解析失败: $transactionDate'); + } + } catch (e) { + _log.d('订单日期格式化异常: $e, 原数据: $transactionDate'); + } + } else { + _log.d('订单日期为空'); + } + _log.d( + '订单: orderId=${b.orderId}, productId=${purchase.productID}, isAcknowledged=${b.isAcknowledged}, purchaseDataLength=${b.originalJson.length}, signatureLength=${b.signature.length}'); + } + // 包含所有未消耗的购买(不区分 isAcknowledged),避免「已拥有此内容」因已确认未 consume 被漏掉 final list = []; + final orderIdsFromQuery = {}; for (final p in response.pastPurchases) { final b = p.billingClientPurchase; - if (b.isAcknowledged) continue; + orderIdsFromQuery.add(b.orderId); list.add(UnacknowledgedGooglePayPurchase( orderId: b.orderId, productId: p.productID, @@ -46,9 +188,41 @@ abstract final class GooglePlayPurchaseService { purchaseData: b.originalJson, signature: b.signature, ), + purchaseDetails: p, )); } - _log.d('未核销订单数: ${list.length}'); + // 未确认的购买在 Android 上常不会出现在 queryPastPurchases 中,需合并来自 purchaseStream 的待处理订单。 + // 等片刻让 stream 在 Billing 连接后有机会收到重新下发的待处理订单。 + await Future.delayed(const Duration(milliseconds: 1500)); + for (final entry in _pendingFromStream.entries) { + if (orderIdsFromQuery.contains(entry.key)) continue; + final p = entry.value; + if (p is! GooglePlayPurchaseDetails) continue; + final b = p.billingClientPurchase; + list.add(UnacknowledgedGooglePayPurchase( + orderId: b.orderId, + productId: p.productID, + payload: GooglePayVerificationPayload( + purchaseData: b.originalJson, + signature: b.signature, + ), + purchaseDetails: p, + )); + _log.d('未核销订单(来自 stream) orderId=${b.orderId} 已合并'); + } + _log.d( + '未核销订单数: ${list.length} (query: ${orderIdsFromQuery.length}, stream: ${_pendingFromStream.length})'); + for (var i = 0; i < list.length; i++) { + final u = list[i]; + _log.d('未核销[$i] orderId=${u.orderId} productId=${u.productId} ' + 'purchaseDataLength=${u.payload.purchaseData.length}'); + logWithEmbeddedJson(jsonEncode({ + 'orderId': u.orderId, + 'productId': u.productId, + 'purchaseData': u.payload.purchaseData, + 'signatureLength': u.payload.signature.length, + })); + } return list; } catch (e, st) { _log.w('获取未核销订单失败: $e\n$st'); @@ -56,9 +230,99 @@ abstract final class GooglePlayPurchaseService { } } - /// 发起购买并返回服务端回调所需凭据(purchaseData=originalJson, signature)。 - /// 成功返回 [GooglePayVerificationPayload],取消/失败返回 null。调用方填入 id(federation)、userId 后组 merchant 上报。 - static Future launchPurchaseAndReturnData( + /// 对单笔购买执行 completePurchase,并在 Android 上显式调用 consumePurchase(autoConsume: false 时必须,否则无法再次购买)。 + /// 正常流程回调成功后请调用此方法,传入 [GooglePayPurchaseResult.purchaseDetails]。 + static Future completeAndConsumePurchase(PurchaseDetails purchaseDetails) async { + final iap = InAppPurchase.instance; + try { + iap.completePurchase(purchaseDetails); + _log.d('completePurchase 已执行'); + if (defaultTargetPlatform == TargetPlatform.android) { + final androidAddition = iap.getPlatformAddition(); + final result = await androidAddition.consumePurchase(purchaseDetails); + final ok = result.responseCode == BillingResponse.ok; + if (ok) { + _log.d('consumePurchase 已执行,可再次购买'); + } else { + _log.w('consumePurchase 未成功 responseCode=${result.responseCode}'); + } + return ok; + } + return true; + } catch (e, st) { + _log.w('completePurchase/consumePurchase 异常: $e\n$st'); + return false; + } + } + + /// 执行 completePurchase + consumePurchase(补单内部用)。 + static Future _consumePurchase(InAppPurchase iap, UnacknowledgedGooglePayPurchase p) async { + return completeAndConsumePurchase(p.purchaseDetails); + } + + /// 补单流程:拉取未消耗订单 → 有 federation 则回调后 completePurchase,无 federation 也执行 completePurchase 以解除「已拥有此内容」。 + /// 仅 Android 且已登录时执行。 + static Future runOrderRecovery() async { + if (defaultTargetPlatform != TargetPlatform.android) return; + final userId = UserState.userId.value; + if (userId == null || userId.isEmpty) { + _log.d('补单跳过:未登录'); + return; + } + final pending = await getUnacknowledgedPurchases(); + if (pending.isEmpty) return; + _log.d('补单开始,待处理 ${pending.length} 笔'); + final iap = InAppPurchase.instance; + bool needRefresh = false; + for (final p in pending) { + try { + final federation = await getFederationForGoogleOrderId(p.orderId); + if (federation != null && federation.isNotEmpty) { + final res = await PaymentApi.googlepay( + sample: p.payload.signature, + merchant: p.payload.purchaseData, + federation: federation, + asset: userId, + ); + if (!res.isSuccess) { + _log.w('补单失败 orderId=${p.orderId}: ${res.msg}'); + continue; + } + final data = res.data is Map + ? res.data as Map + : null; + final line = (data?['line']?.toString() ?? '').toUpperCase(); + final status = (data?['status']?.toString() ?? '').toUpperCase(); + final isSuccess = line == 'SUCCESS' || status == 'SUCCESS'; + _log.d('补单响应 orderId=${p.orderId} data=$data line=$line status=$status isSuccess=$isSuccess'); + if (isSuccess) { + if (await _consumePurchase(iap, p)) { + _pendingFromStream.remove(p.orderId); + await removeFederationForGoogleOrderId(p.orderId); + needRefresh = true; + _log.d('补单成功 orderId=${p.orderId} federation=$federation'); + } + } else { + _log.w('补单服务端未成功 orderId=${p.orderId} line=$line status=$status'); + } + } else { + // 无 federation(如网络失败后重进):仍执行 completePurchase + consumePurchase 以解除「已拥有此内容」 + _log.d('补单无 federation,仅执行 consume 以解除「已拥有此内容」orderId=${p.orderId}'); + if (await _consumePurchase(iap, p)) { + _pendingFromStream.remove(p.orderId); + needRefresh = true; + } + } + } catch (e, st) { + _log.w('补单异常 orderId=${p.orderId}: $e\n$st'); + } + } + if (needRefresh) await refreshAccount(); + } + + /// 发起购买并返回服务端回调所需凭据与 [PurchaseDetails]。 + /// 成功返回 [GooglePayPurchaseResult];调用方应在回调接口返回成功后再对 result.purchaseDetails 执行 [InAppPurchase.instance.completePurchase]。 + static Future launchPurchaseAndReturnData( String productId) async { _log.d('谷歌支付请求商品 ID(helm): "$productId"'); if (defaultTargetPlatform != TargetPlatform.android) { @@ -77,7 +341,7 @@ abstract final class GooglePlayPurchaseService { return null; } final product = response.productDetails.first; - final completer = Completer(); + final completer = Completer(); StreamSubscription>? sub; sub = iap.purchaseStream.listen( (purchases) { @@ -124,17 +388,17 @@ abstract final class GooglePlayPurchaseService { if (p.status == PurchaseStatus.purchased || p.status == PurchaseStatus.restored) { _log.d('购买成功: ${p.toString()}'); - if (!completer.isCompleted) { - GooglePayVerificationPayload? payload; - if (p is GooglePlayPurchaseDetails) { - final b = p.billingClientPurchase; - payload = GooglePayVerificationPayload( + if (!completer.isCompleted && p is GooglePlayPurchaseDetails) { + final b = p.billingClientPurchase; + _pendingFromStream[b.orderId] = p; + completer.complete(GooglePayPurchaseResult( + orderId: b.orderId, + payload: GooglePayVerificationPayload( purchaseData: b.originalJson, signature: b.signature, - ); - } - iap.completePurchase(p); - completer.complete(payload); + ), + purchaseDetails: p, + )); } sub?.cancel(); return; @@ -154,6 +418,7 @@ abstract final class GooglePlayPurchaseService { ); final success = await iap.buyConsumable( purchaseParam: PurchaseParam(productDetails: product), + autoConsume: false, ); if (!success) { sub.cancel(); diff --git a/lib/features/recharge/models/google_pay_purchase_result.dart b/lib/features/recharge/models/google_pay_purchase_result.dart new file mode 100644 index 0000000..9128b3c --- /dev/null +++ b/lib/features/recharge/models/google_pay_purchase_result.dart @@ -0,0 +1,23 @@ +import 'package:in_app_purchase/in_app_purchase.dart'; + +import 'google_pay_verification_payload.dart'; + +/// 谷歌支付发起成功后的结果,含凭据与原始购买详情。 +/// 调用方应在服务端回调成功(如 line == 'SUCCESS')后再对 [purchaseDetails] 执行 +/// [InAppPurchase.instance.completePurchase]。 +class GooglePayPurchaseResult { + const GooglePayPurchaseResult({ + required this.orderId, + required this.payload, + required this.purchaseDetails, + }); + + /// Google Play 订单号,用于 googlepay 回调的 federation(直接内购等无 createPayment 时) + final String orderId; + + /// 用于 POST /v1/payment/googlepay 的 sample/merchant + final GooglePayVerificationPayload payload; + + /// 回调成功后再 [InAppPurchase.instance.completePurchase] + final PurchaseDetails purchaseDetails; +} diff --git a/lib/features/recharge/models/unacknowledged_google_pay_purchase.dart b/lib/features/recharge/models/unacknowledged_google_pay_purchase.dart index cb9caa3..ed02e79 100644 --- a/lib/features/recharge/models/unacknowledged_google_pay_purchase.dart +++ b/lib/features/recharge/models/unacknowledged_google_pay_purchase.dart @@ -1,13 +1,17 @@ +import 'package:in_app_purchase/in_app_purchase.dart'; + import 'google_pay_verification_payload.dart'; /// 未核销(未确认)的谷歌支付订单。 /// 通过 [GooglePlayPurchaseService.getUnacknowledgedPurchases] 查询得到, /// 可用于补发回调或本地展示。上报服务端时 federation 可用 [orderId](若服务端支持)或先 createPayment 再回调。 +/// 补单成功后需对 [purchaseDetails] 调用 [InAppPurchase.instance.completePurchase] 完成核销。 class UnacknowledgedGooglePayPurchase { const UnacknowledgedGooglePayPurchase({ required this.orderId, required this.productId, required this.payload, + required this.purchaseDetails, }); /// Google Play 订单号(purchase 内 orderId) @@ -18,4 +22,7 @@ class UnacknowledgedGooglePayPurchase { /// 凭据,用于 POST /v1/payment/googlepay 的 sample/merchant;federation/asset 由调用方填入。 final GooglePayVerificationPayload payload; + + /// 原始购买详情,补单成功后用于 [InAppPurchase.instance.completePurchase]。 + final PurchaseDetails purchaseDetails; } diff --git a/lib/features/recharge/recharge_screen.dart b/lib/features/recharge/recharge_screen.dart index 691b4e0..7f37f79 100644 --- a/lib/features/recharge/recharge_screen.dart +++ b/lib/features/recharge/recharge_screen.dart @@ -44,6 +44,8 @@ class _RechargeScreenState extends State WidgetsBinding.instance.addObserver(this); refreshAccount(); _fetchActivities(); + // 进入充值页时执行补单,处理未核销订单 + GooglePlayPurchaseService.runOrderRecovery(); } @override @@ -206,8 +208,7 @@ class _RechargeScreenState extends State await _createOrderAndOpenUrl( userId: userId, - activityId: activityId, - productId: item.code, + item: item, paymentMethod: selected.paymentMethod, subPaymentMethod: selected.subPaymentMethod?.isEmpty == true ? null @@ -225,17 +226,17 @@ class _RechargeScreenState extends State } } - /// 创建订单;若为 Google Pay 则调起内购并上报凭据,否则打开支付链接 + /// 创建订单;若为 Google Pay 则走统一入口 [_launchGooglePlayPurchase],否则打开支付链接 Future _createOrderAndOpenUrl({ required String userId, - required String activityId, - required String productId, + required ActivityItem item, required String paymentMethod, String? subPaymentMethod, }) async { if (!mounted) return; try { + final activityId = item.activityId ?? ''; final createRes = await PaymentApi.createPayment( sentinel: ApiConfig.appId, asset: userId, @@ -260,54 +261,8 @@ class _RechargeScreenState extends State final orderId = data?['federation']?.toString(); if (_isGooglePay(paymentMethod, subPaymentMethod)) { - if (defaultTargetPlatform != TargetPlatform.android) { - _showSnackBar(context, 'Google Pay is only available on Android.', - isError: true); - AdjustEvents.trackPaymentFailed(); - return; - } - if (mounted) setState(() => _loadingProductId = null); - final payload = - await GooglePlayPurchaseService.launchPurchaseAndReturnData( - productId); - if (!mounted) return; - if (payload != null && - orderId != null && - orderId.isNotEmpty) { - RechargeScreen._log.d('googlepay 入参: federation=$orderId'); - final googlepayRes = await PaymentApi.googlepay( - sample: payload.signature, - merchant: payload.purchaseData, - federation: orderId, - asset: userId, - ); - if (!mounted) return; - if (googlepayRes.isSuccess) { - final resData = googlepayRes.data is Map - ? googlepayRes.data as Map - : null; - final line = (resData?['line']?.toString() ?? '').toUpperCase(); - if (line == 'SUCCESS') { - await refreshAccount(); - } - if (mounted) { - _showSnackBar(context, 'Purchase completed.'); - } - AdjustEvents.trackPurchaseSuccess(); - } else { - _showSnackBar( - context, - googlepayRes.msg.isNotEmpty - ? googlepayRes.msg - : 'Payment verification failed.', - isError: true); - AdjustEvents.trackPaymentFailed(); - } - } else { - _showSnackBar(context, 'Purchase was cancelled or failed.', - isError: true); - AdjustEvents.trackPaymentFailed(); - } + await _launchGooglePlayPurchase(item, + serverOrderId: orderId, userId: userId); return; } @@ -351,7 +306,7 @@ class _RechargeScreenState extends State return r == 'googlepay' || c == 'googlepay'; } - /// 三方为 false 时走谷歌应用内支付 + /// 三方为 false 时走谷歌应用内支付:先创建订单再调起内购(与第三方选 Google Pay 一致) Future _runGooglePay(ActivityItem item) async { if (defaultTargetPlatform != TargetPlatform.android) { _showSnackBar(context, 'Google Pay is only available on Android.', @@ -359,27 +314,118 @@ class _RechargeScreenState extends State AdjustEvents.trackPaymentFailed(); return; } - await _launchGooglePlayPurchase(item); + final userId = UserState.userId.value; + if (userId == null || userId.isEmpty) { + _showSnackBar(context, 'Please sign in to continue.', isError: true); + AdjustEvents.trackPaymentFailed(); + return; + } + final activityId = item.activityId; + if (activityId == null || activityId.isEmpty) { + if (mounted) _showSnackBar(context, 'Invalid product', isError: true); + AdjustEvents.trackPaymentFailed(); + return; + } + try { + final createRes = await PaymentApi.createPayment( + sentinel: ApiConfig.appId, + asset: userId, + warrior: activityId, + resource: 'GooglePay', + ceremony: 'GooglePay', + ); + if (!mounted) return; + if (!createRes.isSuccess) { + _showSnackBar( + context, + createRes.msg.isNotEmpty ? createRes.msg : 'Failed to create order', + isError: true, + ); + AdjustEvents.trackPaymentFailed(); + return; + } + final data = createRes.data is Map + ? createRes.data as Map + : null; + final orderId = data?['federation']?.toString(); + await _launchGooglePlayPurchase(item, + serverOrderId: orderId, userId: userId); + } catch (e) { + if (mounted) { + _showSnackBar(context, 'Payment error: ${e.toString()}', isError: true); + } + AdjustEvents.trackPaymentFailed(); + } finally { + if (mounted) setState(() => _loadingProductId = null); + } } - /// 调起 Google Play 内购(商品 ID = item.code / helm),凭据用于服务端回调 - Future _launchGooglePlayPurchase(ActivityItem item) async { + /// 谷歌内购统一入口:调起内购 → 回调 googlepay → 成功后再核销并刷新账户。 + /// [serverOrderId] 有值时(来自 createPayment)用作 federation,否则用 Google orderId。 + Future _launchGooglePlayPurchase(ActivityItem item, + {String? serverOrderId, String? userId}) async { if (!mounted) return; try { if (mounted) setState(() => _loadingProductId = null); - final payload = + final result = await GooglePlayPurchaseService.launchPurchaseAndReturnData( item.code); if (!mounted) return; - if (payload != null) { - _showSnackBar(context, 'Purchase completed.'); - AdjustEvents.trackPurchaseSuccess(); - // 直接谷歌支付路径:如需回调服务端,需先 createPayment 取得 federation,再上报 googlepay - } else { + if (result == null) { _showSnackBar(context, 'Purchase was cancelled or failed.', isError: true); AdjustEvents.trackPaymentFailed(); + return; + } + final uid = userId ?? UserState.userId.value; + if (uid == null || uid.isEmpty) { + _showSnackBar(context, 'Please sign in to confirm your purchase.', + isError: true); + AdjustEvents.trackPaymentFailed(); + return; + } + final federation = (serverOrderId != null && serverOrderId.isNotEmpty) + ? serverOrderId + : result.orderId; + if (serverOrderId != null && serverOrderId.isNotEmpty) { + await GooglePlayPurchaseService.saveFederationForGoogleOrderId( + result.orderId, serverOrderId); + } + RechargeScreen._log.d('googlepay 入参: federation=$federation'); + final googlepayRes = await PaymentApi.googlepay( + sample: result.payload.signature, + merchant: result.payload.purchaseData, + federation: federation, + asset: uid, + ); + if (!mounted) return; + if (googlepayRes.isSuccess) { + final resData = googlepayRes.data is Map + ? googlepayRes.data as Map + : null; + final line = (resData?['line']?.toString() ?? '').toUpperCase(); + if (line == 'SUCCESS') { + await GooglePlayPurchaseService.completeAndConsumePurchase( + result.purchaseDetails); + if (serverOrderId != null && serverOrderId.isNotEmpty) { + await GooglePlayPurchaseService.removeFederationForGoogleOrderId( + result.orderId); + } + await refreshAccount(); + } + if (mounted) { + _showSnackBar(context, 'Purchase completed.'); + } + AdjustEvents.trackPurchaseSuccess(); + } else { + _showSnackBar( + context, + googlepayRes.msg.isNotEmpty + ? googlepayRes.msg + : 'Payment verification failed.', + isError: true); + AdjustEvents.trackPaymentFailed(); } } catch (e) { if (mounted) {