petsHero-AI/lib/features/recharge/google_play_purchase_service.dart
2026-03-12 20:40:37 +08:00

128 lines
4.8 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
import 'package:pets_hero_ai/core/api/api.dart';
import '../../core/log/app_logger.dart';
import 'models/google_pay_verification_payload.dart';
/// 调起 Google Play 内购,所有内购均通过本方法发起并返回凭据用于服务端回调。
abstract final class GooglePlayPurchaseService {
static final _log = AppLogger('GooglePlayPurchase');
/// 发起购买并返回服务端回调所需凭据purchaseData=originalJson, signature
/// 成功返回 [GooglePayVerificationPayload],取消/失败返回 null。调用方填入 idfederation、userId 后组 merchant 上报。
static Future<GooglePayVerificationPayload?> launchPurchaseAndReturnData(
String productId) async {
_log.d('谷歌支付请求商品 ID(helm): "$productId"');
if (defaultTargetPlatform != TargetPlatform.android) {
_log.d('非 Android跳过内购');
return null;
}
final iap = InAppPurchase.instance;
if (!await iap.isAvailable()) {
_log.w('Billing 不可用');
return null;
}
final response = await iap.queryProductDetails({productId});
if (response.notFoundIDs.isNotEmpty || response.productDetails.isEmpty) {
_log.w(
'商品未找到: 请求的 productId="$productId", notFoundIDs=${response.notFoundIDs}, 请核对与 Play 后台配置的「产品 ID」是否完全一致区分大小写');
return null;
}
final product = response.productDetails.first;
final completer = Completer<GooglePayVerificationPayload?>();
StreamSubscription<List<PurchaseDetails>>? sub;
sub = iap.purchaseStream.listen(
(purchases) {
// 把 purchases 转为 JSON 输出,方便调试
try {
final list = purchases.map((p) {
final base = <String, Object?>{
'productID': p.productID,
'status': p.status.toString(),
'transactionDate': p.transactionDate,
'verificationData': {
'serverVerificationData':
p.verificationData.serverVerificationData,
'localVerificationData':
p.verificationData.localVerificationData,
'source': p.verificationData.source,
},
'pendingCompletePurchase': p.pendingCompletePurchase,
'error': p.error?.message,
};
if (p is GooglePlayPurchaseDetails) {
final b = p.billingClientPurchase;
base['googlePlay'] = {
'orderId': b.orderId,
'packageName': b.packageName,
'purchaseTime': b.purchaseTime,
'purchaseToken': b.purchaseToken,
'signature': b.signature,
'originalJson': b.originalJson,
'isAcknowledged': b.isAcknowledged,
'purchaseState': b.purchaseState.toString(),
};
}
return base;
}).toList();
_log.d('Google Play purchases json: ${jsonEncode(list)}');
logWithEmbeddedJson(jsonEncode(list));
} catch (e) {
_log.w('序列化 purchases 失败: $e');
}
for (final p in purchases) {
if (p.productID != productId) continue;
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(
purchaseData: b.originalJson,
signature: b.signature,
);
}
iap.completePurchase(p);
completer.complete(payload);
}
sub?.cancel();
return;
}
if (p.status == PurchaseStatus.error ||
p.status == PurchaseStatus.canceled) {
if (!completer.isCompleted) completer.complete(null);
sub?.cancel();
return;
}
}
},
onError: (e) {
if (!completer.isCompleted) completer.complete(null);
sub?.cancel();
},
);
final success = await iap.buyConsumable(
purchaseParam: PurchaseParam(productDetails: product),
);
if (!success) {
sub.cancel();
return null;
}
return completer.future.timeout(
const Duration(seconds: 120),
onTimeout: () {
sub?.cancel();
return null;
},
);
}
}