在第 3 版 Billing API 中,Google 删除了消耗品和非消耗品.两者都被组合成一种称为托管"的新类型,并且表现得有点像混合:您的应用程序需要主动调用一个方法来使用"这些项目.如果从未对一组 skus 执行此操作,那么这些项目基本上表现得就好像它们是非消耗品一样.
With version 3 of the Billing API, Google has removed the distinction between consumable and non-consumable products. Both have been combined into a new type called "managed" and behave somewhat like a hybrid: Your app needs to actively call a method to "consume" the items. If that is never done for a set of skus, those items basically behave as if they were non-consumable.
文档描述了预期的购买流程,如下所示:
The documentation describes the intended purchase flow as follows:
getBuyIntent
调用启动购买流程.Bundle
指示购买是否成功完成.consumePurchase
来消费.getBuyIntent
call.Bundle
from Google Play indicating if the purchase completed successfully.consumePurchase
call.我发现这种方法存在两个问题.一个是相当明显的,并且在文档中比 API 更错误",但另一个相当微妙,我还没有弄清楚如何最好地处理它.让我们从明显的完整性开始:
I see two problems with this approach. One is fairly obvious and more a "bug" in the documentation than the API, but the other is rather subtle and I still haven't figured out how to best handle it. Let's start with the obvious one for completeness:
问题 1:单个设备上的购买丢失:
文档说应用程序应该在每次启动时调用 getPurchases
以检查用户是否拥有任何出色的应用内消耗品".如果是这样,应用程序应该使用这些并提供相关的项目.这包括在购买完成后但在商品被消费之前(即在第 2 步左右)中断购买流程的情况.
The docs say that an app should call getPurchases
every time it is launched to "check if the user owns any outstanding consumable in-app products". If so, the app should consume these and provision the associated item. This covers the case where the purchase flow is interrupted after the purchase is completed, but before the item is consumed (i.e. around step 2).
但是如果购买流程在第 4 步和第 5 步之间中断怎么办?IE.该应用程序已成功消费了购买,但它在有机会向用户提供产品之前就被杀死了(电话打进来并且周围没有足够的内存,电池没电,崩溃等等).在这种情况下,购买将不再包含在 getPurchases
中,并且基本上用户永远不会收到他支付的费用(在此处插入愤怒的支持电子邮件和一星评论)...
But what if the purchase flow is interrupted between step 4 and 5? I.e. the app has successfully consumed the purchase but it got killed (phone call came in and there wasn't enough memory around, battery died, crash, whatever) before it had a chance to provision the product to the user. In such a case, the purchase will no longer be included in getPurchases
and basically the user never receives what he paid for (insert angry support email and one-star review here)...
幸运的是,通过引入日志"(就像在文件系统中一样
Luckily this problem is fairly easy to fix by introducing a "journal" (like in a file system) to change the purchase flow to something more like this (Steps 1 and 2 same as above):
如果购买成功,请在日志中输入一旦购买<order-id here>成功消费后,将硬币从300增加到400."
If the purchase was successful, make entry into journal saying "increase coins from 300 to 400 once purchase <order-id here> is successfully consumed."
确认日记帐分录后,通过调用 consumePurchase
消耗购买.
After journal entry is confirmed, consume the purchase by making a consumePurchase
call.
然后,每次应用启动时,它不应该只检查 getPurchases
,还应该检查日志.如果那里有一个未由 getPurchases
报告的不完整购买条目,请继续第 6 步.如果以后的 getPurchase
应该再次将该订单 ID 返回为已拥有(例如,如果消费最终失败),如果日志将此订单 ID 列为完成,则只需忽略该交易.
Then, every time the app starts, it shouldn't just check getPurchases
, but also the journal. If there is an entry there for an incomplete purchase that wasn't reported by getPurchases
, continue at step 6. If a later getPurchase
should ever return that order ID as owned again (e.g. if the consumption failed after all), simply ignore the transaction if the journal lists this order ID as complete.
这应该解决问题 1,但如果您发现这种方法有任何缺陷,请告诉我.
This should fix problem 1, but please do let me know if you find any flaws in this approach.
问题 2:涉及多个设备时的问题:
假设用户拥有两台设备(例如手机和平板电脑),并且在这两个设备上都使用相同的帐户.
Let's say a user owns two devices (a phone and a tablet, for example) with the same account on both.
他(或她——从现在开始暗示)可以尝试在他的手机上购买更多的硬币,并且应用程序可能会在购买完成后但在消费之前被终止.现在,如果他接下来在他的平板电脑上打开应用程序,getPurchases
将报告该产品为已拥有.
He (or she - to be implied from now on) could try to purchase more coins on his phone and the app could get killed after the purchase completed, but before it is consumed. Now, if he opens the app on his tablet next, getPurchases
will report the product as owned.
平板电脑上的应用程序必须假设购买是在此处开始的,并且在创建日记帐分录之前它已死亡,因此它将创建日记帐分录、消费产品并提供硬币.
The app on the tablet will have to assume that the purchase was initiated there and that it died before the journal entry was created, so it will create the journal entry, consume the product, and provision the coins.
如果手机应用程序在有机会进行日记帐分录之前就死了,硬币将永远不会在手机上提供(在此处插入愤怒的支持电子邮件和一星评论).如果手机应用程序在创建日记帐分录后死机,硬币将也在手机上提供,基本上让用户在平板电脑上免费购买(在此处插入收入损失).
If the phone app died before it had a chance to make the journal entry, the coins will never be provisioned on the phone (insert angry support email and one-star review here). And if the phone app died after the journal entry was created, the coins will also be provisioned on the phone, basically giving the user a purchase for free on the tablet (insert lost revenue here).
解决此问题的一种方法是将一些唯一的安装或设备 ID 作为有效负载添加到购买中,以检查购买是否针对此设备.然后,平板电脑可以简单地忽略购买,只有手机会记入硬币并消费该物品.
One way around this is to add some unique install or device ID as a payload to the purchase to check whether the purchase was meant for this device. Then, the tablet can simply ignore the purchase and only the phone will ever credit the coins and consume the item.
但是:由于此时 sku 仍在用户手中,因此 Play 商店将不允许用户再购买一份副本,所以基本上,直到用户在他的电脑上再次启动该应用程序手机完成待处理的交易,他将无法在平板电脑上购买更多虚拟硬币(在此处插入愤怒的支持电子邮件、一星评论和收入损失).
BUT: Since the sku is still in the user's possession at this point, the Play Store will not allow the user to buy another copy, so basically, until the user launches the app again on his phone to complete the pending transaction, he will not be able to purchase any more virtual coins on the tablet (insert angry support email, one-star review, and lost revenue here).
有没有一种优雅的方式来处理这种情况?我能想到的唯一解决方案是:
Is there an elegant way to handle this scenario? The only solutions I can think of are:
有没有更好的方法?还是我可能只是从根本上误解了某些东西而这里真的没有问题?(我意识到这个问题出现的可能性很小,但是有了足够大的用户群,不太可能"最终会变成所有时间".)
Is there a better way? Or am I maybe just fundamentally misunderstanding something and there really is no issue here? (I realize that the chances of this problem ever coming up are slim, but with a large enough user-base, "unlikely" eventually becomes "all-the-time".)
这是解决这一切的最简单方法,到目前为止我已经想出了.这不是最优雅的方法,但至少它应该有效:
Here's the simplest way to fix all this, that I have come up with so far. It's not the most elegant approach, but at least it should work:
getBuyIntent
启动购买流程,并将 购买 ID 作为开发者有效负载.Bundle
指示购买是否成功完成.consumePurchase
来消费购买getBuyIntent
with the purchase ID as the developer payload.Bundle
from Google Play indicating if the purchase completed successfully.consumePurchase
call每次启动应用程序时,请执行以下操作:
Every time the app is launched, go through the following:
getPurchases
请求以查询用户拥有的应用内商品.getPurchases
request to query the owned in-app products for the user.以下是在单个设备上出现问题的原因以及随后会发生的情况:
在多设备情况下,任何其他设备将简单地忽略任何非本地待处理购买(报告为拥有的消耗品)作为购买 ID 不在该设备的本地列表中.
In the multiple-device-case, any other device will simply ignore any non-local pending purchases (consumables reported as owned) as the purchase ID is not in that device's local list.
一个问题是待定购买将阻止其他设备开始并行购买同一产品.因此,如果用户在他的手机上的第 2 步和第 5 步之间(即在购买完成之后,但在消费完成之前)的某个地方有不完整的交易,他将无法在他的平板电脑上购买相同的产品,直到应用完成第 5 步,即在手机上消费产品.
The one issue is that a pending purchase will prevent other devices from being able to start a parallel purchase for the same product. So, if a user has an incomplete transaction stuck somewhere between step 2 and 5 (i.e. after purchase completion, but before consumption completion) on his phone, he won't be able to do any more purchases of the same product on his tablet until the app completes step 5, i.e. consumes the product, on the phone.
通过将每个消耗品 SKU 的多个副本(可能是 5 个?)添加到 Google Play 并将第一个列表中的第 2 步更改为:
This issue can be resolved very easily (but not elegantly) by adding multiple copies (5 maybe?) of each consumable SKU to Google Play and changing step 2 in the first list to:
getBuyIntent
和 购买 ID 为集合中的下一个可用 SKU启动购买流程开发者有效载荷.getBuyIntent
with the purchase ID as the developer payload.关于可破解性的说明(按黑客难度递增的顺序):
总的来说,购买的签名验证和一些相对简单但不明显的校验和或相关数据的签名(在内存和本地存储中)应该足以迫使黑客反编译(或以其他方式对应用程序进行逆向工程)以窃取产品.除非该应用程序非常受欢迎,否则这应该是一个足够的威慑.代码中的灵活逻辑加上频繁更新会破坏任何已开发的补丁,可以让应用成为黑客的移动目标.
Overall, signature verification for the purchases and some relatively simple but non-obvious checksumming or signing of the relevant data (in memory and in the local storage) should be sufficient to force a hacker to decompile (or otherwise reverse-engineer) the app in order to steal product. Unless the app gets hugely popular this should be a sufficient deterrent. Flexible logic in the code combined with somewhat frequent updates that break any developed patches can keep the app a moving target for hackers.
请记住,我可能会忘记其他一些技巧.如果您知道,请发表评论.
结论:
总的来说,这不是最干净的解决方案,因为需要为每个消耗品维护多个并行 SKU,但到目前为止,我还没有想出更好的解决方案.
Overall, this is not the cleanest solution as one needs to maintain multiple parallel SKUs for each consumable product, but so far I haven't come up with a better one that actually fixes the issues.
所以,请分享您可能有的任何其他想法.+1 保证任何好的指针.:)
这篇关于解决 Google Play 的 Billing API v3 中消耗品的 API-purchase-logic-flaws(与使用 API v3 消耗品的所有人相关)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!