【问题标题】:Am I getting the steps right for verifying a user's Android in-app subscription?我是否正确地验证了用户的 Android 应用内订阅的步骤?
【发布时间】:2012-09-07 19:02:31
【问题描述】:

我正在制作一个不需要用户帐户/登录并允许用户购买订阅的应用。我想使用 Google Play Developer API 来验证用户是否已购买/有效订阅。从所有文档中,我收集了以下步骤。

它们是否正确,你能回答其中的两个问题吗?

  1. 在 Google API 控制台中创建 Service Account。
  2. 保存给我的私钥(在哪里?肯定不在我的代码中/设备上,正如this sample code 建议的那样)
  3. 使用Google APIs Client Library for Java 使用私钥创建和签署 JWT(如何?the docs give me this,但这不是 Java 代码......我该怎么处理它?)
  4. 构造访问令牌请求,并获取对 API 的访问权限
  5. 应用程序现在可以向 API 发送一个GET 请求,以确定是否 用户订阅了
  6. 访问令牌过期后,返回步骤 3。

另外,我有一个 Web 服务,虽然我对 Web 服务或 Web 服务编程一无所知...我只知道足够了解它可能需要在这里使用。

编辑:这些步骤不正确。请参阅下面的答案以了解正确的步骤。但是请注意,这仅适用于使用服务帐户(因为我不想要求用户必须明确允许 API 访问)

【问题讨论】:

    标签: android service google-api google-play subscription


    【解决方案1】:

    事实证明,我的步骤不正确。我花了几个星期才弄清楚这一点,而且似乎没有在其他任何地方记录下来。不客气:

    1. 在Google APIs Console 中创建一个Web 应用程序 帐户。将任何网站作为“重定向 URI”;没关系,因为您不会真正使用它。创建帐户时,您将获得一个客户端 ID 和客户端密码。

    2. 在您计算机上的浏览器中转到https://accounts.google.com/o/oauth2/auth?scope=https://www.googleapis.com/auth/androidpublisher&response_type=code&access_type=offline&redirect_uri=[YOUR REDIRECT URI]&client_id=[YOUR CLIENT ID] 并在出现提示时允许访问。

    3. 查看地址栏。您最初输入的 URI 的末尾将是您的刷新令牌。看起来像1/.... 你将在下一步中需要这个“代码”。刷新令牌永不过期。

    4. 转至https://accounts.google.com/o/oauth2/token?client_id=[YOUR CLIENT ID]&client_secret=[YOUR CLIENT SECRET]&code=[CODE FROM PREVIOUS STEP]&grant_type=authorization_code&redirect_uri=[YOUR REDIRECT URI],将此“代码”转换为“刷新令牌”。您可以将结果值保存在程序中;除非明确撤销,否则它永远不会过期。 (@BrianWhite 插入的这一步——参见 cmets) 确保您使用的是 POST。(由 Gintas 插入)

    5. 在您的代码中,使用 BasicNameValuePairs "grant_type","refresh_token"、"client_id",[YOUR CLIENT ID]、"client_secret",[YOUR CLIENT SECRET]、"refresh_token",[YOUR REFRESH TOKEN] 向 https://accounts.google.com/o/oauth2/token 发送 HttpPost 请求。例如看here。您将需要在单独的线程中执行此操作,可能使用 AsyncTask。这将返回一个 JSONObject。

    6. 从返回的 JSONObject 中获取访问令牌。例如看here。您将需要获取字符串“access_token”。访问令牌将在 1 小时后过期。

    7. 在您的代码中,向https://www.googleapis.com/androidpublisher/v1/applications/[YOUR APP'S PACKAGE NAME]/subscriptions/[THE ID OF YOUR PUBLISHED SUBSCRIPTION FROM YOUR ANDROID DEVELOPER CONSOLE]/purchases/[THE PURCHASE TOKEN THE USER RECEIVES UPON PURCHASING THE SUBSCRIPTION]?accesstoken="[THE ACCESS TOKEN FROM STEP 4]" 发送一个HttpGet 请求。例如看here。

    【讨论】:

    • 顺便说一句 - 您不应该直接从您的应用程序执行此操作,您应该从您的服务器执行此操作。您的应用应该只直接与您的服务器对话。
    • 很公平,只是想我会指出这一点,以防其他人阅读该帖子。
    • @TheBeatlemaniac 当我将您的答案与本文档developers.google.com/android-publisher/authorization 进行比较时,步骤 3 和步骤 4 之间似乎缺少一个步骤,即根据浏览器地址中返回的代码获取刷新令牌线。你能详细说明一下吗?另外,您能否详细说明“刷新令牌永不过期”的声明,因为其他人似乎认为它在某些情况下确实会过期。 (我希望你在这两个方面都是正确的)
    • @TheBeatlemaniac 感谢您的及时答复。在第 2 步之前,我们正在执行完全相同的过程。我在第 2 步中得到的结果是地址行中的 URL,其末尾有 code=xxxxx。然后我将此代码与具有grant_type = authorization_code的额外HttpPost请求一起使用,我得到一个JSON响应,从中我得到我在步骤4中使用的refresh_token。顺便说一句,我得到的代码(在code = xxx中)以4开头/...我们看到的是同一件事吗?再次感谢..
    • 我已编辑答案以添加缺少的步骤。不确定它在上线之前是否需要其他编辑的批准。
    【解决方案2】:

    .NET 用户:我希望这个答案能帮别人省很多事。

    正如@Christophe Fondacci 在 2015 年指出的那样,公认的解决方案在几年前效果很好。

    现在是 2017 年 2020 年,这个过程更容易和更快。

    我的用例是验证应用内订阅,我的移动应用将订阅购买信息发送到我的 RESTful 服务器,然后它会联系 Google 以验证订阅购买。

    策略是创建一个代表您操作的服务帐户。

    1. 登录您的Google Play Dev Console 并点击您正在设置的应用程序。

    2. 访问设置->API访问

    3. 在服务帐户下,点击创建服务帐户按钮。

    4. 自 2017 年 1 月起,将出现一个对话框,其中包含有关设置服务帐户的说明。该对话框会将您带到 Google API 控制台;从那里,

      A) 点击创建服务帐户

      B) 创建有意义的服务帐户名称。由于我们有兴趣访问 Android Publisher Services,因此我选择了“publisher”。

    C) 对于角色,只需选择一些内容 - 您可以稍后更改。

    D) 为 .Net 实现选择“提供新的私钥”并选择 P12。不要丢失此文件!

    1. 现在您已完成 #4,您将看到列出的新服务帐户;单击“授予访问权限”以启用它。

    2. 点击“查看权限”链接。您应该根据自己的需要和 API 修改权限。

    要验证应用内购买,请访问 Cog->更改权限并启用全球“可见性”和“管理订单”权限。

    好的,此时您已经在 Google 端配置了所有内容。现在将您的服务器设置为服务器的东西。我建议创建 一个 .Net 控制台应用程序来测试您的实现,然后在需要的地方卸载它。

    1. 从 Nuget 添加 Android Publisher 客户端库[1]
        PM> Install-Package Google.Apis.AndroidPublisher.v3
    
    1. 将 P12 文件添加到项目根目录

    2. 更改 P12 属性,使“构建操作”为“内容”,“复制到输出目录”为“如果更新则复制”。

    3. 实施类似的方法来测试您的访问权限并微调 [1]。

        using System.Threading.Tasks;
        using System.Security.Cryptography.X509Certificates;
        using Google.Apis.Services;
        using Google.Apis.Auth.OAuth2;
        using Google.Apis.AndroidPublisher.v3;
        ...
        public Task<SubscriptionPurchase> GetSubscriptionPurchase(string packageName, string productId, string purchaseToken)
        {
            var certificate = new X509Certificate2(
                            "{{your p12 file name}}",
                            "{{ your p12 secret }}",
                            X509KeyStorageFlags.Exportable
                        );
            var credentials = new ServiceAccountCredential(
                    new ServiceAccountCredential.Initializer("{{ your service account email }}")
                    {
                        Scopes = new[] { AndroidPublisherService.Scope.Androidpublisher }
                    }.FromCertificate(certificate));
        
            var service = new AndroidPublisherService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credentials,
                ApplicationName = "my server app name",
            });
            return service.Purchases.Subscriptions.Get(packageName, productId, purchaseToken).ExecuteAsync();
        }
    

    祝你好运,希望这对某人有所帮助。

    来源:

    Using OAuth 2.0 for Server to Server Applications

    .Net Client Library for Google.Apis.AndroidPublisher.v3[1]


    1 2020 年 4 月 11 日更新 - Google.Apis.AndroidPublisher.v2 EOL'd,使用 Google.Apis.AndroidPublisher.v3。

    【讨论】:

    • 帮助很大。只需要 1 个额外步骤:在 Google 'Play Developer Console' 链接项目,如此处所述stackoverflow.com/a/25655897/2700303。
    • 谢谢。太好了。你节省了我的时间。我使用“notasecret” - 作为 p12 机密。
    • 如果您将此代码部署到 Azure,您需要在 X509Certificate2 构造函数上使用 X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet 。否则代码将尝试使用可以被拒绝的用户证书存储。
    • 找到了。回到play.google.com,而不是console.developers.google.com。看起来 UI 也已更新。在“全球”下,我选中了 2 个框:“查看应用信息”和“管理订单”。
    • 2020年还在工作,如果遇到“此版本的Play Developer API不再可用”,请使用“Google.Apis.AndroidPublisher.v3”NuGet包。
    【解决方案3】:

    如果您像我一样,想在 PHP 中执行此操作,请按照以下步骤操作...感谢 Kalina 的回答,我只花了三天时间就搞定了工作:)。

    这里是:

    1. 转到谷歌开发者控制台https://console.developers.google.com/并创建一个网络应用程序。将“developers.google.com/oauthplayground”作为“重定向 URI”;您将在第 2 步中使用它。创建帐户时,您将获得一个客户端 ID 和客户端密码。确保您已添加 Google Play Android Developer API。

    2. 转到 Google oauth2 Playground https://developers.google.com/oauthplayground/。这个伟大的工具是你接下来几天最好的朋友。 现在转到设置:确保已设置使用您自己的 OAuth 凭据。只有这样您才能在下面的表格中填写您的客户 ID 和 客户密码。

    3. 在 Google oauth2 playground 中转到第 1 步 选择并授权 API 在输入字段中填写范围 https://www.googleapis.com/auth/androidpublisher .我在列表中找不到 Google Play Android Developer API,也许他们稍后会添加。点击AUTORIZE APIS。执行以下授权操作。

    4. 在 Google oauth2 Playground 中转到第 2 步 交换令牌的授权码。如果一切顺利,您将看到以 /4 开头的授权码。如果某些事情进展不顺利,请检查右侧的错误消息。现在您点击“刷新访问令牌”。复制刷新令牌...它将以 /1 开头...

    5. 现在您可以随时获得访问令牌!方法如下:

      $url ="https://accounts.google.com/o/oauth2/token";
      $fields = array(
         "client_id"=>"{your client id}",
         "client_secret"=>"{your client secret}",
         "refresh_token"=>"{your refresh token 1/.....}",
         "grant_type"=>"refresh_token"
      );
      
      $ch = curl_init($url);
      
      //set the url, number of POST vars, POST data
      curl_setopt($ch, CURLOPT_POST,count($fields));
      curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
      curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
      curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      
      //execute post
      $lResponse_json = curl_exec($ch);
      
      //close connection
      curl_close($ch);
      

    现在你有一个访问令牌万岁...... JSON 将如下所示:

    "access_token" : "{the access token}",  "token_type" : "Bearer",  "expires_in" : 3600
    

    您终于可以向 google 提问了!操作方法如下:

    $lAccessToken = "{The access token you got in}" ;
    $lPackageNameStr = "{your apps package name com.something.something}";
    $lURLStr =  "https://www.googleapis.com/androidpublisher/v1.1/applications/$lPackageNameStr/subscriptions/$pProductIdStr/purchases/$pReceiptStr";
    
    $curl = curl_init($lURLStr);
    
    curl_setopt($curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
    curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 
    $curlheader[0] = "Authorization: Bearer " . $lAccessToken;
    curl_setopt($curl, CURLOPT_HTTPHEADER, $curlheader);
    
    $json_response = curl_exec($curl);
    curl_close($curl);
    
    $responseObj = json_decode($json_response,true);
    

    返回的 JSON 将包含两个时间戳,initiationTimestampMsec 和 validUntilTimestampMsec 订阅有效的时间。两者都是添加到日期 1/1/1970 的毫秒数!

    【讨论】:

    • 不客气,别忘了第二个 accesstoken 的有效期只有几个小时。之后,只需要求另一个。
    • 我得到了类似 Array ( [error] => Array ( [errors] => Array ( [0] => Array ( [domain] => androidpublisher [reason] => permissionDenied [message] => 当前用户没有足够的权限来执行请求的操作。)) [code] => 401 [message] => 当前用户没有足够的权限来执行请求的操作。))。你能帮我吗
    • 在这段代码中,$pProductIdStr 和 $pReceiptStr 是什么
    • @lrfan $pProductIdStr 是您的 Play 商店产品 ID,$pReceiptStr 是您购买时收到的收据。
    • 我需要帮助,在我的情况下,购买而不订阅,我已经实现了 url 地址中的交换,但是总是返回我的结果是“未找到”Url:@ 987654329@
    【解决方案4】:

    我不知道在 2012 年,但在 2015 年,您不应该手动执行任何这些步骤。我很难找到文档,所以我在这里发布以防它对任何人有帮助。

    1. 出于安全原因,您应该只从服务器查询应用内购买,否则您将无法信任购买过程的两端。

    现在在服务器端(如果您绝对需要,我认为您仍然可以在您的应用程序中使用相同的代码),将google-api-services-androidpublisher 客户端库包含到您的项目中(请参阅https://developers.google.com/api-client-library/java/apis/androidpublisher/v1)

    正如您所提到的,您需要一个带有 P12 文件的服务帐户(客户端库只接受 P12 文件)。

    那么下面的代码就会很好的验证并获取购买信息:

        HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
        JsonFactory jsonFactory = new JacksonFactory();
    
        List<String> scopes = new ArrayList<String>();
        scopes.add(AndroidPublisherScopes.ANDROIDPUBLISHER);
    
        Credential credential = new GoogleCredential.Builder().setTransport(httpTransport).setJsonFactory(jsonFactory)
                .setServiceAccountId(googleServiceAccountId)
                .setServiceAccountPrivateKeyFromP12File(new File(googleServicePrivateKeyPath))
                .setServiceAccountScopes(scopes).build();
        AndroidPublisher publisher = new AndroidPublisher.Builder(httpTransport, jsonFactory, credential).build();
        AndroidPublisher.Purchases purchases = publisher.purchases();
        final Get request = purchases.get(packageName, productId, token);
        final SubscriptionPurchase purchase = request.execute();
    
        // Do whatever you want with the purchase bean
    

    可以在此处找到有关 Java 客户端身份验证的信息: https://developers.google.com/identity/protocols/OAuth2ServiceAccount

    【讨论】:

      【解决方案5】:

      我可能误解了您的问题,但我认为您没有理由使用您引用的链接来获取运行 Android 应用的应用内结算。这个页面更有帮助:

      http://developer.android.com/guide/google/play/billing/index.html

      您可以试用其中包含的演示应用程序 (Dungeons -- http://developer.android.com/guide/google/play/billing/billing_integrate.html#billing-download)。这使用产品(一次性购买)而不是订阅,但您应该能够进行修改以测试您想要的内容。

      我认为,对您而言,关键是他们在示例中提供的 restoreTransactions 方法,以查看 Google Play 帐户是否有您的应用订阅:

      @Override
      public void onRestoreTransactionsResponse(RestoreTransactions request, int responseCode) {
          if (responseCode == BillingVars.OK) {                        
              // Update the shared preferences so that we don't perform a RestoreTransactions again.
              // This is also where you could save any existing subscriptions/purchases the user may have.
              SharedPreferences prefs = getSharedPreferences(my_prefs_file, Context.MODE_PRIVATE);
              SharedPreferences.Editor edit = prefs.edit();
              edit.putBoolean(DB_INITIALIZED, true);
              edit.commit();
          } else {
              Log.e(TAG, "RestoreTransactions error: " + responseCode);
          }
      }
      

      【讨论】:

      • 从您发布的链接转到“订阅”页面,有这个:developer.android.com/guide/google/play/billing/… 使用此 API 似乎是验证用户订阅的最安全方式,并且使用此 API 您需要用OAuth2授权账号,这需要我列出的步骤……也许你说的更好?
      【解决方案6】:

      如果有人对最后一步 (#7) 接受的帖子有任何疑问,我发现 ?access_token= 可以工作,而不是 ?accessToken=

      太糟糕的堆栈溢出不会让我直接对线程发表评论......

      【讨论】:

        【解决方案7】:

        由于您的应用程序可以调用 Web 服务,因此我建议您将私钥安全地存储在您的服务器上。您应该尽可能多地将应用程序内的内容转移到服务调用中,请参阅this link。我已经实现了应用内订阅,但那是在这部分 API 出来之前。我必须自己进行注册和安全验证,但看起来这个 API 使用 OAuth 为你完成了大部分工作,尽管看起来你仍然负责存储订阅请求/验证。

        在谈到使用现有库签署 JWT 时,它们似乎确实为您提供了指向 Java 库、Python 库和 PHP 库的链接——这取决于您的 Web 服务或服务器组件是用什么编写的(我的是 C#,所以我使用 RSACryptoServiceProvider) 来验证签名购买。他们使用 JSON 对象进行数据的实际传输。

        【讨论】:

        • 谢谢!然而,我之前对我的问题的回答让我担心。这6个步骤是对的,对吗?还是我应该按照地下城的例子?
        • 从我读到的内容(虽然到目前为止没有使用)我认为你的 6 个步骤是正确的。我相信,地下城的例子是在一些新的 API 功能之前创建的,而地下城的例子并不安全。 Google I/O 上有一些有用的提示,例如youtube.com/watch?v=TnSNCXR9fbY
        • 哇,太好了。最后一个问题——我的网络服务是基于 SOAP 的,而且我知道 Google Play API 是 REST……这会是个问题吗?
        • 是的。您的基于 SOAP 的服务将期望调用基本 url 加上 op=MethodName 并期望有效负载为 XML 格式,而此基于 REST 的服务将期望调用类似 baseurl/method 的 url 并且有效负载是 JSON , 我相信。在您的服务和 Google 的服务之间切换时,您可能需要解析响应并重新格式化它们。
        猜你喜欢
        • 2012-09-20
        • 2012-09-16
        • 2013-04-24
        • 2020-12-01
        • 1970-01-01
        • 2018-04-05
        • 1970-01-01
        • 2016-09-16
        • 1970-01-01
        相关资源
        最近更新 更多