如何在服务器端验证Android应用程序的购买(谷歌在应用程序开发v3中播放)

时间:2015-11-22 02:07:53

标签: android in-app-purchase in-app-billing

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

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

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

  1. 我发现样本使用我的应用程序的公钥来验证购买,看起来不太好,我想我可以将验证过程移到我的服务器并结合用户登录凭据来查看用户购买是否完成,然后更新数据库。
  2. 还有purchase API我可以用来查询,我需要的是将用户的purchaseToken传递给服务器。
  3. 我不确定我应该采用什么方法来验证用户的购买,并在我的数据库中标记用户的状态,可能两者都有?

    我担心有一种情况,如果用户从Google Play购买此商品,但出于某种原因,就在那个时候,当我的应用启动验证到我的服务器时,网络连接中断或我自己的服务器已关闭,用户只是在谷歌播放中支付了钱,但我没有在我的服务器上记录购买?我该怎么办,我该如何处理这种情况。

7 个答案:

答案 0 :(得分:114)

听起来你正在寻找的是一种检查用户是否在其帐户中启用了高级功能的方法,所以这就是我要开始的地方;

确保数据库中存在某种标记,指示用户是否具有高级功能,并在请求帐户信息时将其包含在API响应有效内容中。此标志将是您“高级功能”的主要权限。

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

然后,您的API应将purchaseToken发送到Google Play Developer API进行验证。

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

  1. 收据有效,您的API以200 Ok状态代码
  2. 响应客户端
  3. 收据无效,您的API使用400 Bad Request状态代码
  4. 响应客户端
  5. Google Play API已关闭,您的API会以502 Bad Gateway状态代码进行响应
  6. 在1.或2.(2xx或4xx状态代码)的情况下,您的客户清除购买详细信息的缓存,因为它不再需要它,因为API已表明已收到。

    验证成功后(案例1),您应该为用户将premium标志设置为true。

    如果是3.(5xx状态代码)或网络超时,客户端应继续尝试,直到它从您的API收到2xx或4xx状态代码。

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

    这种方法应该处理网络超时,服务器不可用等等。

    现在需要考虑几个问题:

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

    授予对高级功能的暂时访问权限可以为大多数用户提供流程,但是当您的API验证purchaseToken时,您也会授予对多个欺诈用户的访问权限。

    以另一种方式说明:购买有效,直至证明有欺诈行为或;欺诈直到证明有效?

    为了确定用户在续订订阅期限时是否仍然有有效订阅,您需要在purchaseToken上安排重新验证,以便在expiryTimeMillis处运行已在result返回。

    如果过去expiryTimeMillis,您可以将premium标记设置为false。如果是将来,请再次为新expiryTimeMillis重新安排。

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

答案 1 :(得分:14)

使用Google API Client Library for PHP的完整示例:

  1. 设置您的 Google Project 并访问 Google Play 以获取服务帐户,如Marc的回答所述https://stackoverflow.com/a/35138885/1046909

  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);
    

    此次购买后是 Google_Service_AndroidPublisher_SubscriptionPurchase

    的实例
    $purchase->getAutoRenewing();
    $purchase->getCancelReason();
    ...
    

答案 2 :(得分:12)

您可以尝试使用Purchases.subscriptions: get服务器端。它将packageName,subscriptionId和token作为参数,需要authorization

  

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

如果成功,此方法会在响应正文中返回Purchases.subscriptions resource

答案 3 :(得分:7)

与此相关的文档非常混乱和奇怪,几乎没有什么意义,而实际上重要的文档却几乎没有链接,而且很难找到。这在可以运行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

  5. 获取适用于您的服务器平台的google api客户端库
  6. 使用您特定平台的客户端库来构建服务界面,并直接读取购买验证的结果。

不需要不需要麻烦授权范围,进行自定义请求调用,刷新访问令牌等。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) | Go (Github JSON)

答案 4 :(得分:0)

马克·格林斯托克(Marc Greenstock)的答案无疑是令人鼓舞的,需要注意一些事项,尽管这花了我很长时间(至少比我预期的要多得多):

  1. 我必须在“服务帐户”设置上选中“启用G Suite域范围委派”。没有这个,我不断收到此错误:“当前用户没有足够的权限来执行请求的操作” Image with Enable G Suite Domain-wide Delegation option checked

  2. 出于测试目的,您可以为服务帐户here创建JWT令牌,只是不要忘记选择RS256算法。

  3. 公用密钥是您下载的JSON文件中的“ private_key_id”。它还具有以下格式:

    -----开始公钥----- {private_key_id} -----结束公钥-----

  4. 私钥是下载的JSON文件中的“ private_key”

  5. here中描述了JWT生成所需的声明。

  6. 对于JWT令牌到底是什么以及如何组装感到困惑?不要感到羞耻,请检查this link。奇怪的是,您就像我一样,花了很长时间才去寻找它到底是什么,它比看起来要简单得多。

答案 5 :(得分:0)

我在使用建议的 google API python 库时遇到了一些严重的问题,但从头开始实现通信并不难。 首先,您必须按照所有答案中的描述在 Google Play Console 创建一个服务帐户,并获取包含私钥的 JSON 文件。将其保存到您的服务器。 然后使用下面的代码。无需获取 google API 客户端库。您只需要以下(非常常见的)python 库 RequestsPycrypto

    import requests
    import datetime
    import json
    import base64
    from Crypto.Signature import PKCS1_v1_5 as Signature_pkcs1_v1_5
    from Crypto.Hash import SHA256 
    from Crypto.PublicKey import RSA

    jwtheader64 = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
    #SERVICE_ACCOUNT_FILE: full path to the json key file obtained from google
    with open(SERVICE_ACCOUNT_FILE) as json_file:
        authinfo = json.load(json_file)

    packageName = #your package name
    product = #your inapp id
    token = #your purchase token
    
    #create the JWT to use for authentication
    now = datetime.datetime.now()
    now1970 = (now - datetime.datetime(1970,1,1)).total_seconds()
    jwtclaim = {"iss":authinfo["client_email"],"scope":"https://www.googleapis.com/auth/androidpublisher","aud": "https://oauth2.googleapis.com/token","iat":now1970,"exp":now1970+1800,"sub":authinfo["client_email"]}
    jwtclaimstring = json.dumps(jwtclaim).encode(encoding='UTF-8')
    jwtclaim64 = base64.urlsafe_b64encode(jwtclaimstring).decode(encoding='UTF-8')
    tosign = (jwtheader64+"."+jwtclaim64).encode(encoding='UTF-8')

    #sign it with your private key
    private = authinfo["private_key"].encode(encoding='UTF-8')
    signingkey = RSA.importKey(private)
    signer = Signature_pkcs1_v1_5.new(signingkey)
    digest = SHA256.new()
    digest.update(tosign)
    signature = signer.sign(digest)
    res = base64.urlsafe_b64encode(signature).decode(encoding='UTF-8')

    #send it to Google authentication server to obtain your access token
    headers = {'Content-Type': 'mapplication/x-www-form-urlencoded'}
    payload = "grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion="+jwtheader64+"."+jwtclaim64+"."+res
    r = requests.post("https://oauth2.googleapis.com/token",headers=headers,data=payload)
    if r.status_code == 200:
        authdata = json.loads(r.text)
        accesstoken = authdata['access_token']
        bearerheader = {'Authorization':'Bearer '+authdata['access_token']}
        #Now you have at last your authentication token, so you can use it to make calls. In this example we want to verify a subscription
        url = "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/"+packageName+"/purchases/subscriptions/"+product+"/tokens/"+token
        subscription = requests.get(url,headers=bearerheader)

答案 6 :(得分:-1)

我回答这个问题

  

网络连接断开或我自己的服务器关闭,用户只是   在谷歌播放支付了钱,但我没有记录我的购买   服务器?我该怎么办,我该如何处理这种情况。

情况是:

用户使用Google Play服务购买'abc'项目​​ - >返回OK - >由于某些原因(例如没有Internet连接)而无法与服务器进行验证。

解决方案是:

在客户端,在显示“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