修复:补单功能bug修复

This commit is contained in:
ivan 2026-04-14 12:53:31 +08:00
parent b55faf0db5
commit f14474a39f
3 changed files with 69 additions and 39 deletions

View File

@ -180,7 +180,7 @@ abstract final class PaymentApi {
); );
} }
/// query 使 `id` / ID /// query 使**** `id` / ID [FieldMapping] 线 `timing`
static Future<EntityResponse<OrderDetailResponse>> getOrderDetail({ static Future<EntityResponse<OrderDetailResponse>> getOrderDetail({
required String userId, required String userId,
required String orderId, required String orderId,
@ -199,7 +199,8 @@ abstract final class PaymentApi {
/// Google /// Google
/// ///
/// **Query**`app``userId` /// **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({ static Future<EntityResponse<GooglePayCallbackResponse>> googlepay({
required String signature, required String signature,
required String purchaseData, required String purchaseData,

View File

@ -61,9 +61,8 @@ abstract final class NativeIapCoordinator {
if (!createRes.isSuccess || createRes.data == null) { if (!createRes.isSuccess || createRes.data == null) {
sink.onPaymentSettled(PaymentSettlement.failure( sink.onPaymentSettled(PaymentSettlement.failure(
message: createRes.msg.isNotEmpty message:
? createRes.msg createRes.msg.isNotEmpty ? createRes.msg : 'createPayment failed',
: 'createPayment failed',
)); ));
return; return;
} }
@ -76,8 +75,7 @@ abstract final class NativeIapCoordinator {
app: app, app: app,
); );
} catch (e) { } catch (e) {
sink.onPaymentSettled( sink.onPaymentSettled(PaymentSettlement.failure(message: e.toString()));
PaymentSettlement.failure(message: e.toString()));
} }
} }
@ -118,8 +116,7 @@ abstract final class NativeIapCoordinator {
app: app, app: app,
); );
} catch (e) { } catch (e) {
sink.onPaymentSettled( sink.onPaymentSettled(PaymentSettlement.failure(message: e.toString()));
PaymentSettlement.failure(message: e.toString()));
} }
} }
@ -134,6 +131,7 @@ abstract final class NativeIapCoordinator {
final purchase = await PaymentService.launchPurchaseAndReturnData( final purchase = await PaymentService.launchPurchaseAndReturnData(
storeProductId, storeProductId,
idFromCreatePayment: serverFederation,
); );
if (purchase == null) { if (purchase == null) {
sink.onPaymentSettled(PaymentSettlement.cancelled( sink.onPaymentSettled(PaymentSettlement.cancelled(
@ -142,16 +140,11 @@ abstract final class NativeIapCoordinator {
return; return;
} }
final federation = (serverFederation != null && final federation = (serverFederation != null && serverFederation.isNotEmpty)
serverFederation.isNotEmpty)
? serverFederation ? serverFederation
: purchase.orderId; : purchase.orderId;
if (serverFederation != null && serverFederation.isNotEmpty) {
await PaymentService.saveFederationForGoogleOrderId( // [PaymentService.launchPurchaseAndReturnData] Play
purchase.orderId,
serverFederation,
);
}
final googlepayRes = await PaymentApi.googlepay( final googlepayRes = await PaymentApi.googlepay(
signature: purchase.payload.signature, signature: purchase.payload.signature,
@ -181,9 +174,7 @@ abstract final class NativeIapCoordinator {
} }
await PaymentService.completeAndConsumePurchase(purchase.purchaseDetails); await PaymentService.completeAndConsumePurchase(purchase.purchaseDetails);
if (serverFederation != null && serverFederation.isNotEmpty) { await PaymentService.removeFederationForGoogleOrderId(purchase.orderId);
await PaymentService.removeFederationForGoogleOrderId(purchase.orderId);
}
sink.onPaymentSettled(PaymentSettlement.success( sink.onPaymentSettled(PaymentSettlement.success(
orderId: federation, orderId: federation,

View File

@ -60,8 +60,7 @@ class PaymentService {
static final _log = AppLogger('PaymentService'); static final _log = AppLogger('PaymentService');
static const String _kFederationMapKey = static const String _kFederationMapKey = 'google_pay_google_order_to_id';
'google_pay_google_order_to_federation';
static final Map<String, PurchaseDetails> _pendingFromStream = {}; static final Map<String, PurchaseDetails> _pendingFromStream = {};
static StreamSubscription<List<PurchaseDetails>>? _pendingStreamSub; static StreamSubscription<List<PurchaseDetails>>? _pendingStreamSub;
@ -92,8 +91,9 @@ class PaymentService {
_log.d('Subscribed to purchaseStream'); _log.d('Subscribed to purchaseStream');
} }
static Future<void> saveFederationForGoogleOrderId( static Future<void> saveIdForGoogleOrderId(
String googleOrderId, String federation) async { String googleOrderId, String federation) async {
_log.d('保存订单信息: googleOrderId=$googleOrderId, federation=$federation');
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(_kFederationMapKey); final json = prefs.getString(_kFederationMapKey);
@ -108,8 +108,7 @@ class PaymentService {
} }
} }
static Future<String?> getFederationForGoogleOrderId( static Future<String?> getIdForGoogleOrderId(String googleOrderId) async {
String googleOrderId) async {
try { try {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final json = prefs.getString(_kFederationMapKey); 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( static Future<bool> completeAndConsumePurchase(
PurchaseDetails purchaseDetails) async { PurchaseDetails purchaseDetails) async {
final iap = InAppPurchase.instance; final iap = InAppPurchase.instance;
try { try {
iap.completePurchase(purchaseDetails); await iap.completePurchase(purchaseDetails);
_log.d('completePurchase executed'); _log.d('completePurchase finished (awaited)');
if (defaultTargetPlatform == TargetPlatform.android) { if (defaultTargetPlatform == TargetPlatform.android) {
final androidAddition = final androidAddition =
iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>(); iap.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
final result = await androidAddition.consumePurchase(purchaseDetails); var result = await androidAddition.consumePurchase(purchaseDetails);
final ok = result.responseCode == BillingResponse.ok; 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) { if (ok) {
_log.d('consumePurchase executed'); _log.d('consumePurchase executed');
} else {
_log.w('consumePurchase failed: ${result.responseCode}');
} }
return ok; return ok;
} }
@ -241,29 +255,43 @@ class PaymentService {
_log.d('Order recovery: ${pending.length} pending'); _log.d('Order recovery: ${pending.length} pending');
bool needRefresh = false; bool needRefresh = false;
final iap = InAppPurchase.instance;
for (final p in pending) { for (final p in pending) {
try { try {
final federation = await getFederationForGoogleOrderId(p.orderId); // app_client [GooglePlayPurchaseService.runOrderRecovery]
if (federation != null && federation.isNotEmpty) { // Google orderId googlepay id
// complete+consume
final id = await getIdForGoogleOrderId(p.orderId);
if (id != null && id.isNotEmpty) {
final res = await onPaymentCallback( final res = await onPaymentCallback(
federation, id,
p.payload.signature, p.payload.signature,
p.payload.purchaseData, p.payload.purchaseData,
userId, userId,
); );
if (res.isSuccess) { if (res.isSuccess) {
if (await completeAndConsumePurchase(p.purchaseDetails)) { final consumed =
await completeAndConsumePurchase(p.purchaseDetails);
if (consumed) {
_pendingFromStream.remove(p.orderId); _pendingFromStream.remove(p.orderId);
await removeFederationForGoogleOrderId(p.orderId); await removeFederationForGoogleOrderId(p.orderId);
needRefresh = true; 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 { } else {
_log.w('Order recovery failed: ${p.orderId}, ${res.msg}'); _log.w('Order recovery failed: ${p.orderId}, ${res.msg}');
} }
} else { } else {
_log.d(
'Order recovery: no id map, consume only orderId=${p.orderId}',
);
if (await completeAndConsumePurchase(p.purchaseDetails)) { if (await completeAndConsumePurchase(p.purchaseDetails)) {
_pendingFromStream.remove(p.orderId); _pendingFromStream.remove(p.orderId);
needRefresh = true; needRefresh = true;
@ -276,8 +304,13 @@ class PaymentService {
return needRefresh; return needRefresh;
} }
/// [idFromCreatePayment] federation / id
/// Play `orderId` **** [Completer.complete]
///
static Future<GooglePayPurchaseResult?> launchPurchaseAndReturnData( static Future<GooglePayPurchaseResult?> launchPurchaseAndReturnData(
String productId) async { String productId, {
String? idFromCreatePayment,
}) async {
_log.d('Purchase request for productId: "$productId"'); _log.d('Purchase request for productId: "$productId"');
if (defaultTargetPlatform != TargetPlatform.android) { if (defaultTargetPlatform != TargetPlatform.android) {
return null; return null;
@ -297,13 +330,18 @@ class PaymentService {
StreamSubscription<List<PurchaseDetails>>? sub; StreamSubscription<List<PurchaseDetails>>? sub;
sub = iap.purchaseStream.listen( sub = iap.purchaseStream.listen(
(purchases) { (purchases) async {
for (final p in purchases) { for (final p in purchases) {
if (p.productID != productId) continue; if (p.productID != productId) continue;
if (p.status == PurchaseStatus.purchased || if (p.status == PurchaseStatus.purchased ||
p.status == PurchaseStatus.restored) { p.status == PurchaseStatus.restored) {
if (!completer.isCompleted && p is GooglePlayPurchaseDetails) { if (!completer.isCompleted && p is GooglePlayPurchaseDetails) {
final b = p.billingClientPurchase; final b = p.billingClientPurchase;
final federation = (idFromCreatePayment != null &&
idFromCreatePayment.isNotEmpty)
? idFromCreatePayment
: b.orderId;
await saveIdForGoogleOrderId(b.orderId, federation);
_pendingFromStream[b.orderId] = p; _pendingFromStream[b.orderId] = p;
completer.complete(GooglePayPurchaseResult( completer.complete(GooglePayPurchaseResult(
orderId: b.orderId, orderId: b.orderId,