無法從 Google Play Android Developer API 獲取訂閱資訊



我正在尝试使用适用于Java的Google APIs Client Library来获取有关在我的Android应用程序中购买的用户订阅的信息。这是我现在的做法:

HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
                    .setJsonFactory(JSON_FACTORY)
                    .setServiceAccountId(GOOGLE_CLIENT_MAIL)
                    .setServiceAccountScopes("https://www.googleapis.com/auth/androidpublisher")
                    .setServiceAccountPrivateKeyFromP12File(new File(GOOGLE_KEY_FILE_PATH))
                    .build();
Androidpublisher publisher = new Androidpublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).
                    setApplicationName(GOOGLE_PRODUCT_NAME).
                    build();
Androidpublisher.Purchases purchases = publisher.purchases();
Get get = purchases.get("XXXXX", subscriptionId, token);
SubscriptionPurchase subscripcion = get.execute(); //Exception returned here

GOOGLE_CLIENT_MAIL 是来自 Google 控制台的 API Access 的电子邮件地址。 GOOGLE_KEY_FILE_PATH是从 API Access 下载的 p12 文件。
GOOGLE_PRODUCT_NAME是品牌信息中的产品名称。
在Google APIS控制台中,启用了"Google Play Android Developer API"服务。

我得到的是:

{
  "code" : 401,
  "errors" : [ {
    "domain" : "androidpublisher",
    "message" : "This developer account does not own the application.",
    "reason" : "developerDoesNotOwnApplication"
  } ],
  "message" : "This developer account does not own the application."
}

我非常感谢您对此问题的帮助...

我让它工作了!我遵循的步骤:

先决条件

在开始之前,我们需要生成一个刷新令牌。为此,我们首先必须创建一个 API 控制台项目:

  1. 转到 API 控制台并与您的安卓开发人员一起登录帐户(与安卓开发者控制台中用于上传 APK 的帐户相同)。
  2. 选择"创建项目"。
  3. 转到左侧导航面板中的服务。
  4. 打开 Google Play Android Developer API
  5. 接受服务条款。
  6. 转到左侧导航面板中的 API 访问。
  7. 选择创建 OAuth 2.0 客户端 ID:
    • 在第一页上,您需要填写产品名称,但是徽标不是必需的。
    • 在第二页上,选择"Web 应用程序"并设置重定向 URI和Javascript的起源。我们稍后将用它重定向 URI。
  8. 选择"创建客户端 ID"
  9. 。请记住"客户端 ID"和"客户端密码",稍后我们将使用它们。

因此,现在我们可以生成刷新令牌:

  1. 转到以下 URI(请注意,重定向 URI 必须与在客户端 ID 中输入的值完全匹配,包括任何尾随反斜杠):

https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=REDIRECT_URI&client_id=CLIENT_ID

  1. 出现提示时选择允许访问。
  2. 浏览器将使用代码参数重定向到重定向 URI,该参数类似于 4/eWdxD7b-YSQ5CNNb-c2iI83KQx19.wp6198ti5Zc7dJ3UXOl0T3aRLxQmbwI。复制此值。

使用以下命令创建主类:

public static String getRefreshToken(String code)
{
    HttpClient client = new DefaultHttpClient();
    HttpPost post = new HttpPost("https://accounts.google.com/o/oauth2/token");
    try 
    {
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(5);
        nameValuePairs.add(new BasicNameValuePair("grant_type",    "authorization_code"));
        nameValuePairs.add(new BasicNameValuePair("client_id",     GOOGLE_CLIENT_ID));
        nameValuePairs.add(new BasicNameValuePair("client_secret", GOOGLE_CLIENT_SECRET));
        nameValuePairs.add(new BasicNameValuePair("code", code));
        nameValuePairs.add(new BasicNameValuePair("redirect_uri", GOOGLE_REDIRECT_URI));
        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
        org.apache.http.HttpResponse response = client.execute(post);
        BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        StringBuffer buffer = new StringBuffer();
        for (String line = reader.readLine(); line != null; line = reader.readLine())
        {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        String refreshToken = json.getString("refresh_token");                      
        return refreshToken;
    }
    catch (Exception e) { e.printStackTrace(); }
    return null;
}

GOOGLE_CLIENT_IDGOOGLE_CLIENT_SECRETGOOGLE_REDIRECT_URI是先前的值。

最后,我们有了刷新令牌!此值不会过期,因此我们可以存储在某个站点中,例如属性文件。

访问 Google Play Android Developer API

  1. 获取访问令牌。我们将需要我们之前的刷新令牌:

    private static String getAccessToken(String refreshToken){
    HttpClient client = new DefaultHttpClient();
    HttpPost post = new HttpPost("https://accounts.google.com/o/oauth2/token");
    try 
    {
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(4);
        nameValuePairs.add(new BasicNameValuePair("grant_type",    "refresh_token"));
        nameValuePairs.add(new BasicNameValuePair("client_id",     GOOGLE_CLIENT_ID));
        nameValuePairs.add(new BasicNameValuePair("client_secret", GOOGLE_CLIENT_SECRET));
        nameValuePairs.add(new BasicNameValuePair("refresh_token", refreshToken));
        post.setEntity(new UrlEncodedFormEntity(nameValuePairs));
        org.apache.http.HttpResponse response = client.execute(post);
        BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
        StringBuffer buffer = new StringBuffer();
        for (String line = reader.readLine(); line != null; line = reader.readLine())
        {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        String accessToken = json.getString("access_token");
        return accessToken;
    }
    catch (IOException e) { e.printStackTrace(); }
    return null;
    

    }

  2. 现在,我们可以访问Android API。我对订阅的到期时间感兴趣,所以:

    private static HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
    private static JsonFactory JSON_FACTORY = new com.google.api.client.json.jackson2.JacksonFactory();
    private static Long getSubscriptionExpire(String accessToken, String refreshToken, String subscriptionId, String purchaseToken){
    try{
        TokenResponse tokenResponse = new TokenResponse();
        tokenResponse.setAccessToken(accessToken);
        tokenResponse.setRefreshToken(refreshToken);
        tokenResponse.setExpiresInSeconds(3600L);
        tokenResponse.setScope("https://www.googleapis.com/auth/androidpublisher");
        tokenResponse.setTokenType("Bearer");
        HttpRequestInitializer credential =  new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
                .setJsonFactory(JSON_FACTORY)
                .setClientSecrets(GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET)
                .build()
                .setFromTokenResponse(tokenResponse);
        Androidpublisher publisher = new Androidpublisher.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).
                setApplicationName(GOOGLE_PRODUCT_NAME).
                build();
        Androidpublisher.Purchases purchases = publisher.purchases();
        Get get = purchases.get(GOOGLE_PACKAGE_NAME, subscriptionId, purchaseToken);
        SubscriptionPurchase subscripcion = get.execute();
        return subscripcion.getValidUntilTimestampMsec();
    }
    catch (IOException e) { e.printStackTrace(); }
    return null;
    

    }

仅此而已!

有些步骤来自 https://developers.google.com/android-publisher/authorization。

您可以使用

com.google.api-clientgoogle-api-services-androidpublisher库。

首先转到谷歌开发者控制台上的项目(https://console.developers.google.com)

    API
  • 和身份验证 -> API
  • 启用"Google Play Android Developer API"
  • 转到凭据 ->创建新的客户端 ID
  • 选择服务帐户
  • 创建客户端 ID
  • 将 p12 文件保存在安全的地方

然后将刚刚为服务帐号生成的电子邮件地址添加到您的 Google Play 开发者管理中心 (https://play.google.com/apps/publish/)

    设置 -> 用户
  • 和权限 -> 邀请新用户
  • 粘贴@developer.gserviceaccount.com电子邮件帐户
  • 选择"查看财务报告"
  • 发送邀请

现在进入代码。将以下依赖项添加到 pom.xml 文件中:

<dependency>
    <groupId>com.google.api-client</groupId>
    <artifactId>google-api-client</artifactId>
    <version>1.18.0-rc</version>
</dependency>
<dependency>
    <groupId>com.google.http-client</groupId>
    <artifactId>google-http-client-jackson2</artifactId>
    <version>1.18.0-rc</version>
</dependency>
<dependency>
    <groupId>com.google.apis</groupId>
    <artifactId>google-api-services-androidpublisher</artifactId>
    <version>v1.1-rev25-1.18.0-rc</version>
</dependency>

然后首先验证签名:

byte[] decoded = BASE64DecoderStream.decode(KEY.getBytes());
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(decoded));
Signature sig = Signature.getInstance("SHA1withRSA");
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (sig.verify(BASE64DecoderStream.decode(signature.getBytes())))
{
    // Valid
}

如果签名验证了提取订阅详细信息:

// fetch signature details from google
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential credential = new GoogleCredential.Builder()
    .setTransport(httpTransport)
    .setJsonFactory(jsonFactory)
    .setServiceAccountId(ACCOUNT_ID)
    .setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/androidpublisher"))
    .setServiceAccountPrivateKeyFromP12File(new File("key.p12"))
    .build();
AndroidPublisher pub = new AndroidPublisher.Builder(httpTransport, jsonFactory, credential)
    .setApplicationName(APPLICATION_NAME)
    .build();
AndroidPublisher.Purchases.Get get = pub.purchases().get(
    APPLICATION_NAME,
    PRODUCT_ID,
    token);
SubscriptionPurchase subscription = get.execute();
System.out.println(subscription.toPrettyString());

这将通过生成 JWT 令牌来解决所有令牌问题,因此您不必自己处理它。

对于那些想要在Google的AppEngine上使用Java检查订阅状态的人,这是我基于SO上找到的许多代码的工作示例。我花了几天时间解决许多因缺乏经验而造成的错误。我看到很多在服务器上检查订阅状态的建议,但在 AppEngine 上对我来说并不容易。如果在SO上找不到答案,我无法想出这个。

步骤 1

首先,我们需要浏览Jonathan Naguin答案中的"先决条件"部分,直到您从Web浏览器获得代码。现在你有;

  • 客户端标识
  • 客户端密码
  • 重定向 URI
  • 法典

准备。

请注意,我们在 AppEngine 上运行下面显示的所有代码。我像这样使用记录器。

static final Logger log = Logger.getLogger(MyClassName.class.getName());

步骤 2

我们需要获取刷新令牌。将 [客户端 ID]、[客户端密码]、[代码]、[重定向 URI] 替换为字符串后,运行如下所示的代码。

private String getRefreshToken()
{
    try
    {
        Map<String,Object> params = new LinkedHashMap<>();
        params.put("grant_type","authorization_code");
        params.put("client_id",[YOUR CLIENT ID]);
        params.put("client_secret",[YOUR CLIENT SECRET]);
        params.put("code",[YOUR CODE]);
        params.put("redirect_uri",[YOUR REDIRECT URI]);
        StringBuilder postData = new StringBuilder();
        for(Map.Entry<String,Object> param : params.entrySet())
        {
            if(postData.length() != 0)
            {
                postData.append('&');
            }
            postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));
        }
        byte[] postDataBytes = postData.toString().getBytes("UTF-8");
        URL url = new URL("https://accounts.google.com/o/oauth2/token");
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        conn.setRequestMethod("POST");
        conn.getOutputStream().write(postDataBytes);
        BufferedReader  reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuffer buffer = new StringBuffer();
        for (String line = reader.readLine(); line != null; line = reader.readLine())
        {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        String refreshToken = json.getString("refresh_token");
        return refreshToken;
    }
    catch (Exception ex)
    {
        log.severe("oops! " + ex.getMessage());
    }
    return null;
}

由于刷新令牌不会过期,我们可以将其保存在某个地方,或者只是在我们的代码中进行硬编码。(我们只需要运行一次上面的代码即可获得刷新令牌。

步骤 3

我们需要获取访问令牌。将 [客户端 ID]、[客户端密码]、[刷新令牌] 替换为字符串后,运行如下所示的代码。

private String getAccessToken()
{
    try
    {
        Map<String,Object> params = new LinkedHashMap<>();
        params.put("grant_type","refresh_token");
        params.put("client_id",[YOUR CLIENT ID]);
        params.put("client_secret",[YOUR CLIENT SECRET]);
        params.put("refresh_token",[YOUR REFRESH TOKEN]);
        StringBuilder postData = new StringBuilder();
        for(Map.Entry<String,Object> param : params.entrySet())
        {
            if(postData.length() != 0)
            {
                postData.append('&');
            }
            postData.append(URLEncoder.encode(param.getKey(),"UTF-8"));
            postData.append('=');
            postData.append(URLEncoder.encode(String.valueOf(param.getValue()),"UTF-8"));
        }
        byte[] postDataBytes = postData.toString().getBytes("UTF-8");
        URL url = new URL("https://accounts.google.com/o/oauth2/token");
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        conn.setRequestMethod("POST");
        conn.getOutputStream().write(postDataBytes);
        BufferedReader  reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        StringBuffer buffer = new StringBuffer();
        for (String line = reader.readLine(); line != null; line = reader.readLine())
        {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        String accessToken = json.getString("access_token");
        return accessToken;
    }
    catch (Exception ex)
    {
        log.severe("oops! " + ex.getMessage());
    }
    return null;
}

步骤 4

我想知道的是订阅的UTC过期。下面显示的代码在发现错误时返回过期 UTC,0。您需要提供程序包名称、产品 ID(=订阅 ID)、在步骤 3 中获得的访问令牌以及购买数据中找到的购买令牌。

private long getExpireDate(String packageName,String productId,String accessToken,String purchaseToken)
{
    try
    {
        String charset = "UTF-8";
        String query = String.format("access_token=%s",URLEncoder.encode(accessToken,charset));
        String path = String.format("https://www.googleapis.com/androidpublisher/v1/applications/%s/subscriptions/%s/purchases/%s",packageName,productId,purchaseToken);
        URL url = new URL(path + "?" + query);
        HttpURLConnection connection = (HttpURLConnection)url.openConnection();
        connection.setRequestProperty("Accept-Charset",charset);
        connection.setRequestMethod("GET");
        BufferedReader  reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuffer buffer = new StringBuffer();
        for(String line = reader.readLine(); line != null; line = reader.readLine())
        {
            buffer.append(line);
        }
        JSONObject json = new JSONObject(buffer.toString());
        return json.optLong("validUntilTimestampMsec");
    }
    catch (Exception ex)
    {
        log.severe("oops! " + ex.getMessage());
    }
    return 0;
}

注意 产品 ID 或订阅 ID 是在开发人员控制台上找到的字符串。此时将显示订阅项,并带有"名称/ID"列。它看起来像这样。

Description of item(product id)

最后一步(有趣的部分)

现在,我们有了所有组件来验证订阅是否有效。我确实喜欢这个。您需要将 [您的包裹名称]、[您的产品 ID] 替换为您的。

您需要提供购买数据,您可以通过 iabHelper 代码中的 Purchase#getOriginalJson() 获得这些数据。

private boolean checkValidSubscription(String purchaseData)
{
    String purchaseToken;
    JSONObject json;
    try
    {
        json = new JSONObject(purchaseData);
    }
    catch (JSONException e)
    {
        log.severe("purchaseData is corrupted");
        return true;    // false positive
    }
    purchaseToken = json.optString("purchaseToken");
    if(purchaseToken.length() == 0)
    {
        log.severe("no purchase token found");
        return true;    // false positive
    }
    String accessToken = getAccessToken();
    if(accessToken == null)
    {
        return true;    // false positive
    }
    long expireDate = getExpireDate([YOUR PACKAGE NAME],[YOUR PRODUCT ID],accessToken,purchaseToken);
    if(expireDate == 0)
    {
        log.severe("no expire date found");
        return true;    // false positive
    }
    expireDate += 86400000l;    // add one day to avoid mis judge
    if(expireDate  < System.currentTimeMillis())
    {
        log.severe("subscription is expired");
        return false;
    }
    // just for log output
    long leftDays = (expireDate - System.currentTimeMillis()) / 86400000l;
    log.info(leftDays + " days left");
    return true;
}

调试注意事项

谷歌返回JSON字符串作为响应。如果代码无法按预期工作,则记录 JSON 字符串可能有助于了解问题所在。

我希望这对某人有所帮助。

为了利用Jonathan Naguin的伟大答案,以下是获取刷新和访问令牌的nodejs版本:

//This script is to retreive a refresh token and an access token from Google API. 
//NOTE: The refresh token will only appear the first time your client credentials are used. 
//      I had to delete my client id within api console and create a new one to get the refresh token again.
//This is the downloaded json object from Google API Console. Just copy and paste over the template below.
var googleJson = {"web":{"auth_uri":"","client_secret":"","token_uri":"","client_email":"","redirect_uris":[""],"client_x509_cert_url":"","client_id":"","auth_provider_x509_cert_url":"","javascript_origins":[""]}};
//Retrieved from OAuth
var code            = ''; // Retrieved from the response of the URL generated by printGoogleAuthUrl(). You will need to be logged in as your publisher. Copy and paste the generated url. Copy the code parameter into this variable.
var refreshToken    = ''; // Retrieved from the printRefreshToken() function call. Requires the code variable to be filled out.
var accessToken     = ''; // Retrieved from the printAccessToken() function call. Requires the refreshToken variable to be filled out.

var querystring = require('querystring');
var https = require('https');
var fs = require('fs');
function printGoogleAuthUrl()
{
    console.log("https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=" + googleJson.web.redirect_uris[0] + "&client_id=" + googleJson.web.client_id);
}
function printRefreshToken()
{
    var post_data = querystring.stringify({
        'grant_type'    : 'authorization_code',
        'client_id'     : googleJson.web.client_id,
        'client_secret' : googleJson.web.client_secret,
        'code'          : code,
        'redirect_uri'  : googleJson.web.redirect_uris[0]
    });
    var post_options = {
      host: 'accounts.google.com',
      port: '443',
      path: '/o/oauth2/token',
      method: 'POST',
      headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': post_data.length
        }
    };
    var post_req = https.request(post_options, function(res) {
        res.setEncoding('utf8');
        var data = "";
        res.on('data', function (chunk) {
            data += chunk;
        });
        res.on('end', function(){
            var obj = JSON.parse(data);
            if(obj.refresh_token)
            {
                refreshToken = obj.refresh_token;
            }
            else
            {
                console.log("No refresh token found. I had to clear the web client id in Google Api Console and create a new one. There might be a better way here.");
            }   
            console.log(data);
        });
    });
    post_req.write(post_data);
    post_req.end();
}
function printAccessToken()
{
    var post_data = querystring.stringify({
        'grant_type'    : 'refresh_token',
        'client_id'     : googleJson.web.client_id,
        'client_secret' : googleJson.web.client_secret,
        'refresh_token' : refreshToken
    });
    var post_options = {
      host: 'accounts.google.com',
      port: '443',
      path: '/o/oauth2/token',
      method: 'POST',
      headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Content-Length': post_data.length
        }
    };
    var post_req = https.request(post_options, function(res) {
        res.setEncoding('utf8');
        var data = "";
        res.on('data', function (chunk) {
            data += chunk;
        });
        res.on('end', function(){
            var obj = JSON.parse(data);
            if(obj.access_token)
                accessToken = obj.access_token;
            else
                console.log("No access token found.");
            console.log(data);
        });
    });
    post_req.write(post_data);
    post_req.end();
}
printGoogleAuthUrl();
//printRefreshToken();  
//printAccessToken();

对于那些寻找AndroidPublisher v3最新答案的人,请看这里:https://stackoverflow.com/a/57943483/1028256。

无需处理 refreshToken 和 accessToken,只需几行代码。

对于Android客户端,我找到了这个"官方"示例代码:https://github.com/googlesamples/android-play-publisher-api/blob/master/v3/java/src/com/google/play/developerapi/samples/AndroidPublisherHelper.java 并且有选项可以使用.p12文件或应用程序凭据获取AndroidPublisher。

我很确定您必须使用您的客户 ID,而不是电子邮件地址。它看起来像这样: 37382847321922.apps.googleusercontent.com

见 https://developers.google.com/android-publisher/authorization

client_id=<the client ID token created in the APIs Console>

而且我很确定您不需要 P12 文件。你只需要

client_secret=<the client secret corresponding to the client ID>

首先尝试从命令行手动执行此操作,使用"wget"。

最新更新