修复:补单功能bug修复
This commit is contained in:
parent
b55faf0db5
commit
f14474a39f
@ -180,7 +180,7 @@ abstract final class PaymentApi {
|
||||
);
|
||||
}
|
||||
|
||||
/// 获取订单详情(query 使用逻辑字段 `id` 表示订单/支付 ID)
|
||||
/// 获取订单详情(query 使用**逻辑字段** `id` 表示订单/支付 ID,经 [FieldMapping] 映射为线网 `timing`)
|
||||
static Future<EntityResponse<OrderDetailResponse>> getOrderDetail({
|
||||
required String userId,
|
||||
required String orderId,
|
||||
@ -199,7 +199,8 @@ abstract final class PaymentApi {
|
||||
/// Google 支付结果回调
|
||||
///
|
||||
/// **Query**:`app`、`userId`。
|
||||
/// **Body**(顺序与文档一致):`signature`、`purchaseData`、`id`、`userId`。
|
||||
/// **Body**(**逻辑字段**,与 [skin_config.json] `fieldMapping` 一致):`signature`、`purchaseData`、`id`、`userId`。
|
||||
/// 其中 `id` 对应 app_client 请求体里的订单标识(对方字段名常为 `federation`),在本工程映射为线网 V2 名 `timing`。
|
||||
static Future<EntityResponse<GooglePayCallbackResponse>> googlepay({
|
||||
required String signature,
|
||||
required String purchaseData,
|
||||
|
||||
@ -61,9 +61,8 @@ abstract final class NativeIapCoordinator {
|
||||
|
||||
if (!createRes.isSuccess || createRes.data == null) {
|
||||
sink.onPaymentSettled(PaymentSettlement.failure(
|
||||
message: createRes.msg.isNotEmpty
|
||||
? createRes.msg
|
||||
: 'createPayment failed',
|
||||
message:
|
||||
createRes.msg.isNotEmpty ? createRes.msg : 'createPayment failed',
|
||||
));
|
||||
return;
|
||||
}
|
||||
@ -76,8 +75,7 @@ abstract final class NativeIapCoordinator {
|
||||
app: app,
|
||||
);
|
||||
} catch (e) {
|
||||
sink.onPaymentSettled(
|
||||
PaymentSettlement.failure(message: e.toString()));
|
||||
sink.onPaymentSettled(PaymentSettlement.failure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,8 +116,7 @@ abstract final class NativeIapCoordinator {
|
||||
app: app,
|
||||
);
|
||||
} catch (e) {
|
||||
sink.onPaymentSettled(
|
||||
PaymentSettlement.failure(message: e.toString()));
|
||||
sink.onPaymentSettled(PaymentSettlement.failure(message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -134,6 +131,7 @@ abstract final class NativeIapCoordinator {
|
||||
|
||||
final purchase = await PaymentService.launchPurchaseAndReturnData(
|
||||
storeProductId,
|
||||
idFromCreatePayment: serverFederation,
|
||||
);
|
||||
if (purchase == null) {
|
||||
sink.onPaymentSettled(PaymentSettlement.cancelled(
|
||||
@ -142,16 +140,11 @@ abstract final class NativeIapCoordinator {
|
||||
return;
|
||||
}
|
||||
|
||||
final federation = (serverFederation != null &&
|
||||
serverFederation.isNotEmpty)
|
||||
final federation = (serverFederation != null && serverFederation.isNotEmpty)
|
||||
? serverFederation
|
||||
: purchase.orderId;
|
||||
if (serverFederation != null && serverFederation.isNotEmpty) {
|
||||
await PaymentService.saveFederationForGoogleOrderId(
|
||||
purchase.orderId,
|
||||
serverFederation,
|
||||
);
|
||||
}
|
||||
|
||||
// 映射已在 [PaymentService.launchPurchaseAndReturnData] 收到 Play 回调后、返回前写入。
|
||||
|
||||
final googlepayRes = await PaymentApi.googlepay(
|
||||
signature: purchase.payload.signature,
|
||||
@ -181,9 +174,7 @@ abstract final class NativeIapCoordinator {
|
||||
}
|
||||
|
||||
await PaymentService.completeAndConsumePurchase(purchase.purchaseDetails);
|
||||
if (serverFederation != null && serverFederation.isNotEmpty) {
|
||||
await PaymentService.removeFederationForGoogleOrderId(purchase.orderId);
|
||||
}
|
||||
await PaymentService.removeFederationForGoogleOrderId(purchase.orderId);
|
||||
|
||||
sink.onPaymentSettled(PaymentSettlement.success(
|
||||
orderId: federation,
|
||||
|
||||
@ -60,8 +60,7 @@ class PaymentService {
|
||||
|
||||
static final _log = AppLogger('PaymentService');
|
||||
|
||||
static const String _kFederationMapKey =
|
||||
'google_pay_google_order_to_federation';
|
||||
static const String _kFederationMapKey = 'google_pay_google_order_to_id';
|
||||
|
||||
static final Map<String, PurchaseDetails> _pendingFromStream = {};
|
||||
static StreamSubscription<List<PurchaseDetails>>? _pendingStreamSub;
|
||||
@ -92,8 +91,9 @@ class PaymentService {
|
||||
_log.d('Subscribed to purchaseStream');
|
||||
}
|
||||
|
||||
static Future<void> saveFederationForGoogleOrderId(
|
||||
static Future<void> saveIdForGoogleOrderId(
|
||||
String googleOrderId, String federation) async {
|
||||
_log.d('保存订单信息: googleOrderId=$googleOrderId, federation=$federation');
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final json = prefs.getString(_kFederationMapKey);
|
||||
@ -108,8 +108,7 @@ class PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> getFederationForGoogleOrderId(
|
||||
String googleOrderId) async {
|
||||
static Future<String?> getIdForGoogleOrderId(String googleOrderId) async {
|
||||
try {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final json = prefs.getString(_kFederationMapKey);
|
||||
@ -200,21 +199,36 @@ class PaymentService {
|
||||
}
|
||||
}
|
||||
|
||||
/// 先 **await** [InAppPurchase.completePurchase](Android 上为 acknowledge),再 [consumePurchase]。
|
||||
/// 若未等待 acknowledge 完成即 consume,易与 Billing 竞态,出现 `BillingResponse.error`。
|
||||
static Future<bool> completeAndConsumePurchase(
|
||||
PurchaseDetails purchaseDetails) async {
|
||||
final iap = InAppPurchase.instance;
|
||||
try {
|
||||
iap.completePurchase(purchaseDetails);
|
||||
_log.d('completePurchase executed');
|
||||
await iap.completePurchase(purchaseDetails);
|
||||
_log.d('completePurchase finished (awaited)');
|
||||
if (defaultTargetPlatform == TargetPlatform.android) {
|
||||
final androidAddition =
|
||||
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
|
||||
final result = await androidAddition.consumePurchase(purchaseDetails);
|
||||
final ok = result.responseCode == BillingResponse.ok;
|
||||
var result = await androidAddition.consumePurchase(purchaseDetails);
|
||||
var ok = result.responseCode == BillingResponse.ok;
|
||||
if (!ok) {
|
||||
_log.w(
|
||||
'consumePurchase failed: ${result.responseCode} '
|
||||
'${result.debugMessage ?? ''}',
|
||||
);
|
||||
await Future<void>.delayed(const Duration(milliseconds: 350));
|
||||
result = await androidAddition.consumePurchase(purchaseDetails);
|
||||
ok = result.responseCode == BillingResponse.ok;
|
||||
if (!ok) {
|
||||
_log.w(
|
||||
'consumePurchase retry failed: ${result.responseCode} '
|
||||
'${result.debugMessage ?? ''}',
|
||||
);
|
||||
}
|
||||
}
|
||||
if (ok) {
|
||||
_log.d('consumePurchase executed');
|
||||
} else {
|
||||
_log.w('consumePurchase failed: ${result.responseCode}');
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@ -241,29 +255,43 @@ class PaymentService {
|
||||
|
||||
_log.d('Order recovery: ${pending.length} pending');
|
||||
bool needRefresh = false;
|
||||
final iap = InAppPurchase.instance;
|
||||
|
||||
for (final p in pending) {
|
||||
try {
|
||||
final federation = await getFederationForGoogleOrderId(p.orderId);
|
||||
if (federation != null && federation.isNotEmpty) {
|
||||
// 与 app_client [GooglePlayPurchaseService.runOrderRecovery] 一致:
|
||||
// 有本地「Google orderId → 建单时用于 googlepay 的 id」映射则先调服务端再核销;
|
||||
// 无映射(如进程被杀在落库前)仅 complete+consume,避免永久「已拥有此内容」阻塞再次购买。
|
||||
final id = await getIdForGoogleOrderId(p.orderId);
|
||||
if (id != null && id.isNotEmpty) {
|
||||
final res = await onPaymentCallback(
|
||||
federation,
|
||||
id,
|
||||
p.payload.signature,
|
||||
p.payload.purchaseData,
|
||||
userId,
|
||||
);
|
||||
if (res.isSuccess) {
|
||||
if (await completeAndConsumePurchase(p.purchaseDetails)) {
|
||||
final consumed =
|
||||
await completeAndConsumePurchase(p.purchaseDetails);
|
||||
if (consumed) {
|
||||
_pendingFromStream.remove(p.orderId);
|
||||
await removeFederationForGoogleOrderId(p.orderId);
|
||||
needRefresh = true;
|
||||
_log.d('Order recovery success: ${p.orderId}');
|
||||
_log.d('Order recovery success: ${p.orderId} id=$id');
|
||||
} else {
|
||||
// 后端已核销成功时仍刷新本地账户,避免积分不更新;映射保留以便下次重试 consume
|
||||
needRefresh = true;
|
||||
_log.w(
|
||||
'Order recovery: googlepay ok but Play consume failed '
|
||||
'orderId=${p.orderId}; account refresh still scheduled',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
_log.w('Order recovery failed: ${p.orderId}, ${res.msg}');
|
||||
}
|
||||
} else {
|
||||
_log.d(
|
||||
'Order recovery: no id map, consume only orderId=${p.orderId}',
|
||||
);
|
||||
if (await completeAndConsumePurchase(p.purchaseDetails)) {
|
||||
_pendingFromStream.remove(p.orderId);
|
||||
needRefresh = true;
|
||||
@ -276,8 +304,13 @@ class PaymentService {
|
||||
return needRefresh;
|
||||
}
|
||||
|
||||
/// [idFromCreatePayment]:建单接口返回的 federation / 业务订单 id。
|
||||
/// 若提供,会在收到 Play 的 `orderId` 后 **立即** 落库映射(在 [Completer.complete] 之前),
|
||||
/// 降低用户支付后立刻杀进程导致补单无映射的概率。
|
||||
static Future<GooglePayPurchaseResult?> launchPurchaseAndReturnData(
|
||||
String productId) async {
|
||||
String productId, {
|
||||
String? idFromCreatePayment,
|
||||
}) async {
|
||||
_log.d('Purchase request for productId: "$productId"');
|
||||
if (defaultTargetPlatform != TargetPlatform.android) {
|
||||
return null;
|
||||
@ -297,13 +330,18 @@ class PaymentService {
|
||||
StreamSubscription<List<PurchaseDetails>>? sub;
|
||||
|
||||
sub = iap.purchaseStream.listen(
|
||||
(purchases) {
|
||||
(purchases) async {
|
||||
for (final p in purchases) {
|
||||
if (p.productID != productId) continue;
|
||||
if (p.status == PurchaseStatus.purchased ||
|
||||
p.status == PurchaseStatus.restored) {
|
||||
if (!completer.isCompleted && p is GooglePlayPurchaseDetails) {
|
||||
final b = p.billingClientPurchase;
|
||||
final federation = (idFromCreatePayment != null &&
|
||||
idFromCreatePayment.isNotEmpty)
|
||||
? idFromCreatePayment
|
||||
: b.orderId;
|
||||
await saveIdForGoogleOrderId(b.orderId, federation);
|
||||
_pendingFromStream[b.orderId] = p;
|
||||
completer.complete(GooglePayPurchaseResult(
|
||||
orderId: b.orderId,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user