适用于 Android IAP 应用的 RVS


适用于 Android IAP 应用的 RVS

Receipt Verification Service (RVS) 允许从您自己的服务器验证您的应用用户所进行的购买。RVS 具有两个环境选项,具体取决于您的应用是处于开发/测试阶段,还是已发布到 Amazon Appstore:

RVS 工作流程

下表显示了带有收据验证的购买工作流程。RVS 在 IAP API 完成购买后启动并将购买收据返回给应用。

步骤 组件 任务
步骤 1应用应用启动应用内购买流程。应用调用 IAP API 来管理购买。
步骤 2IAP APIIAP API 与用户交互以完成购买。IAP API 向应用返回购买收据。
步骤 3应用 应用将购买收据转发给应用服务器。
步骤 4 应用服务器 应用服务器向 RVS 服务器发送验证收据请求。
步骤 5 RVS 服务器 RVS 服务器确认收据是否有效。
步骤 6 应用服务器 应用服务器将向用户提供内容。

上表将取代下图,显示 RVS 工作流程:​

您还可以使用 RVS 来允许访问在其他平台(如您的网站)上购买的订阅,只要是通过亚马逊购买的。以下情景描述了此工作流程:

  1. 您的应用用户从您的公司网站上通过亚马逊购买订阅。
  2. 您的应用收到所购买订阅的收据
  3. 为允许访问,您的应用将收据的信息发送到您的服务器。最后,您的服务器通过查询 RVS 来验证此交易。

设置 RVS

为要测试的应用单独设置 RVS 沙盒。在发布您的应用并使其在生产环境中后,您可以使用 RVS 生产服务器。

设置 RVS 沙盒

结合使用 RVS 沙盒和 App Tester 测试工具来在测试环境中验证您的收据,然后再将您的应用发布到 Amazon Appstore。RVS 沙盒作为 .war 文件包含在 Amazon Android SDK zip 文件中。从 Apps & Games Services SDKs(应用和游戏服务 SDK)页面上,单击 Android 链接来下载 SDK 文件。

您必须在 Android 移动设备上安装 Amazon App Tester 工具,然后才能使用 RVS 沙盒。

设置 RVS 沙盒:

  1. 安装并启动 Apache Tomcat 服务器:
    1. 从 Apache Tomcat 网站中,下载并安装 Apache Tomcat 服务器(版本 6 或更高版本)。
    2. 配置 Tomcat 以使用您的凭证。在 /apache-tomcat-< version >/conf/tomcat-users.xml 文件中,添加以下元素:

        <tomcat-users>
            <role rolename="manager-gui"/>
            <user username="MyUserName" password="MyPassword" roles="manager-gui"/>
        </tomcat-users>
      
    3. 更改 /apache-tomcat-< version >/bin/startup.sh 的权限以使此文件可执行(如果您尚未执行此操作):

      $ chmod +x /apache-tomcat-< **_version_** >/bin/startup.sh
      
    4. 要启动 Tomcat 服务器,请执行 /apache-tomcat-< version >/bin/startup.sh 文件。

      $ /apache-tomcat-< **_version_** >/bin/startup.sh
      
    5. 验证 Tomcat 服务器是否正在运行,方法是打开 Web 浏览器窗口,然后转到:http://localhost:8080/

      如果 Tomcat 服务器正在运行,您将看到 Apache Tomcat/< version > 主页。现在,您可以部署 RVS 沙盒了。

  2. 部署 RVS 沙盒:
    1. 解压缩 Amazon Android SDK zip 文件。
    2. 在 Amazon Android SDK 文件夹中,导航到 AmazonInAppPurchasing/tools
    3. 找到 RVSSandbox.war 文件并记下其文件路径。
    4. 从浏览器的 Tomcat 主页中,选择 Manager App(管理器应用),然后滚动到 Deploy(部署)部分。
    5. Deploy(部署)部分中,导航到 WAR file to deploy(要部署的 WAR 文件)> WAR file to upload(要上传的 WAR 文件)> Browse(浏览),然后从 Apps-SDK 中选择 RVSSandbox.war 文件。
    6. 选择 Deploy(部署)以在应用列表中查看 RVSSandbox。确保 Running(正在运行)列的值为 true
    7. 从应用列表中选择 RVSSandbox,或在浏览器中访问 http://localhost:8080/RVSSandbox/

      下面的确认消息指示 RVS 沙盒正在运行:

      Receipt Verification Service 沙盒已启动!
      

设置生产服务器

在您的应用已发布并已准备好与 RVS 生产服务器通信后,您将需要设置自己的服务器以与 RVS 进行通信(如果您在使用 RVS 沙盒进行测试时尚未执行此操作)。从使用 RVS 沙盒进行测试转为使用 RVS 生产服务器进行测试所需进行的唯一更改是将主机从“http://localhost:8080/RVSSandbox”更改为“https://appstore-sdk.amazon.com”。

亚马逊建议仅从安全服务器调用 RVS,因为它托管了共享密钥。请勿从您的应用调用此服务。此外,验证您此时使用的是否是真正的共享密钥。RVS 沙盒忽略共享密钥,因此如果您在沙盒中未使用正确的共享密钥,则需要确保为 RVS 生产服务器使用正确的共享密钥。共享密钥可在 Amazon Appstore 中您的开发者账户的“Shared Key”(共享密钥)页面上找到:

https://developer.amazon.com/sdk/shared-key.html

对于您使用哪种代码语言和其他选项,您可以将自己的服务器设置为自己的首选项。您的服务器需要能够通过安全协议(如 https 或 REST)与 RVS 安全地通信。您的服务器需要能够向 RVS 发送请求并接收和处理其响应。

作为先决条件,您需要向您的项目中添加 JSON 解析库(如 Jackson)来解析 JSON 响应。

以下示例代码适用于通用的 Java 服务器。此代码调用 verifyReceipt() 来执行以下任务:

  1. 创建包含相应开发者和交易信息的 URL 字符串。
  2. 连接到 Amazon RVS 服务器并将交易 URL 传递到服务器。
  3. 从 Amazon RVS 服务器检索响应。
  4. 将响应传递到应用。

      public static void verifyReceipt(final String developerSecret, final String userId, final String receiptId) {
        System.out.println("Start Receipt Validation");
    
        String url = "https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/" + developerSecret + "/user/" + userId + "/receiptId/" + receiptId;
    
        System.out.println("Amazon Receipt Validation URL: " + url);
    
        try {
            System.out.println("Open HTTP connection to Amazon RVS");
    
            URL obj = new URL(url);
            HttpURLConnection con = (HttpURLConnection) obj.openConnection();
    
            int responseCode = con.getResponseCode();
    
            System.out.println("Amazon RVS Response Code: " + responseCode);
    
            switch (responseCode)
            {
                case 400:
                    System.out.println("Amazon RVS Error: Invalid receiptID");
                    // Process Response Data locally
                    // Respond to app
                    break;
    
                case 496:
                    System.out.println("Amazon RVS Error: Invalid developerSecret");
                    // Process Response Data locally
                    // Respond to app
                    break;
    
                case 497:
                    System.out.println("Amazon RVS Error: Invalid userId");
                    // Process Response Data locally
                    // Respond to app
                    break;
    
                case 500:
                    System.out.println("Amazon RVS Error: Internal Server Error");
                    // Process Response Data locally
                    // Respond to app
                    break;
    
                case 200:
    
                    //Retrieve Amazon RVS Response
                    BufferedReader in = new BufferedReader( new InputStreamReader(con.getInputStream()));
    
                    String inputLine;
                    StringBuffer response = new StringBuffer();
    
                    while ((inputLine = in.readLine()) != null) {
                        response.append(inputLine);
                    }
                    in.close();
    
                    //Log Amazon RVS Response
                    System.out.println("Amazon RVS Response: " + response.toString()());
    
                    //Create JSONObject for RVS Response
                    JSONObject responseJson = new JSONObject(response.toString());
    
                    //Parse RVS Response
                    JSONObject responseJson = new JSONObject(response.toString());
                    String receiptId = responseJson.getString("receiptId");
                    String productType = responseJson.getString("productType");
                    String productId = responseJson.getString("productId");
                    long purchaseDate = responseJson.optLong("purchaseDate");
                    long cancelDate = responseJson.optLong("cancelDate");
                    boolean testTransaction = responseJson.optBoolean("testTransaction");
    
                    // Process Response Data locally
                    // Respond to app
    
                    break;
    
                default:
                    System.out.println("Amazon RVS Error: Undefined Response Code From Amazon RVS");
                    // Process Response Data locally
                    // Respond to app
                    break;
            }
    
        } catch (MalformedURLException e) {
    
            // As a best practice, replace the following logic with logic for logging.
            System.out.println("Amazon RVS MalformedURLException");
            e.printStackTrace();
            // Process Response Data locally
            // Respond to app
        } catch (IOException e) {
    
            // As a best practice, replace the following logic with logic for logging.
            System.out.println("Amazon RVS IOException");
            e.printStackTrace();
            // Process Response Data locally
            // Respond to app
        }
      }
    

RVS 请求语法

使用 RVS 来验证在 PurchaseResponsePurchaseUpdatesResponse 中收到的收据对象。这些响应对象包含表示用户的唯一标识符的用户 ID 值。PurchaseResponse 中的收据包含 ReceiptId,它与用户 ID 结合使用来对购买执行带外服务器端验证。为了安全起见,来自您的服务器的请求需要共享密钥来确认您的身份,然后才能传递。

这些请求使用以下格式:

<Protocol>//<Server>/<[_RVSSandbox_]>/version/<Operation_version_number>/verifyReceiptId/developer/<Shared_Secret>/user/<UserId>/receiptId/<ReceiptId>

将尖括号中的词语替换为要验证的交易的以下值:

  • Protocol: 用于与服务器或沙盒通信的协议,如 https:。
  • Server:​ 要与之通信的 RVS 服务器的 URL。
    • 如果您使用的是 RVS 沙盒,请使用“localhost:8080”或您设置的 Tomcat 位置。
    • 如果您使用的是 RVS 生产服务器,则 URL 为“appstore-sdk.amazon.com”。
  • [RVSSandbox]:​ 如果您使用的是 RVSSandbox,请使用值“RVSSandbox”。如果您使用的是 RVS 生产服务器,请省略此字段。
  • Operation_version_number:​ verifyReceiptId 操作的版本号。此版本号独立于 IAP 版本号。当前 verifyReceiptId 版本号为“1.0”。
  • Shared_secret:​ 用于标识发出请求的开发者的共享密钥。您的共享密钥可在 Amazon Appstore 中您的开发者账户的“Shared Key”(共享密钥)页面上找到:https://developer.amazon.com/sdk/shared-key.html。对于 RVSSandbox,共享密钥可以是任何非空字符串。
  • UserId:​ 代表您的应用商店应用的不同亚马逊客户的 ID:purchaseResponse->userData->userId
  • ReceiptId:​ 购买的唯一 ID:purchaseResponse->receipt->receiptIdpurchaseUpdatesResponse->receipts->receipt->receiptId

RVS 响应语法

RVS 提供了 RESTful JSON API 接口。作为最佳实践,使用 JSON 解析器类从 RVS 服务器读取 JSON 响应。

在发出验证交易的请求后,RVS 服务器或沙盒将返回响应代码来指示请求是否成功。如果成功,则返回的 JSON 响应将包括有关交易的信息。

以下示例显示了一个成功响应:

   { "betaProduct":false,
     "cancelDate":null,
     "parentProductId":null,
     "productId":"com.amazon.iapsamplev2.gold_medal",
     "productType":"CONSUMABLE",
     "purchaseDate":1399070221749,
     "quantity":1,
     "receiptId":"wE1EG1gsEZI9q9UnI5YoZ2OxeoVKPdR5bvPMqyKQq5Y=:1:11",
     "renewalDate":null,
     "term":null,
     "termSku":null,
     "testTransaction":true
   }

RVS 响应代码

Receipt Verification Service 将回复以下代码之一,这些代码指示验证检查的结果:

响应代码描述
HTTP 200Success: 收据 ID、用户 ID 和共享密钥都是有效的。产品类型是以下项之一: “ENTITLED”、“CONSUMABLE”或“SUBSCRIPTION”
HTTP 400由此 receiptId 表示的交易无效,或没有找到此 receiptId 的交易。
HTTP 496sharedSecret 无效
HTTP 497用户 ID 无效
HTTP 500出现内部服务器错误

成功交易的 RVS 响应字段

下表列出并描述了成功交易的 RVS 响应中包含的字段:

字段数据类型描述
betaProduct布尔值指示所购买的产品是否是 Live App Testing 产品。
cancelDate长整数 取消购买的日期,或订阅到期的日期。如果未取消购买,则此字段为 null。
parentProductId字符串 Null。预留以供将来使用。
productId字符串 您在应用中为此项目定义的 SKU。
productType字符串 所购买产品的类型。有效产品类型为 CONSUMABLE、SUBSCRIPTION 和 ENTITLED。
purchaseDate长整数 购买的日期,以纪元以后的毫秒数计算。对于订阅项目,purchaseDate 代表首次购买日期,而不是后续续订的购买日期。
quantity整数 所购买的数量。始终为 null 或 1。
receiptID字符串 购买的唯一标识符。
renewalDate长整数 需要续订订阅购买的日期。日期以纪元以后的毫秒数计算。
term字符串 订阅 IAP 将保持有效的持续时间(期限从购买之日开始)。期限由数字和时间段(天、周、月、年)构成,如 1 Week2 Months
termSku字符串 对应于订阅期限的唯一 SKU。
testTransaction布尔值指示此购买是否是作为亚马逊发布和测试过程的一部分执行的。

请考虑以下用户具有已取消的订阅的示例。

  • 订阅的有效期为 01/01/2016 – 03/01/2016。对于此收据,为此订阅返回的 purchaseDate 将为 01/01/2016,而 cancelDate 将为 03/01/2016。
  • 如果此订阅后来在 04/01/2016 重新激活,则订阅将具有第二个收据。第二个收据将显示 purchaseDate 为 04/01/2016,cancelDate 为 null。

示例 RVS 调用

本部分将演示 RVS 沙盒和 RVS 生产服务器的示例 RVS 调用,还将演示这些调用的示例响应。请注意,这些是有效的 URL,因此,如果您将请求粘贴到浏览器中,您应获得本部分中与该请求配对的响应。

示例: RVS 沙盒请求

以下请求在 RVS 沙盒中验证收据:

http://localhost:8080/RVSSandbox/version/1.0/verifyReceiptId/developer/developerSecret/user/99FD_DL23EMhrOGDnur9-ulvqomrSg6qyLPSD3CFE=/receiptId/q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ

此调用将收到以下示例 JSON 响应:

{
    "betaProduct":false,
    "cancelDate":null,
    "parentProductId":null,
    "productId":"com.amazon.iapsamplev2.expansion_set_3",
    "productType":"ENTITLED",
    "purchaseDate":1402008634018,
    "quantity":1,
    "receiptId":"q1YqVrJSSs7P1UvMTazKz9PLTCwoTswtyEktM9JLrShIzCvOzM-LL04tiTdW0lFKASo2NDEwMjCwMDM2MTC0AIqVAsUsLd1c4l18jIxdfTOK_N1d8kqLLHVLc8oK83OLgtPNCit9AoJdjJ3dXG2BGkqUrAxrAQ",
    "renewalDate":null,
    "term":null,
    "termSku":null,
    "testTransaction":true
}

示例: 通过 RVS 生产服务器购买消费品

以下请求将在生产服务器上验证消费品购买收据:

https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/2:smXBjZkWCxDMSBvQ8HBGsUS1PK3jvVc8tuTjLNfPHfYAga6WaDzXJPoWpfemXaHg:iEzHzPjJ-XwRdZ4b4e7Hxw==/user/LRyD0FfW_3zeOlfJyxpVll-Z1rKn6dSf9xD3mUMSFg0=/receiptId/wE1EG1gsEZI9q9UnI5YoZ2OxeoVKPdR5bvPMqyKQq5Y=:1:11

此调用将收到以下示例 JSON 响应:

{
 "betaProduct":false,
 "cancelDate":null,
 "parentProductId":null,
 "productId":"com.amazon.iapsamplev2.gold_medal",
 "productType":"CONSUMABLE",
 "purchaseDate":1399070221749,
 "quantity":1,
 "receiptId":"wE1EG1gsEZI9q9UnI5YoZ2OxeoVKPdR5bvPMqyKQq5Y=:1:11",
 "renewalDate":null,
 "term":null,
 "termSku":null,
 "testTransaction":true
}

purchaseDatecancelDate 表示为以毫秒为单位的时间。您可以调用 java.util.Date(timeInMillis) 来将值转换为日期对象。cancelDatenull 指示尚未取消购买。如果收据针对已取消的购买,则 cancelDate 将反映亚马逊客户支持取消购买的日期。

示例: 通过 RVS 生产服务器购买订阅

以下请求将在生产服务器上验证订阅购买收据:

https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/2:SPOkNr03vVx0_u04edvPTf5t6VC-HHS4535VkVviYJp7fCvSepKM5Ys-_ODYdtw8:Y-tOqaWFAXCHluaxssj9VQ==/user/7m7UQpSnce0DcAOgcCZFVW5-sNc2rVYE6aQCGc6URNU=/receiptId/JyGJ5iEtYgFu1ngnQovTqSIHQxR53GsMLqkR1tKLp5c=:3:11
{
    "betaProduct":true,
    "cancelDate":1400784371000,
    "parentProductId":null,
    "productId":"sub1",
    "productType":"SUBSCRIPTION",
    "purchaseDate":1400784241000,
    "quantity":null,
    "receiptId":"JyGJ5iEtYgFu1ngnQovTqSIHQxR53GsMLqkR1tKLp5c=:3:11",
    "renewalDate":null,
    "term":"1 Week",
    "termSku":"sub1-weekly",
    "testTransaction":true
}

purchaseDatecancelDate 表示为以毫秒为单位的时间。您可以调用 java.util.Date(timeInMillis) 来将值转换为日期对象。cancelDatenull 指示此收据的订阅仍有效。如果收据针对已过期的订阅,则 cancelDate 将反映订阅过期的日期,或亚马逊客户支持取消它的日期。

示例: 通过 RVS 生产服务器购买权利

以下请求将在生产服务器上验证订阅购买收据:

https://appstore-sdk.amazon.com/version/1.0/verifyReceiptId/developer/2:smXBjZkWCxDMSBvQ8HBGsUS1PK3jvVc8tuTjLNfPHfYAga6WaDzXJPoWpfemXaHg:iEzHzPjJ-XwRdZ4b4e7Hxw==/user/LRyD0FfW_3zeOlfJyxpVll-Z1rKn6dSf9xD3mUMSFg0=/receiptId/mINy5VRd1FqjVOz-WBtTqw9FBGWhnuVx07kzTBMR600=:2:11

此调用将收到以下示例 JSON 响应:

{
    "betaProduct":false,
    "cancelDate":null,
    "parentProductId":null,
    "productId":"com.amazon.iapsamplev2.gold_medal",
    "productType":"ENTITLED",
    "purchaseDate":1399070221749,
    "quantity":1,
    "receiptId":"mINy5VRd1FqjVOz-WBtTqw9FBGWhnuVx07kzTBMR600=:2:11",
    "renewalDate":null,
    "term":null,
    "termSku":null,
    "testTransaction":true
}

purchaseDatecancelDate 表示为以毫秒为单位的时间。您可以调用 java.util.Date(timeInMillis) 来将值转换为日期对象。cancelDatenull 指示尚未取消购买。如果收据针对已取消的购买,则 cancelDate 将反映亚马逊客户支持取消购买的日期。