如何在服务器端验证对Android应用的购买(应用结算v3中的Google Play)


96

我有一个简单的应用程序(需要用户使用帐户登录)。我为付费用户提供了一些高级功能,例如更多新闻内容。

我需要记录用户是否在我的服务器数据库中购买了该物品。当我向用户的设备提供数据内容时,我便可以检查用户的状态,并为付费用户提供不同的内容。

我检查了Google提供的Trivialdrive官方示例,它没有提供用于服务器端验证的任何示例代码,这是我的问题。

  1. 我发现该示例使用我的应用程序的公钥来验证购买,看起来效果不佳,我认为我可以将验证过程与用户登录凭据一起移至我的服务器,以查看用户购买是否完成,然后更新数据库。
  2. 还有一些我可以用来查询的购买API,我需要将用户的PurchaseToken传递到服务器中。

我不确定应该采用哪种方法来验证用户的购买并在数据库中标记用户的状态,也许两者都可以?

而且,恐怕情况会发生,如果用户从google play购买了此商品,但由于某种原因,就在那个时候,当我的应用启动对我的服务器的验证时,网络连接已关闭或我自己的服务器已关闭,用户只是通过Google Play支付了钱,但是我没有在服务器中记录购买的金额?我该怎么办,我该如何处理这种情况。


您可能应该从此问题中删除ios标志。
古斯塔沃·格瓦拉

Answers:


161

听起来您正在寻找一种检查用户的帐户中是否启用了高级功能的方法,所以这就是我的起点。

确保数据库上有某种标记,指示用户是否具有高级功能,并在请求帐户信息时将其包括在API响应有效负载中。该标志将是您对“高级功能”的主要授权。

当用户进行应用内购买时,请在客户端(即应用)上本地缓存详细信息(令牌,订单ID和产品ID),然后将其发送到您的API。

然后,您的API应该将发送purchaseTokenGoogle Play开发者API进行验证。

从这里可能会发生一些事情:

  1. 收据有效,您的API会以200 Ok状态代码响应客户端
  2. 收据无效,您的API会以400错误的请求状态代码响应客户端
  3. Google Play API已关闭,您的API响应502错误的网关状态代码

对于1或2(状态代码为2xx或4xx),您的客户端会清除购买明细的缓存,因为它不再需要它,因为API指示已收到。

验证成功后(情况1),应premium为用户将标志设置为true。

对于3(5xx状态代码)或网络超时的情况,客户端应继续尝试直到从API收到2xx或4xx状态代码。

根据您的要求,您可以让它等待几秒钟再发送一次,或者在再次启动该应用程序时将详细信息发送到您的API,或者如果应用程序缓存中存在购买详细信息,则将其发送到后台。

此方法应注意网络超时,服务器不可用等情况。

现在您需要考虑一些问题:

购买后应立即发生什么?该应用程序应该在提供优质内容之前等待验证成功,还是应该暂时授予访问权限,如果验证失败将其删除?

暂时授予高级功能访问权限可简化大多数用户的流程,但是在API验证时,您还将授予许多欺诈性用户访问权限purchaseToken

换句话说:购买在被证明是欺诈之前是有效的;或者 欺诈直到证明有效?

为了确定用户的订阅期即将更新时,是否仍具有有效的订阅,您需要安排对进行重新验证,purchaseToken以在expiryTimeMillis返回结果时运行

如果expiryTimeMillis过去,则可以将premium标志设置为false。如果是将来,请重新安排新的时间expiryTimeMillis

最后,为了确保用户具有(或没有)高级访问权限,您的应用应向API查询有关应用启动或背景不完整的用户详细信息。


对于付费应用程序,我将如何从Google获得收据?
Merbin Joe

2
嗨!无法通过Google访问订阅历史记录?如果应用在存储purchaseToken时崩溃了,该如何防止已经使用的已购买cuubscription的事实丢失?
scythargon

2
我有一个类似的问题..而不是让应用程序将令牌发送到api会更可靠地指示Google开发人员服务器直接向我的api发送推送通知来这样做吗?
Gianluca Ghettini '17

对于已取消的订阅,如果使用相同的旧购买令牌进行验证,则Google Play开发者API在取消后仍将返回200。
Cezar Cobuz

因此,对于订阅,您建议在服务器上进行首次呼叫后,我们存储购买令牌和产品ID,并在expiryTimeMillis发生时安排另一个验证呼叫(重新运行相同的请求)?这就是我们应该如何验证订阅有效性的方法吗?Android是否有有关如何操作的指南?苹果公司获得了有关它的WWDC视频,很清楚地解释了它的良好实践,但是对Play Store知之甚少。
schankam

28

与此相关的文档使这些几乎无关紧要的事情变得令人困惑和奇怪,同时却使实际上重要的文档几乎没有链接,而且很难找到。这在可以运行google api客户端库(包括Java,Python,.Net和NodeJS等)的最流行的服务器平台上应该能很好地工作。注意:我仅测试了Python api客户端,如下所示。

必要步骤:

  1. 通过Google Play控制台中的“ API访问”链接创建一个API项目

  2. 新建一个服务帐户,保存生成的JSON私钥。您需要将此文件带到服务器。

  3. 在Play控制台的服务帐户部分中按完成以刷新,然后授予对该服务帐户的访问权限

  4. https://developers.google.com/api-client-library获取适用于您的服务器平台的google api客户端库

  5. 使用特定平台的客户端库来构建服务接口,并直接读取购买验证的结果。

不会需要授权范围打扰,使得定制请求调用,刷新访问令牌等的API客户端库完成所有的操作。这是一个验证订阅的python库用法示例:

首先,像这样在您的pipenv中安装google api客户端:

$ pipenv install google-api-python-client

然后,您可以使用私钥json文件设置api客户端凭据,以对服务帐户进行身份验证。

credentials = service_account.Credentials.from_service_account_file("service_account.json")

现在,您可以直接使用该库来验证订购购买或产品购买。

#Build the "service" interface to the API you want
service = googleapiclient.discovery.build("androidpublisher", "v3", credentials=credentials)

#Use the token your API got from the app to verify the purchase
result = service.purchases().subscriptions().get(packageName="your.app.package.id", subscriptionId="sku.name", token="token-from-app").execute()
#result is a python object that looks like this ->
# {'kind': 'androidpublisher#subscriptionPurchase', 'startTimeMillis': '1534326259450', 'expiryTimeMillis': '1534328356187', 'autoRenewing': False, 'priceCurrencyCode': 'INR', 'priceAmountMicros': '70000000', 'countryCode': 'IN', 'developerPayload': '', 'cancelReason': 1, 'orderId': 'GPA.1234-4567-1234-1234..5', 'purchaseType': 0}

Play开发人员API的平台服务接口的文档未以易于查找的方式链接在一起,因为有些链接很难找到。这是我发现的流行平台的链接:

Python | Java | .NET | PHP | NodeJS(Github TS) | 转到(Github JSON)


5
同意,文档太可怕了……关于如何使用Firebase(Firestore)和Cloud作为后端的任何想法?
杰夫·帕吉特

如果您的Cloud函数在NodeJS中,那么您也许可以使用上面的NodeJS链接来使API客户端库正常工作?
Dhiraj Gupta '18

17

使用适用于PHP的Google API客户端库的完整示例:

  1. 按照Marc的答案https://stackoverflow.com/a/35138885/1046909所述,设置您的Google Project并访问您的服务帐户的Google Play

  2. 安装库:https : //developers.google.com/api-client-library/php/start/installation

  3. 现在,您可以通过以下方式验证收据:

    $client = new \Google_Client();
    $client->setAuthConfig('/path/to/service/account/credentials.json');
    $client->addScope('https://www.googleapis.com/auth/androidpublisher');
    $service = new \Google_Service_AndroidPublisher($client);
    $purchase = $service->purchases_subscriptions->get($packageName, $productId, $token);

    之后,$ purchase是Google_Service_AndroidPublisher_SubscriptionPurchase的实例

    $purchase->getAutoRenewing();
    $purchase->getCancelReason();
    ...

这不起作用,我不断收到(401)需要登录并且setAuthConfig不接受服务帐户凭据json
Raulnd

这个为我工作了putenv('GOOGLE_APPLICATION_CREDENTIALS = credentials.json'); $ client = new Google_Client(); $ client-> useApplicationDefaultCredentials(); $ client-> addScope(' googleapis.com/auth/androidpublisher'); $ service =新的Google_Service_AndroidPublisher($ client); $ purchase = $ service-> purchases_products-> get($ packageName,$ productId,$ token); var_dump($ purchase);
Raulnd

这是在Inapp结算的情况下。如果每当用户从Play商店而不是inapp购买我的应用程序时,我想将orderId获取到数据库怎么办?
Ankesh kumar Jaisansaria

stackoverflow.com/questions/48662787/… 请参阅此问题。我正在寻找这个问题的答案。这也有积极的赏金
Ankesh kumar Jaisansaria '18

@MingalevME如果令牌格式无效并且PHP出现致命错误,该怎么办?
alexx0186 '18

12

您可以尝试使用Purchases.subscriptions:获取服务器端。它需要packageName,subscriptionId和token作为参数,并需要授权

检查用户的订购购买是否有效,并返回其到期时间。

如果成功,此方法将在响应正文中返回Purchases.subscriptions资源


9
我遇到了严重的问题,无法正常使用授权。

8
说真的 对于某些应用程序的关键购买程度,支持和文档是疯狂的。这是您需要在服务器上执行的操作:github.com/google/…。此处的更多信息:stackoverflow.com/questions/35127086/…–
用户

0

我回答这个问题

网络连接已关闭或我自己的服务器已关闭,用户只是在google play中付款,但我没有在服务器中记录购买金额?我该怎么办,我该如何处理这种情况。

情况是:

用户使用Google Play服务购买了“ abc”商品->返回确定->由于某些原因(例如没有互联网连接)无法与服务器验证。

解决方案是:

在客户端,在显示“ Google电子钱包”按钮之前,请检查“ abc”项目是否已拥有。

  • 如果是,请再次与服务器验证
  • 如果没有,请显示“ Google电子钱包”按钮。

购买购买= mInventory.getPurchase('abc');

if (purchase != null) // Verify with server 

else // show Google Wallet button

https://developer.android.com/google/play/billing/billing_reference.html#getSkuDetails


4
我不明白为什么在服务器上进行验证比在应用程序上进行验证更安全。归根结底,是由应用程序解锁的功能,因此仍然可以删除或反转应用程序中检查服务器响应是否“正常”的代码
Gianluca Ghettini

2
@GianlucaGhettini因为有时服务器是提供购买的服务的应用程序,而不是应用程序,所以该应用程序可能会被反向工程,然后经过一些努力,验证过程可能会被黑客入侵。
Mohyaddin Alaoddin
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.