RVS for Android IAP Apps

The Receipt Verification Service (RVS) enables validation of purchases made by your app's users from your own server. RVS has two environment options, depending on whether your app is in the development/testing stage or has been published to the Amazon Appstore:

RVS Workflow

The following table shows the purchase workflow with receipt verification. RVS starts after the IAP API completes the purchase and returns the purchase receipt to the App.

Step Component Task
Step 1 App App launches the in-app purchase flow. App invokes IAP API to manage the purchase.
Step 2 IAP API IAP API interacts with the user to complete the purchase. IAP API returns a purchase receipt to the App.
Step 3 App App forwards the purchase receipt to the App Server.
Step 4 App server App server sends a request to RVS server for validation of the receipt.
Step 5 RVS server RVS server confirms that the receipt is valid.
Step 6 App server App server makes the content available to the user.

The above table replaces the following diagram, showing the RVS workflow:

You can also use RVS to enable access to a subscription purchased on another platform, such as your website, as long as the purchase was made through Amazon. The following scenario describes this workflow:

  1. Your app's user purchases a subscription through Amazon via your company's website.
  2. Your app receives a receipt for the purchased subscription
  3. To enable access, your app then sends information from the receipt to your server. Finally, your server validates this transaction by querying RVS.

Setting up RVS

Set up the RVS Sandbox separately for apps that you are testing. Once you have published your app and have it in production, you can use the RVS production server.

Setting up the RVS Sandbox

Use the RVS Sandbox along with the App Tester testing tool to verify your receipts in a test environment before you publish your app to the Amazon Appstore. The RVS Sandbox is included as a .war file in the Amazon Android SDK zip file. From the Apps & Games Services SDKs page, click the Android link to download the SDK file.

You must install the Amazon App Tester tool on an Android mobile device before you can use the RVS Sandbox.

To set up the RVS Sandbox:

  1. Install and start the Apache Tomcat server:
    1. From the Apache Tomcat website, download and install the Apache Tomcat server (version 6 or later).
    2. Configure Tomcat to use your credentials. In the /apache-tomcat-< version >/conf/tomcat-users.xml file, add the following elements:

        <tomcat-users>
            <role rolename="manager-gui"/>
            <user username="MyUserName" password="MyPassword" roles="manager-gui"/>
        </tomcat-users>
      
    3. If you have not already done so, change the permissions on the /apache-tomcat-< version >/bin/startup.sh to make the file executable:

      $ chmod +x /apache-tomcat-< **_version_** >/bin/startup.sh
      
    4. To start the Tomcat server, execute the /apache-tomcat-< version >/bin/startup.sh file.

      $ /apache-tomcat-< **_version_** >/bin/startup.sh
      
    5. Verify that the Tomcat server is running by opening a web browser window and going to: http://localhost:8080/.

      If the Tomcat server is running, you will see the Apache Tomcat/< version > homepage. Now you can deploy the RVS Sandbox.

  2. Deploy the RVS Sandbox:
    1. Unzip the Amazon Android SDK zip file.
    2. In the Amazon Android SDK folder, navigate to AmazonInAppPurchasing/tools.
    3. Locate the RVSSandbox.war file and note its file path.
    4. From the Tomcat homepage in your browser, select Manager App, and scroll to the Deploy section.
    5. From the Deploy section, navigate to WAR file to deploy > WAR file to upload > Browse > From Apps-SDK, choose the RVSSandbox.war file.
    6. Choose Deploy to view the RVSSandbox in the list of applications. Be sure that the Running column has a value of true.
    7. From the list of applications choose RVSSandbox, or in the browser visit http://localhost:8080/RVSSandbox/.

      The following confirmation message indicates that the RVS Sandbox is running:

      Receipt Verification Service Sandbox is up!
      

Setting up the Production Server

Once your app has been published and is ready to communicate with the RVS production server, you will need to set up your own server to communicate with RVS, if you didn't already do this when testing against the RVS Sandbox.The only change that you will need to make from testing against the RVS Sandbox to testing against the RVS production server is to change the host from "http://localhost:8080/RVSSandbox" to "https://appstore-sdk.amazon.com".

Amazon recommends calling RVS from only a secure server because it hosts the shared secret. Do not call this service from your app. Additionally, verify that you are using your real Shared Secret at this point. The RVS Sandbox ignores the Shared Secret, so if you were not using the correct Shared Secret in the sandbox, you will need to make sure to do so for the RVS production server. The shared secret can be found on the Shared Key page for your developer account with the Amazon Appstore:

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

You can set up your own server to your own preferences, as far as what code language you use and other options. Your server will need to be able to communicate securely with RVS via a secure protocol such as https or REST. Your server will need to be able to send requests to RVS and receive and process its responses.

As a prerequisite, you will need to add a JSON parsing library to your project, such as Jackson, to parse the JSON responses.

The following sample code is for a generic Java server. This code calls verifyReceipt() to perform the following tasks:

  1. Creates a URL string with the appropriate developer and transaction information.
  2. Connects to the Amazon RVS server and passes the transaction URL to the server.
  3. Retrieves the response from the Amazon RVS server.
  4. Passes the response to the app.

      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 Request Syntax

Use RVS to validate the Receipt objects received in a PurchaseResponse or PurchaseUpdatesResponse. These response objects contain a User ID value that denotes a unique identifier of the user. The Receipt in the PurchaseResponse contains a ReceiptId, which is used in conjunction with the User ID to perform an out-of-band server-side validation of the purchase. Requests from your server require a shared secret to be passed to confirm your identity for security.

These requests use the following format:

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

Replace the terms in the angle brackets with the following values for transaction being verified:

  • Protocol: Protocol being used to communicate with the server or sandbox, such as https:.
  • Server: URL for the RVS server that you are communicating with.
    • If you are using the RVS Sandbox, use "localhost:8080" or wherever you set up Tomcat.
    • If you are using the RVS production server, the URL is "appstore-sdk.amazon.com".
  • [RVSSandbox]: If you are using the RVSSandbox, use the value "RVSSandbox". If you are using the RVS production server, omit this field.
  • Operation_version_number: Version number of the verifyReceiptId operation. This version number is independent of the IAP version number. The current verifyReceiptId version number is "1.0".
  • Shared_secret: Shared secret used to identify the developer issuing the request. Your shared secret can be found on the Shared Key page for your developer account with the Amazon Appstore: https://developer.amazon.com/sdk/shared-key.html. For the RVSSandbox, the Shared Secret can be any non-empty string.
  • UserId: ID representing a distinct Amazon customer for your appstore app: purchaseResponse->userData->userId.
  • ReceiptId: Unique ID for the purchase: purchaseResponse->receipt->receiptId or purchaseUpdatesResponse->receipts->receipt->receiptId.

RVS Response Syntax

RVS provides a RESTful JSON API interface. As a best practice, utilize a JSON parser class for reading the JSON responses from the RVS server.

After making a request to verify a transaction, the RVS server or sandbox will return a response code indicating if the request was successful. If successful, the returned JSON response will include information about the transaction.

The following example shows a successful response:

   { "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 Response Codes

The Receipt Verification Service will respond with one of the following codes, which indicate the result of the validation check:

Response Code Description
HTTP 200 Success: The Receipt ID, User ID, and shared secret are all valid. Product Type is one of: "ENTITLED", "CONSUMABLE", or "SUBSCRIPTION"
HTTP 400 The transaction represented by this receiptId is invalid, or no transaction was found for this receiptId.
HTTP 496 Invalid sharedSecret
HTTP 497 Invalid User ID
HTTP 500 There was an Internal Server Error

RVS Response Fields for Successful Transactions

The following table lists and describes the fields included in an RVS response for a successful transaction:

Field Data Type Description
betaProduct Boolean Indicates whether the product purchased is a Live App Testing product.
cancelDate Long integer The date the purchase was cancelled, or the subscription expired. The field is null if the purchase was not cancelled.
parentProductId String Null. Reserved for future use.
productId String The SKU that you defined for this item in your app.
productType String Type of product purchased. Valid product types are CONSUMABLE, SUBSCRIPTION, and ENTITLED.
purchaseDate Long integer The date of the purchase, calculated as the number of milliseconds since the epoch. For subscription items, purchaseDate represents the initial purchase date, not the purchase date of subsequent renewals.
quantity Integer Quantity purchased. Always null or 1.
receiptID String Unique identifier for the purchase.
renewalDate Long integer The date that a subscription purchase needs to be renewed. The date is calculated as the number of milliseconds since the epoch.
term String Duration that a subscription IAP will remain valid (the term starts on the date of purchase). The term consists of a number and a time period (Day, Week, Month, Year), such as 1 Week or 2 Months.
termSku String Unique SKU that corresponds to the subscription term.
testTransaction Boolean Indicates whether this purchase was executed as a part of Amazon’s publishing and testing process.

Consider the following example where a user has a subscription that was cancelled.

  • The subscription was active from 01/01/2016 – 03/01/2016. For this receipt, the returned purchaseDate for this subscription would be 01/01/2016, while the cancelDate would be 03/01/2016.
  • If this subscription is then reactivated on 04/01/2016, the subscription will have a second receipt. The second receipt will show a purchaseDate of 04/01/2016 and a cancelDate of null.

Example RVS Calls

This section shows example RVS calls for the RVS Sandbox and RVS production server and also shows example responses for those calls. Note that these are working URLs, so if you paste the request into a browser, you should get the response paired with that request in this section.

Example: RVS Sandbox request

The following request verifies a receipt in the RVS Sandbox:

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

This call receives the following sample JSON response:

{
    "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
}

Example: Consumable Purchase Via RVS Production Server

The following request is to verify the purchase of a consumable receipt on the production server:

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

This call receives the following sample JSON response:

{
 "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
}

The purchaseDate and cancelDate are represented as time in milliseconds. You can call java.util.Date(timeInMillis) to convert the values into a date object. A cancelDate of null indicates that the purchase has not been canceled. If the receipt is for a canceled purchase the cancelDate would reflect the date the purchase was canceled by Amazon customer support.

Example: Subscription Purchase Via RVS Production Server

The following request is to verify the purchase of a subscription receipt on the production server:

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
}

The purchaseData and cancelDate are represented as time in milliseconds. You can call java.util.Date(timeInMillis) to convert the values into a date object. A cancelDate of null indicates that the subscription for this receipt is still active. If the receipt is for an expired subscription the cancelDate would reflect the date the subscription expired, or the date it was canceled by Amazon customer support.

Example: Entitlement Purchase Via RVS Production Server

The following request is to verify the purchase of a subscription receipt on the production server:

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

This call receives the following sample JSON response:

{
    "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
}

The purchaseDate and cancelDate are represented as time in milliseconds. You can call java.util.Date(timeInMillis) to convert the values into a date object. A cancelDate of null indicates that the purchase has not been canceled. If the receipt is for a canceled purchase the cancelDate would reflect the date the purchase was canceled by Amazon customer support.