Xamarin.Android迁移.NET8后Google Play内购3天自动退款问题排查
原本使用Xamarin.Android开发应用,使用Plugin.InAppBilling库。迁移至.NET 8后,用户支付几天后订单会被自动退款,损失销量。了解到需要确认支付后添加了一段代码,但未生效,代码如下:
const string PRODUCT_ID = "my.product.com"; const string PRODUCT_ID_MORE_THAN_ONE= "my.other.product.com"; readonly Context context = Application.Context; public async Task<bool> ShowDonate(bool isMoreThanOneAllowed = false) { try { var connected = await CrossInAppBilling.Current.ConnectAsync(); //Couldn't connect to billing, could be offline, alert user if (!connected) { Toast.MakeText(context, "No internet available", ToastLength.Short).Show(); return false; } var productId = isMoreThanOneAllowed ? PRODUCT_ID_MORE_THAN_ONE : PRODUCT_ID; var purchase = await CrossInAppBilling.Current.PurchaseAsync(productId, ItemType.InAppPurchase); if (purchase == null) { //Not purchased, alert the user Toast.MakeText(context, "Didn't work", ToastLength.Short).Show(); return false; } else { // Purchase was successful if ((bool)!purchase.IsAcknowledged) { if (isMoreThanOneAllowed) { // For consumables, we need to consume the purchase var consumeResult = await CrossInAppBilling.Current.ConsumePurchaseAsync(productId, purchase.PurchaseToken); if (consumeResult) { // Consuming was successful Toast.MakeText(context, "Thank you", ToastLength.Short).Show(); return true; } else { Toast.MakeText(context, "Didn't work", ToastLength.Short).Show(); return false; } } else { // For non-consumables, we finalize (acknowledge) the purchase var acknowledgeResult = await CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.PurchaseToken); if (acknowledgeResult.First().Success) { Toast.MakeText(context, "Thank you", ToastLength.Short).Show(); return true; } else { Toast.MakeText(context, "Didn't work", ToastLength.Short).Show(); return false; } } } else { // Purchase was already acknowledged Toast.MakeText(context, "Thank you", ToastLength.Short).Show(); return true; } } } catch { // Handle exception Toast.MakeText(context, "Didn't work", ToastLength.Short).Show(); return false; } finally { await CrossInAppBilling.Current.DisconnectAsync(); } }
你的代码遗漏的核心问题
未处理历史未确认/未消耗的订单
代码只处理了当前发起购买后的确认逻辑,但如果用户购买时网络中断、APP崩溃,或者之前版本没有确认逻辑,这些历史遗留的未确认订单会触发自动退款。必须在APP启动时(或进入支付页面时)查询所有未完成的购买并批量处理:// 示例:查询并处理历史未确认订单 var purchases = await CrossInAppBilling.Current.GetPurchasesAsync(ItemType.InAppPurchase); foreach (var purchase in purchases) { if (!purchase.IsAcknowledged) { bool isConsumable = purchase.ProductId == PRODUCT_ID_MORE_THAN_ONE; if (isConsumable) { await CrossInAppBilling.Current.ConsumePurchaseAsync(purchase.ProductId, purchase.PurchaseToken); } else { await CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.PurchaseToken); } } }FinalizePurchaseAsync的使用错误
调用acknowledgeResult.First().Success存在逻辑问题:FinalizePurchaseAsync返回的是所有确认请求的结果集合,对于单个订单的确认,应该检查所有结果是否成功,而非只取第一个:var acknowledgeResults = await CrossInAppBilling.Current.FinalizePurchaseAsync(purchase.PurchaseToken); bool allSuccess = acknowledgeResults.All(r => r.Success); if (allSuccess) { Toast.MakeText(context, "Thank you", ToastLength.Short).Show(); return true; }另外,确认操作必须具备重试机制——即使APP退出,下次启动也要重新处理未确认的订单,不能依赖单次购买流程中的调用。
异常处理过于笼统
无差别捕获所有异常会掩盖确认/消耗操作中的具体问题(比如Google Play服务错误、网络超时),无法定位故障原因。应该捕获具体异常并记录日志:catch (InAppBillingException billingEx) { // 记录计费异常日志 Toast.MakeText(context, $"支付错误: {billingEx.Message}", ToastLength.Short).Show(); return false; } catch (Exception ex) { // 记录其他异常日志 Toast.MakeText(context, "操作失败", ToastLength.Short).Show(); return false; }产品类型配置与代码逻辑不匹配
必须确保PRODUCT_ID_MORE_THAN_ONE在Google Play控制台中标记为消耗品,PRODUCT_ID标记为非消耗品。如果类型配置错误,消耗/确认操作会失败,导致订单无法完成,触发自动退款。ConnectAsync参数缺失
在.NET 8环境下,Plugin.InAppBilling的ConnectAsync可能需要传入指定Google Play Billing版本的参数,确保你使用的是最新版本的NuGet包,并按照官方文档正确调用连接方法。
内容的提问来源于stack exchange,提问作者Federico Navarrete

