Android 应用内计费 - 如何处理退款

问题描述 投票:0回答:2

我正在尝试在我的应用程序中实现应用内计费,除了退款之外,一切都很好。在过去的几天里,我一直在努力反对这一点,似乎令人难以置信的是,没有办法从应用程序方面知道用户是否要求退款。 我希望能够在用户退款后撤销对一次性托管产品的访问权限(删除广告)。 我没有使用后端,所以我依赖 Google Play API。

我尝试过使用

queryPurchaseHistoryAsync
查询 Google Play API,它会返回用户最近购买的列表。这似乎不起作用,因为在要求退款后购买的商品仍然存在(在写这篇文章之前已经等了一天)。

这就是我所做的:

  1. 在真实设备上安装应用程序
  2. 购买应用内内容
  3. 验证应用程序解锁内容
  4. 转到我的 Google Play 订单历史记录并要求应用内商品退款
  5. 10 分钟后交易得到退款(我作为开发者完全没有参与)
  6. App仍提供付费内容
  7. 清理 Play 商店应用程序数据和缓存
  8. App仍提供付费内容

那么任何用户都可以购买我的应用内产品,然后立即转到他的 Google Play 页面并要求退款? 是我遗漏了一些明显的东西还是这个 API 是一场噩梦?

PurchaseState
枚举是

  public @interface PurchaseState {
    // Purchase with unknown state.
    int UNSPECIFIED_STATE = 0;
    // Purchase is completed.
    int PURCHASED = 1;
    // Purchase is waiting for payment completion.
    int PENDING = 2;
  }

我在这里没有看到任何与退款相关的内容, 在我看来,这是一个非常正常的用例,所以我仍然认为我缺少一些关键信息,我该怎么做?

感谢您的帮助

android in-app-billing
2个回答
12
投票

问题

即使用户退款并实际收到退款,任何购买行为仍会被记录。当您(开发者/应用程序所有者)对应用内产品发出退款/撤销请求时,情况也是如此。

唯一的区别是购买的“购买状态”。 Google 的计费库的问题在于,它们将 buy.getPurchaseState() 调用中的“purchaseState”值隐藏为 PENDNGPURCHASED 状态。反编译代码中可见:

public int getPurchaseState() {
    switch(this.zzc.optInt("purchaseState", 1)) {
    case 4:
        return 2; // PENDING
    default:
        return 1; // PURCHASED
    }
}

当你的应用内商品处于退款状态时,它的状态是 UNSPECIFIED_STATE,如上面的代码所示,被屏蔽为 PURCHASED。

简单的解决方案

要克服这个问题,只需忽略库的purchase.getPurchaseState()方法,而是使用您自己的未屏蔽的自定义:

int getUnmaskedPurchaseState(Purchase purchase) {
    int purchaseState = purchase.getPurchaseState();
    try {
        purchaseState = new JSONObject(purchase.getOriginalJson()).optInt("purchaseState", 0);
    } catch (JSONException e) {
        e.printStackTrace();
    }

    return purchaseState;
}

硬解

谷歌似乎只是掩盖了这一点,以推动您采取此解决方案。使用 作废的购买 API 用于提供与用户已作废的购买相关的订单列表。 根据他们的文档...

可以通过以下方式取消购买:

  • 用户要求退款。用户取消订单。
  • 订单被退款。
  • 开发商取消订单或退款。注意:只有撤销的订单才会显示在作废的购买 API 中。
  • 如果开发者在未设置撤销选项的情况下退款,订单将不会显示在 API 中。
  • Google 取消订单或退款。

但是,此解决方案目前无法作为 Java android 库使用,您必须发出服务器请求并使用 Google Play 开发者 API。


0
投票

要处理退款,您应该查询购买情况,最好是 Activity 的 onResume():

fun queryPurchases() {
    MainScope().launch {
        Log.d(TAG, "queryPurchases: Called")
        val params = QueryPurchasesParams.newBuilder()
            .setProductType(BillingClient.ProductType.INAPP)
            .build()
        val queryPurchasesResult = billingClient.queryPurchasesAsync(params)

        if (queryPurchasesResult.billingResult.responseCode == BillingResponseCode.OK) {

            val hasPremiumPurchase = queryPurchasesResult.purchasesList.any { purchase ->
                purchase.purchaseState == PurchaseState.PURCHASED && purchase.products.contains(Premium_Product_ID)
            }

            // If any premium purchase is found, invoke the restore callback
            if (hasPremiumPurchase) {
                Log.d(TAG, "queryPurchases: SHOULD RESTORE PURCHASE")
                onRestorePurchase?.invoke()
                return@launch
            }
            if (queryPurchasesResult.purchasesList.isEmpty()) {
                Log.d(TAG, "queryPurchases: No premium purchase found. UNSET PREMIUM STATUS")
                unsetPremiumStatus?.invoke()
            }
        }
    }
}

当purchasingList为空或不包含PurchaseState.PURCHASED状态下的购买时,您应该取消设置高级状态

© www.soinside.com 2019 - 2024. All rights reserved.