如何在Android上取消应用内测试购买?

53
直到2016年6月20日,我可以取消应用内测试购买。使用相同的测试账户进行多个应用内购买(非消耗性)使得开发和测试代码变得容易,没有太多的麻烦。
在2016年6月20日之后,这些购买不会显示在我的商家账户中,我无法从测试账户中购买超过1个的物品,每次只会看到“你已拥有此物品”的消息。
我向谷歌开发者支持小组提交了请求,答复如下: 从2016年6月20日开始,我们改变了一次性应用内购买的测试购买方式。以前,一次性应用内购买的测试购买生成订单ID。从2016年6月20日开始,一次性应用内购买不会生成官方订单ID(如果有的话),也不会出现在商家中心。订阅应用内购买的行为已经适用于这种情况。 您可以在Android开发者帮助中心了解有关应用内计费测试的更多信息:https://developer.android.com/google/play/billing/billing_testing.html#testing-purchases 好吧,那么我转到了提到的链接,里面有一个部分: 取消完成的测试购买 Google Play会为每个用户累计完成的测试购买,但不会将它们传递给财务处理。在某些情况下,您可能希望手动取消测试购买以继续测试。要这样做,请在Play商店中打开应用程序页面。如果您要取消的测试购买是订阅,则还可以使用Purchases.subscriptions API的cancel()方法。 重要提示:Purchases.subscriptions API的refund()和revoke()方法不支持测试购买。
那么我转到Play商店中的应用程序页面......然后该怎么做呢?网页没有说明我应该在那里做什么。有人知道吗?
它确实说:您还可以使用Purchases.subscriptions API的cancel()方法。

这表明使用cancel()方法并不是唯一的方法。

如何在不添加额外代码的情况下解决这个问题?


1
嗨,你解决了吗?我在进行测试购买后也遇到了同样的情况。 - Ramesh_D
1
您可以从以下链接打开所有测试购买:https://play.google.com/store/account?feature=gp_receipt,在此页面上,我发现唯一的取消方式是使用“报告问题”按钮。 - Ramesh_D
1
我也想知道怎么做。无法多次测试购买几乎使得测试购买变得不可能... - Moritur
1
你找到取消它的解决方案了吗? - powder366
这需要准确地报告我即将发布的问题。这是谷歌不负责任文档的又一个例子。 - Utsav Gupta
显示剩余2条评论
8个回答

30

我进入了Google Play主控制台页面,并点击了订单管理。在此之下,我能够选择所有测试购买并退款。由于我是该应用程序的主要开发者,所以我有权限进行此操作。如果您是测试人员,则可能需要联系支持团队并请求他们退款您的订单。


3
棒极了,非常棒的答案!完美解决了问题。 一旦交易被标记为“已退款”,我就强制退出了Play Store并清除了其缓存和应用数据。 然后我取消订阅,就可以重新购买订阅了。 - Michael Rodrigues
我还必须消费该产品(请参见此处的UPDATE2:https://dev59.com/dloT5IYBdhLWcg3w0SKZ) - franswa
1
尝试过了,似乎起作用了,在Play Console中,订单显示为“已退款”。但是在游戏内,我仍然收到了收据。然而今天早上,它又起作用了,即应用程序没有收到收据。我猜测,需要一些时间来清除某些缓存。也许这可以帮助某些人。 - joergipoergi
1
应该有更好的方法来取消应用内测试订阅、检查它们的到期时间、续订时间等。整个API都是一团糟。 - Karanveer Singh
即使我强制退出Play Store并清除缓存和数据,这仍然对我无效。我有两个总是成功的购买,它们没有交易ID。我的订单管理页面上的所有订单都已退款或付款被拒绝(通过测试卡)。我正在使用flutter_inapp_purchase插件,所以问题可能出在那里。但我无法删除它们,也不知道如何将它们与真正的购买区分开来。 - user1978019
显示剩余3条评论

20

所有通过应用内购买获得的产品都是可消耗型商品。

正如文档中所述。

这意味着您可以消费已拥有的物品,而不是取消购买并再次购买。我建议在应用启动时查询库存:

mIabHelper.queryInventoryAsync(this);

然后您可以在回调函数中消费所拥有的物品:

@Override
public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
    Purchase purchase = inventory.getPurchase(MY_SKU);
    boolean isBought = (purchase != null && verifyDeveloperPayload(purchase));
    if (isBought) {
        mIabHelper.consumeAsync(purchase, new OnConsumeFinishedListener() {
            @Override
            public void onConsumeFinished(Purchase purchase, IabResult result) {
                //Clear the purchase info from persistent storage
            }
        });
    }
}

这段代码可以用来测试IAB流程,但在发布版本中一定要删除此代码。


“//Clear the purchase info from persistent storage” 的意思是什么? - IgorGanapolsky
1
这取决于你的实现方式。在我的情况下,我将购买详情存储在共享首选项中,因此可以从其他活动中进行检查。 - dev.bmax
1
你能解释一下 verifyDeveloperPayload 方法吗? - Nidhi Savaliya
您可以在此处了解有关开发人员有效载荷的更多信息:https://developer.android.com/google/play/billing/billing_library_overview#attach_a_developer_payload - dev.bmax

12
  • 在 Play 控制台中,转到“开发者帐户”->“帐户详细信息”,设置许可证测试人员(默认情况下您是其中之一)。

  • 购买物品。

  • 进入订单管理,选择那些测试订单,并选择:退款,但不要忘记在退款时勾选“吊销”。(我忘记了吊销,现在找不到办法将它们取回。)

无论如何,另一个测试帐户也可以。


3
我发现即使忘记了,实际上仍然可以撤销,但需要使用API,这很麻烦。文档在此:https://developers.google.com/android-publisher/api-ref/orders/refund 在执行请求时,请确保在URL末尾包含“?revoke=true”。我延迟了很久才进行了此请求,等了最多30分钟,但成功地撤销了我之前在控制台中忘记撤销的购买。 - Cliff Cawley
Cliff,我尝试了你的解决方案,但是我收到了一个错误消息,说这个ID对应的产品不存在。我花了几个小时来设置一切。延迟可能是原因吗?即使在执行“adb shell pm clear com.android.vending”之后,我仍然收到拥有产品的消息。 - Developer

6

我找到了一个解决方案,虽然不太方便,但是有效。看起来你可以消耗无法消耗的产品,这样你就可以再次购买它们。我正在使用PhoneGap,所以我只有cordova-plugin-purchase插件的示例代码:

store.when("your.product.id").updated(product => {
    if(product.owned) {
        var transaction = product.transaction;
        product.transaction = null;
        store.inappbilling.consumePurchase(
            function() { // success
                alert("consume success");
            },
            function(err, code) { // error
                alert("consume error " + err)
            },
            product.id,
            transaction.id
        );
    }
});

更新的回调函数将在您调用store.refresh()或购买产品时被调用。因此,根据您的使用情况,您需要实现一个额外的检查方法来确定何时消耗该产品。
我没有使用原生Android应用内付款的经验,但显然您也可以在那里消耗产品。
编辑:对不起,我刚才看到您不想在项目中包含其他代码。我认为目前这是不可能的,但我希望保留我的答案,因为它可能有助于其他试图测试应用内付款的人。

store.inappbilling.consumePurchase 在 Java 中的等价物是什么? - IgorGanapolsky
其次是Cordova的解决方案。我只需要将类型更改为"可消耗",运行应用程序,让它进行消费,然后关闭它,将类型改回并重新运行即可。 - trueicecold

1
我的做法是结合以下两种方法:
  • 前往订单管理并退款
  • 清除Play商店应用程序中的缓存/数据(以及您在其中放置了一些共享首选项的应用程序)。

此外,如果您遇到已拥有物品的状态,则可以使用购买令牌并调用billingClient.consumeAsync()来消耗购买。


0

对于使用基于新的TrivialDriveKotlin的方式的人来说,在应用程序安装期间,可消耗产品将被消耗掉。

handleConsumablePurchasesAsync

如果您的购买不是可消耗品,您可以通过将相应的 SKU 添加到 GameSku 对象中的 CONSUMABLE_SKUS 中使其成为可消耗品。例如:
val CONSUMABLE_SKUS = listOf(GAS, PREMIUM_CAR)

然后从您的设备中卸载应用程序并重新安装,您的非消耗性购买将再次可用。快速简便。 当然,不要忘记删除您的


0

未找到解决方法。我的解决方法是从测试用户列表中删除当前测试用户,进行真实购买,然后使用商家控制台取消购买。


你们还在测试阶段吗? - powder366
不,生产环境禁止。 - Eli
只是提醒一下,您可以使用Alpha或Beta版本进行真正的购买。我不确定谷歌在关于购买后取消以进行测试方面的政策是什么,所以我不能建议这样做。 - Dave Rager
我已经将用户从“许可证测试”部分中删除,但测试购买仍然存在。 - Srikar Reddy
我是应用程序的所有者,每当我尝试购买时,它总是进入测试购买模式。但其他人可以进行实际购买。如何更改这个问题?有人有任何想法吗? - sreenadh

0

即使我早已消耗、退款和撤销了去年的测试订单,queryPurchaseHistoryAsync方法仍然可以找到它们。我发现一个有用的预防措施是清除Google Play商店应用程序中的数据(设置/应用程序/Google Play商店/存储/清除数据)。queryPurchaseHistoryAsync从这里提取过时的购买数据,尽管只有非网络(我发现完全不可靠)的queryPurchases应该这样做。你可能需要在你的应用程序中添加额外的代码,但它不必太多。

由于不再支持 Trivial Drive 2(文档中的链接会导致“404页面不存在”错误,github 文件已存档,并且更新到 billing:2.1.0 将在 IabHelper 类中给出售货机导入编译错误),因此涉及 IabHelper 的这个常见问题的答案可能已经过时。如果您遵循文档中从这里开始的基本代码片段https://developer.android.com/google/play/billing/billing_overview,则现在的计费方式要简单得多,而不需要混乱的帮助类。一个持久的问题是,在这种实现中,您可能会遇到“两种方法具有相同的擦除,但都没有覆盖另一个”的方法冲突错误,请参见我在这里提供的解决方案'Both methods have same erasure, yet neither overides the other' method clash error in SkuDetailsResponseListener()

一旦您实现了最新的计费代码,您可以在生产应用程序中创建一个隐藏触发器来调用queryPurchaseHistoryAsync以获取purchaseHistoryRecordList。然后为此列表中的每个项目调用consumeAsync。以下是消耗所有测试订单的基本代码,允许对非消耗品进行多次测试:

billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.INAPP,
   new PurchaseHistoryResponseListener() {
      @Override
      public void onPurchaseHistoryResponse(BillingResult billingResult,
         List<PurchaseHistoryRecord> purchaseHistoryRecordList){                                            
         if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
             && purchaseHistoryRecordList != null) {
             for (PurchaseHistoryRecord purchaserecord : purchaseHistoryRecordList) {
                 if(purchaserecord!=null){
                 ConsumeParams consumeParams =
                     ConsumeParams.newBuilder()
                    .setPurchaseToken(purchaserecord.getPurchaseToken())
                    .setDeveloperPayload(purchaserecord.getDeveloperPayload())
                    .build();                                                                 
                     billingClient.consumeAsync(consumeParams, consumelistener);
}}}}});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接