如何验证购买安卓应用程序在服务器端(谷歌播放在应用程序收费 v3)

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

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

我检查了由谷歌提供的官方 Trivialdrive 样本,它没有提供任何样本代码的服务器端验证,这里是我的问题。

  1. 我发现样本使用我的应用程序的公开密钥里面验证购买,它看起来不好,我想我可以只移动验证过程到我的服务器结合用户登录凭证,看看用户购买是否完成,然后更新数据库。
  2. 还有 购买空气污染指数我可以用来查询,我需要的是传递用户的购买令牌到服务器。

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

恐怕有这样一种情况,如果一个用户从 google play 购买了这个商品,但是出于某种原因,就在那个时候,当我的应用程序启动验证到我的服务器 网络连接中断或者我自己的服务器中断时,用户刚刚在 google play 中付了钱,但是我没有在我的服务器中记录购买?我该怎么办,我该怎么处理这种情况。

125338 次浏览

您可以尝试使用 采购订阅: 获取服务器端。它使用 packageName、 subscriptionId 和 Token 作为参数,并要求使用 授权

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

如果成功,此方法将在响应主体中返回 订阅资源

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

确保数据库中有某种标志,指示用户是否具有高级特性,并在请求帐户信息时在 API 响应有效负载中包含这些特性。这个标志将是你的“优质功能”的主要权威。

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

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

接下来可能会发生一些事情:

  1. 收据是有效的,您的 API 响应客户端一个200 OK 状态码
  2. 收据无效,您的 API 用400个错误请求状态代码响应客户端
  3. GooglePlayAPI 已关闭,您的 API 响应一个502Bad 网关状态代码

在1的情况下。或2。(2xx 或4xx 状态码)您的客户端清除缓存的购买细节,因为它不再需要它,因为 API 已经表明它已经收到。

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

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

根据您的需求,您可以让它等待几秒钟后再次发送或只是发送细节到您的 API 时,应用程序再次启动或出来的后台,如果购买细节是在应用程序缓存。

这种方法应该考虑网络超时、服务器不可用等问题。

现在有几个问题你需要考虑:

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

对于大多数用户来说,授予对高级功能的暂时访问权限可以使这个过程变得平滑,但是当你的 API 验证 purchaseToken时,你也将授予对一些欺诈用户的访问权限。

换句话说: 购买是有效的,直到证明欺诈或; 欺诈,直到证明有效?

为了确定用户在续订期间是否仍然拥有有效的订阅,您需要安排对 purchaseToken进行重新验证,以便在 结果返回的 expiryTimeMillis上运行。

如果 expiryTimeMillis是过去的,您可以将 premium标志设置为 false。如果是在未来,重新安排它的新 expiryTimeMillis

最后,为了确保用户有额外的访问权限(或者没有) ,你的应用程序应该在应用程序启动时或者从后台出来时查询用户的详细信息。

我来解决这个问题

网络连接中断或者我自己的服务器中断,用户只是 在谷歌游戏支付的钱,但我没有记录在我的购买 服务器? 我该怎么办,我该怎么处理这种情况。

情况是:

用户购买“ abc”项目使用谷歌播放服务-> 返回确定-> 未能与服务器验证一些原因,如没有互联网连接。

解决办法是:

在客户端,在显示“ GoogleWallet”按钮之前,检查“ abc”项是否已经拥有。

  • 如果是,请再次与服务器确认
  • 如果没有,显示“谷歌钱包”按钮。

购买购买 = mInventory.getbuy (‘ abc’) ;

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


else // show Google Wallet button

Https://developer.android.com/google/play/billing/billing_reference.html#getskudetails

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

  1. 设置您的 谷歌项目和访问 Google Play为您的 服务帐户,如马克的答案在这里 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);
    

    之后 $buy 是 Google _ Service _ AndroidPublisher _ SubscriptionPurchsGoogle _ Service _ AndroidPublisher _ SubscriptionPurchsGoogle _ Service _ AndroidPublisher _ SubscriptionPurchsGoogle _的实例

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

这方面的文档令人困惑,而且奇怪地冗长,其中的内容几乎是无关紧要的,而实际上重要的文档几乎没有链接,超级难以找到。这应该可以在最流行的服务器平台上很好地运行 google api 客户端库,包括 Java,Python,。Net 和 NodeJS 等等。注意: 我只测试了 Pythonapi 客户机,如下所示。

必要步骤:

  1. 从 GooglePlay 控制台中的 API Access 链接创建一个 API 项目

  2. 创建一个新的服务帐户,拯救是生成的 JSON 私钥。你需要把这个文件带到你的服务器上。

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

  4. 为你的服务器平台获取一个谷歌应用程序接口(api)客户端库(client library) ,请访问 https://developers.google.com/api-client-library

  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}

用于游戏开发者 API 的平台服务接口的文档没有以一种容易找到的方式链接起来,对于一些人来说,它完全是 很难找到。下面是我发现的流行平台的链接:

Python = “ https://Github.com/google/google-api-Go-client/blob/master/android 出版商/v3/android 出版商-api.JSON”rel = “ noReferrer”> Go (Github JSON)

马克•格林斯托克(Marc Greenstock)的回答绝对具有启发性,有几点需要注意,尽管我花了很长时间才弄明白(至少比我预期的时间长得多) :

  1. 我必须在服务帐户设置中检查“启用 G 套件域范围委托”。如果没有这个,我会不断得到这个错误: “当前用户没有足够的权限来执行请求的操作” 选中启用 G 套件域范围委托选项的图像

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

  3. 公钥是您下载的 JSON 文件中的“ private _ key _ id”,它也有以下格式:

    ——开始公钥—— { private _ key _ id } ——结束公钥——

  4. 私钥是下载的 JSON 文件中的“ private _ key”

  5. JWT 生成所需的索赔描述为 给你

  6. 对 JWT 令牌到底是什么以及如何组装感到困惑?不要害羞,检查 这个链接。很可能你就像我一样,花了很长时间去寻找它到底是什么,它比看起来要简单得多。

我在使用建议的 google API python 库时遇到了一些严重的问题,但是从头开始实现通信并没有那么困难。 首先,您必须按照所有答案中的描述,在 谷歌播放控制台上创建一个服务帐户,并获取包含私钥的 JSON 文件。保存到服务器上。 然后使用以下代码。不需要获得谷歌 API 客户端库。您只需要以下(非常常见) Python 库 请求和 < a href = “ https://pypi.org/project/Pycrypto/”rel = “ nofollow noReferrer”> Pycrypto

    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)

网络连接中断或者我自己的服务器中断,

你没必要这么想。 客户端知道自己的消费产品。所以,客户端可以发送所有令牌回服务器。

只需用生产 ID 和事务 ID 重新检查标记。 服务器检查消费产品。

  1. 如果你失败检查
  2. MakUI 按钮客户端可以重新发送令牌。
  3. 服务器对项目的重新检查令牌。
  4. 结束了。