Google Play In-app billing 版本 3 购买的服务器端验证
Posted
技术标签:
【中文标题】Google Play In-app billing 版本 3 购买的服务器端验证【英文标题】:Server-side verification of Google Play In-app billing version 3 purchase 【发布时间】:2013-04-10 15:17:06 【问题描述】:在向用户提供可下载内容之前,我无法找到关于如何在服务器上验证应用内结算购买的直接答案。
我在应用计费版本 3 中使用。我使用基于 TrivialDrive 示例代码中的 IabHelper 类的代码购买托管产品。一切都很好,购买成功完成,我得到了一个完整的购买对象和以下原始 JSON 数据:
"orderId":"12999763169054705758.1364365967744519",
"packageName":"my package name",
"productId":"77",
"purchaseTime":1366217534000,
"purchaseState":0,
"purchaseToken":"utfwimslnrrwvglktizikdcd.AO-J1OwZ4l5oXz_3d2SAWAAUgFE3QErKoyIX8WuSEnBW26ntsyDmlLgoUd5lshqIY2p2LnlV4tpH4NITB4mJMX98sCtZizH7wGf6Izw3tfW_GflJDKFyb-g"
据我了解,我需要将 purchaseToken 和我看到的称为签名的东西传递给服务器。然后服务器使用私钥来验证购买。它是否正确?如果是这样,我从哪里获得签名?真的没有关于服务器端购买验证的像样文档吗?
【问题讨论】:
当您在社区中提出一些问题时,请始终记住一件事,不要放置敏感数据。敏感一词是指密码、任何交易的订单号等。只需用一些虚拟值更改它们即可。 您是否找到任何用于服务器端验证的工作示例? 如何使用和提取google上面的json数据?我想看看这方面的实际 php 代码。 使用公钥而不是私钥完成签名验证 【参考方案1】:1.用composer安装谷歌客户端PHP库
composer require google/apiclient:"^2.0"
-
创建服务帐户,它会为您提供 json 并保存为 credentials.json
开始代码
require $_SERVER['DOCUMENT_ROOT'] . '/client/vendor/autoload.php';
$packageName='Your Package name'; //com.example.blahblah
$productId='your_product_ID';
$token='Purchase_Token_Form_Payment';
$client = new \Google_Client();
$client->setAuthConfig('credentials.json');
$client->addScope('https://www.googleapis.com/auth/androidpublisher');
$service = new \Google_Service_AndroidPublisher($client);
$purchase = $service->purchases_products->get($packageName, $productId, $token);
//echo $purchase['purchaseState'];
//echo '<br>';
echo json_encode($purchase);
结果会是这样的
"acknowledgementState":1,"consumptionState":1,"developerPayload":"","kind":"androidpublisher#productPurchase","orderId":"GPA.3342-8146-5668-57982","productId":null,"purchaseState":0,"purchaseTimeMillis":"1586978561493","purchaseToken":null,"purchaseType":0,"quantity":null
【讨论】:
您好,我喜欢使用官方库来处理身份验证的方法。问题:服务帐户的外观如何,即需要授予哪些权限以及在哪里?我以为我在 Google 文档中看到过,但找不到了……非常感谢!【参考方案2】:这是一个非常古老的问题,但我认为它仍然相关。
我的小贡献。购买对象扩展了一个新参数“已确认”,现在它看起来:
"orderId":"12999763169054705758.1364365967744519",
"packageName":"my package name",
"productId":"77",
"purchaseTime":1366217534000,
"purchaseState":0,
"purchaseToken":"utfwimslnrrwvglktizikdcd.AO-J1OwZ4l5oXz_3d2SAWAAUgFE3QErKoyIX8WuSEnBW26ntsyDmlLgoUd5lshqIY2p2LnlV4tpH4NITB4mJMX98sCtZizH7wGf6Izw3tfW_GflJDKFyb-g",
"acknowledged":true
所以在检查签名时要小心添加这个额外的参数。
【讨论】:
不包含autoRenewing字段吗? @KishanVaishnav 可能只是为了订阅而不是一次性产品?【参考方案3】:我对减少应用内购买欺诈的小小贡献
在您的 Android 代码上的外部服务器上进行签名验证:
verifySignatureOnServer()
private boolean verifySignatureOnServer(String data, String signature)
String retFromServer = "";
URL url;
HttpsURLConnection urlConnection = null;
try
String urlStr = "https://www.example.com/verify.php?data=" + URLEncoder.encode(data, "UTF-8") + "&signature=" + URLEncoder.encode(signature, "UTF-8");
url = new URL(urlStr);
urlConnection = (HttpsURLConnection) url.openConnection();
InputStream in = urlConnection.getInputStream();
InputStreamReader inRead = new InputStreamReader(in);
retFromServer = convertStreamToString(inRead);
catch (IOException e)
e.printStackTrace();
finally
if (urlConnection != null)
urlConnection.disconnect();
return retFromServer.equals("good");
convertStreamToString()
private static String convertStreamToString(java.io.InputStreamReader is)
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
verify.php在虚拟主机的根目录
<?php
// get data param
$data = $_GET['data'];
// get signature param
$signature = $_GET['signature'];
// get key
$key_64 = ".... put here the base64 encoded pub key from google play console , all in one row !! ....";
$key = "-----BEGIN PUBLIC KEY-----\n".
chunk_split($key_64, 64,"\n").
'-----END PUBLIC KEY-----';
//using PHP to create an RSA key
$key = openssl_get_publickey($key);
// state whether signature is okay or not
$ok = openssl_verify($data, base64_decode($signature), $key, OPENSSL_ALGO_SHA1);
if ($ok == 1)
echo "good";
elseif ($ok == 0)
echo "bad";
else
die ("fault, error checking signature");
// free the key from memory
openssl_free_key($key);
?>
注意事项:
您应该在您的 java 代码中加密 URL,如果没有,可以在您的解压缩应用程序 apk 中通过简单的文本搜索轻松找到该 URL
最好将 php 文件名、url 参数、好/坏响应更改为毫无意义的内容。
verifySignatureOnServer() 应该在一个单独的线程中运行,如果不会抛出主线程上的网络异常。
希望对你有所帮助...
【讨论】:
什么是String data, String signature
?
getPurchases() 返回一个拥有物品的捆绑包,数据和签名分别是捆绑包上“INAPP_PURCHASE_DATA_LIST”键和“INAPP_DATA_SIGNATURE_LIST”键的成员
所以我们应该只验证它onActivityResult
? String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA"); String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
p.s.最后没有_LIST,但我想还是一样吧?
是的,你是对的,onActivityResult你得到了最后一次购买数据&签名,只有一个。在 getPurchases() 上,您会获得所有已购买商品的列表,在这种情况下,应该对每件商品进行验证。但是由于主UI线程无法访问网络并且无法调用外部服务器,onActivityResult存在问题,我所做的是在AsyncTask中执行
您是否每次都验证已购买的商品?我的意思是你总是需要互联网连接【参考方案4】:
where do I get the signature from ?
看看official docs,
它说在您的onActivityResult()
方法中,您可以获得以下数据,如示例所示,
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
if (requestCode == 1001)
int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");//this is the signature which you want
if (resultCode == RESULT_OK)
try
JSONObject jo = new JSONObject(purchaseData);//this is the JSONObject which you have included in Your Question right now
String sku = jo.getString("productId");
String purchaseToken = jo.getString("purchaseToken");
//you need to send sku and purchaseToken to server for verification
catch (JSONException e)
alert("Failed to parse purchase data.");
e.printStackTrace();
用于服务器端验证, 看看official docs
如前所述,客户端应用程序会将sku
和purchaseToken
发送到服务器API。服务器必须接收这些值,并且必须使用 android publish api 执行检查以验证购买:
服务器可以通过添加必要的参数来调用以下GET请求:
https://www.googleapis.com/androidpublisher/v2/applications/packageName/purchases/products/productId/tokens/token
这里, packageName = 客户端应用程序的 packageName productId = 从客户端应用收到的 sku token = 从客户端应用收到的 purchaseToken
这将导致JSONObject
响应格式:
"kind": "androidpublisher#productPurchase",
"purchaseTimeMillis": long,
"purchaseState": integer,
"consumptionState": integer,
"developerPayload": string,
"orderId": string,
"purchaseType": integer
这里,purchaseState = 0 表示有效购买
希望对你有帮助!!
【讨论】:
我不敢相信我忽略了这一点。 RTFM的教科书案例。谢谢! 那么如何将上面的 json 数据传递给我的 php 服务器代码以及如何从那里使用(验证)它? 这不是服务器端验证 你拯救了我的一天:),谢谢。对于无法验证服务器端的人,请与我联系。我在 php 中成功验证了它的服务器端。 @KrunalPanchal 你能说说你是如何实现服务器端验证的吗?【参考方案5】:这个问题太老了,但我希望我的回答可以帮助别人。
您必须在客户端验证签名,然后您必须将purchaseToken
传递给服务器端,然后服务器将联系Google的服务器 并获取有关购买的所有必要信息,例如purchaseState
和consumptionState
。
https://developers.google.com/android-publisher/api-ref/purchases/products
【讨论】:
这是部分正确的。 Google 实际上建议尽可能在服务器端执行签名验证。这样您就不会在 android 应用中公开您的公钥。 如何在客户端验证签名。 @Eran 你能在服务器端签名验证上发布一个指向谷歌文档的链接吗?以上是关于Google Play In-app billing 版本 3 购买的服务器端验证的主要内容,如果未能解决你的问题,请参考以下文章
SDK接入之Android Google Play内支付(in-app Billing)接入
Google Play In-app billing 版本 3 购买的服务器端验证
Google play billing(Google play 内支付)
在 Google In-App Billing API 中使用啥作为开发人员有效负载?
Google In-App billing, IllegalArgumentException: Service Intent must be explicit, after upgrade to A