无法从Google Play Android Developer API获取订阅信息

Jon*_*uin 25 java android google-api subscription google-play

我正在使用Google API Client Library for Java来获取有关在我的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
Run Code Online (Sandbox Code Playgroud)

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."
}
Run Code Online (Sandbox Code Playgroud)

我真的很感谢你对这个问题的帮助......

Jon*_*uin 43

我搞定了!我遵循的步骤:

条件

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

  1. 转到API控制台并使用您的Android开发者帐户登录(与Android Developer Console中用于上传APK的帐户相同).
  2. 选择创建项目.
  3. 转到左侧导航面板中的"服务".
  4. 打开谷歌Play Android开发者API上.
  5. 接受服务条款.
  6. 转到左侧导航面板中的API Access.
  7. 选择"创建OAuth 2.0客户端ID":
    • 在第一页上,您需要填写产品名称,但不需要徽标.
    • 在第二页上,选择Web应用程序并设置重定向URI 和Javascript源.稍后我们将使用重定向URI.
  8. 选择创建客户端ID.请记住客户端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;
}
Run Code Online (Sandbox Code Playgroud)

GOOGLE_CLIENT_ID,GOOGLE_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;
    
    Run Code Online (Sandbox Code Playgroud)

    }

  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;
    
    Run Code Online (Sandbox Code Playgroud)

    }

就这样!

部分步骤来自https://developers.google.com/android-publisher/authorization.

  • 我觉得这个过程看起来很迟钝是荒谬的.这方面的文件几乎不存在.对于'帮助'库,不得不深入挖掘SO甚至找到一个不错的代码示例是荒谬的.Apple的文档完全糟糕,但至少验证收据的过程很简单./咆哮 (2认同)

Mih*_*bar 5

您可以使用com.google.api-clientgoogle-api-services-androidpublisher库。

首先,在Google Developer Console(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>
Run Code Online (Sandbox Code Playgroud)

然后首先验证签名:

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
}
Run Code Online (Sandbox Code Playgroud)

如果签名验证获取订阅详细信息,请执行以下操作:

// 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());
Run Code Online (Sandbox Code Playgroud)

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


Tom*_*cat 5

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

步骤1

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

  • 客户ID
  • 客户秘密
  • 重定向URI
  • 代码

准备好。

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

static final Logger log = Logger.getLogger(MyClassName.class.getName());
Run Code Online (Sandbox Code Playgroud)

第2步

我们需要获取刷新令牌。将 [YOUR CLIENT ID]、[YOUR CLIENT SECRET]、[YOUR CODE]、[YOUR REDIRECT 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;
}
Run Code Online (Sandbox Code Playgroud)

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

步骤3

我们需要获取访问令牌。将 [YOUR CLIENT ID]、[YOUR CLIENT SECRET]、[YOUR REFRESH TOKEN] 替换为您的字符串后,运行下面所示的代码。

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;
}
Run Code Online (Sandbox Code Playgroud)

步骤4

我想知道的是订阅的 UTC 到期时间。下面显示的代码在发现错误时返回expire 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;
}
Run Code Online (Sandbox Code Playgroud)

注意产品 ID 或订阅 ID 是在开发人员控制台上找到的字符串。您的订阅项目将显示在名称/ID 列中。看起来像这样。

Description of item(product id)
Run Code Online (Sandbox Code Playgroud)

最后一步(有趣的部分)

现在我们拥有验证订阅是否有效的所有组件。我确实喜欢这个。您需要将 [您的包名称]、[您的产品 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;
}
Run Code Online (Sandbox Code Playgroud)

调试注意事项

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

我希望这可以帮助别人。