在通过 purchases.products.get 验证 Android In App Product 时,productId 被忽略并返回为 null

productId is ignored and returned as null when validating Android In App Product via purchases.products.get

我正在尝试使用 PHP 验证 Google Play/Android 在应用程序产品消费购买服务器端。我收到带有有效收据的回复,但有两个令人困惑的问题:

  1. productId总是null
  2. 如果我将下面示例中的 $productId 更改为无效 ID,它会 return 完全相同的响应。这似乎是任何字符串的情况。

这是我的示例代码:

$purchaseToken = 'TEST purchaseToken FROM ANDROID APP';
$appId = 'com.example.myapp';
$productId = 'com.example.myapp.iap1';

$googleClient = new \Google_Client();
$googleClient->setScopes([\Google_Service_AndroidPublisher::ANDROIDPUBLISHER]);
$googleClient->setApplicationName($appId);
$googleClient->setAuthConfig(__DIR__ . '/gp-service.json');
$googleAndroidPublisher = new \Google_Service_AndroidPublisher($googleClient);
$purchase = $googleAndroidPublisher->purchases_products->get($appId, $productId, $purchaseToken);

如果我转储 $purchase,我得到:

=> Google_Service_AndroidPublisher_ProductPurchase {
     +acknowledgementState: 1,
     +consumptionState: 1,
     +developerPayload: "",
     +kind: "androidpublisher#productPurchase",
     +obfuscatedExternalAccountId: null,
     +obfuscatedExternalProfileId: null,
     +orderId: "GPA.XXXX-XXXX-XXXX-XXXXX",
     +productId: null,
     +purchaseState: 0,
     +purchaseTimeMillis: "1602771022178",
     +purchaseToken: null,
     +purchaseType: 0,
     +quantity: null,
     +regionCode: "US",
   }

有谁知道我在这里做错了什么?它似乎并没有最终验证 productId,也没有向我提供我需要验证它的数据,这意味着我现在无法验证此 IAP。

https://issuetracker.google.com/issues/169870112

我在 google 问题跟踪器上发现忽略 productId 是一种预期行为。我写了 post 询问我们在响应中收到的 null 是否也是预期的行为。我希望不会,因为正如您所写,如果 productId 始终为空,我们现在无法完全验证 IAP。

在看到 Deusald 的回复 并开始输入对 Google 问题票的回复后,我顿悟了:Google 只是在验证交易对您的帐户有效,并希望您验证收据数据 server-side。它们在收据数据中包含一个 base64 编码的 RSA SHA1 签名以及用于创建该签名的原始数据,因此它们为您提供了完成此操作所需的一切。

下面的代码片段完成了 PHP 的验证,但它应该很容易移植到其他语言:

<?php

// our app's bundle id
$appId = 'com.example.myapp';

// the location of a Service Account JSON file for a Service account that has access to the "Financial Data" permissions in the Play Console
$serviceAccountJson = __DIR__ . '/gp-service.json';

// this is the raw receipt data received on the device from Google Play; this example is obfuscated and only shows the keys for sensitive fields
$googlePlayReceipt = '{"productId": "com.example.myapp.iap1","transactionDate": 1602714720893,"transactionReceipt": "","purchaseToken": "","transactionId": "","dataAndroid": "","signatureAndroid": "","isAcknowledgedAndroid": false,"autoRenewingAndroid": false,"purchaseStateAndroid": 1}';
// decode the json to an array we can use
$decodedGooglePlayReceipt = json_decode($googlePlayReceipt, true);

// the data that was signed for verification purposes
$data = $decodedGooglePlayReceipt['transactionReceipt'];
// the signature that was used to sign the $data
$signature = $decodedGooglePlayReceipt['signatureAndroid'];

// The "Base64-encoded RSA public key" for your app, taken from the Google Play Console
// In the Classic Console: Your App -> Development Tools -> Services & APIs -> Licensing & in-app billing
// In the New Console: Your App -> Monetize -> Monetization Setup -> Licensing
$base64EncodedPublicKeyFromGoogle  = '################';

// Convert the key into the normal public key format
// Just need to split the base64 key into 64 character long lines and add the usual prefix/suffix
$openSslFriendlyKey = "-----BEGIN PUBLIC KEY-----\n" . chunk_split($base64EncodedPublicKeyFromGoogle, 64, "\n") .  "-----END PUBLIC KEY-----";
// Convert the key to the openssl key ID that openssl_verify() expects
// I'm unsure if this step would be necessary on all platforms
$publicKeyId = openssl_get_publickey($openSslFriendlyKey);

// Use openssl_verify() to verify the $signature (which has to be base64 decoded!) against the $data using the public key we have
$result = openssl_verify($data, base64_decode($signature), $publicKeyId, OPENSSL_ALGO_SHA1);
if ($result === 1) {
    // receipt data is valid. now let's grab the productId and purchaseToken
    $decodedData = json_decode($data, true);
    $purchasedProductId = decodedData['productId'];
    $purchaseToken = decodedData['purchaseToken'];

    // now we'll verify that the receipt is valid for our account
    try {
        $googleClient = new \Google_Client();
        $googleClient->setScopes([\Google_Service_AndroidPublisher::ANDROIDPUBLISHER]);
        $googleClient->setApplicationName($appId);
        $googleClient->setAuthConfig($serviceAccountJson);
        $googleAndroidPublisher = new \Google_Service_AndroidPublisher($googleClient);
        $purchase = $googleAndroidPublisher->purchases_products->get($appId, $purchasedProductId, $purchaseToken);
    } catch (Throwable $exception) {
        // this means the receipt data is unable to be validated by Google
        throw new Exception('Invalid receipt');
    }
} elseif ($result === 0) {
    throw new Exception('Invalid receipt');
} else {
    throw new Exception(openssl_error_string());
}

$purchaseToken 在 Josh 的回答中未定义。


<?php
$appId = "com.example.myapp";
$serviceAccountJson = __DIR__ . "/gp-service.json";

// this is the raw receipt data received on the device from Google Play; this example is obfuscated and only shows the keys for sensitive fields
$googlePlayReceipt =
    '{"productId": "com.example.myapp.iap1","transactionDate": 1602714720893,"transactionReceipt": "","purchaseToken": "","transactionId": "","dataAndroid": "","signatureAndroid": "","isAcknowledgedAndroid": false,"autoRenewingAndroid": false,"purchaseStateAndroid": 1}';
// decode the json to an array we can use
$decodedGooglePlayReceipt = json_decode($googlePlayReceipt, true);

// the data that was signed for verification purposes
$data = $decodedGooglePlayReceipt["transactionReceipt"];
// the signature that was used to sign the $data
$signature = $decodedGooglePlayReceipt["signatureAndroid"];

// The "Base64-encoded RSA public key" for your app, taken from the Google Play Console
// In the Classic Console: Your App -> Development Tools -> Services & APIs -> Licensing & in-app billing
// In the New Console: Your App -> Monetize -> Monetization Setup -> Licensing
$base64EncodedPublicKeyFromGoogle = "################";

// Convert the key into the normal public key format
// Just need to split the base64 key into 64 character long lines and add the usual prefix/suffix
$openSslFriendlyKey =
    "-----BEGIN PUBLIC KEY-----\n" .
    chunk_split($base64EncodedPublicKeyFromGoogle, 64, "\n") .
    "-----END PUBLIC KEY-----";
// Convert the key to the openssl key ID that openssl_verify() expects
// I'm unsure if this step would be necessary on all platforms
$publicKeyId = openssl_get_publickey($openSslFriendlyKey);

// Use openssl_verify() to verify the $signature (which has to be base64 decoded!) against the $data using the public key we have
$result = openssl_verify(
    $data,
    base64_decode($signature),
    $publicKeyId,
    OPENSSL_ALGO_SHA1
);

if ($result === 0) {
    throw new Exception("Invalid receipt");
}

if ($result !== 1) {
    throw new Exception(openssl_error_string());
}

// receipt data is valid. now let's grab the productId and purchaseToken
$decodedData = json_decode($data, true);
$purchasedProductId = decodedData["productId"];
$purchaseToken = decodedData["purchaseToken"];

// now we'll verify that the receipt is valid for our account
try {
    $googleClient = new \Google_Client();
    $googleClient->setScopes([
        \Google_Service_AndroidPublisher::ANDROIDPUBLISHER,
    ]);
    $googleClient->setApplicationName($appId);
    $googleClient->setAuthConfig($serviceAccountJson);
    $googleAndroidPublisher = new \Google_Service_AndroidPublisher(
        $googleClient
    );
    $purchase = $googleAndroidPublisher->purchases_products->get(
        $appId,
        $purchasedProductId,
        $purchaseToken
    );
} catch (Throwable $exception) {
    // this means the receipt data is unable to be validated by Google
    throw new Exception("Invalid receipt");
}