You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin.Android迁移.NET8后Google Play内购3天自动退款问题排查

订单自动退款问题排查:你的InAppBilling代码遗漏了这些关键点

原本使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 14:25:55