修复:补单功能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({
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,

View File

@ -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);
}
sink.onPaymentSettled(PaymentSettlement.success(
orderId: federation,

View File

@ -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,