IN-APP PURCHASE API CODING RECOMMENDATIONS

***This page has been removed from the VDC due to the Verizon Apps shutdown. Feel free to browse our other development areasdevicesAPIs and Tools.***

VERIZON APPS SUNSET

***NOTE: Effective November 1st, 2012, the VDC no longer accepts new Verizon App submissions. Additionally, beginning December 2012, final billing to customers for all existing Subscriptions and In-App Purchase apps will occur, and all paid Subscriptions and In-App Purchase API apps will be removed from the catalog on November 1st, 2012. Customers will receive a final full month of use & access, before Verizon Apps begins its complete shutdown in January 2013.

For additional details, please review the Verizon Apps Notice and Verizon Apps Transition Timeline.***

IN-APP API CODING RECOMMENDATIONS

The following sections provide more details around coding to the In-App Purchase SDK.  Make sure you reference the javadocs, the call flow diagram, and sample application provided in the SDK download as you work your way through this page.  Remember you can email questions to VCast-Apps@verizonwireless.com or post to our forums if you have specific questions.

ANDROID MANIFEST PERMISSIONS

In order for the application to access Verizon Apps to perform the any In-App Purchase functionality or license checking, permissions must be set in the manifest file.  Simply paste the following permissions into the AndroidManifest.xml file before the closing manifest tag:

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.START_BACKGROUND_SERVICE"/>
<uses-permission android:name="com.verizon.vcast.apps.VCAST_APPS_LICENSE_SERVICE"/>

IN-APP PURCHASE API: CALL FLOW DIAGRAM

The screenshot below lists all the API calls required, in order of operation, as well as the required parameters and return values of each call.  The diagram describes the flow of calls to display a list of In-App content to an end user but the sequence of discovery and purchase calls applies to most situations.  For additional detail of each API class, refer to the javadocs contained within the SDK and continue on below.

Flow_Diagram_FINAL
IN-APP ITEM CATALOG MANAGEMENT

Before jumping into the API flows in more detail it is important to understand how developers should manage their own catalog of In-App content to offer when using the In-App Billing API.  Developer maintained catalogs can be either:

  • Static
    • In-App catalog of items is stored within the application. Updates to pricing and items require resubmission of binary to VDC portal
    • Examples:
      • Game that offers five advanced levels for purchase.
      • Developer sells in-game currency for in-app purchases.
  • Dynamic
    • In-App catalog of items is accessed via developer-owned remote server with In-App content submitted in the VDC portal.
    • Examples:
      • E-book retailer that has a fluctuating catalog of items where prices change on regular basis. Content requires download from developer server.
      • Newspaper app releases special issues when significant events occur.

Note: Maintaining a static In-App catalog will require you to submit a new binary to the VDC Portal when you want to change prices, update names, etc.  If you plan to constantly update your catalog it is recommended that you go the Dynamic catalog route.

Whether you’ve chosen Static or Dynamic catalog, at minimum, developers should map the following in their catalogs:

  • itemName (contentType)
  • Exact price
  • Unique name of item
  •  Specific developer defined SKU for each in-app, can be same as name or any value.

Following the example on the page "Setting Up In-App Content in the VDC Portal", you might store the following level information in the table below in a flat file, database on the Android device, or on your own server that maps your level offerings to the options you’ve submitted in the VDC Portal.  When you query Verizon servers for the offerings you would match the values coming from the server to what is stored to ensure what is displayed to the end user lines up with what is in the Portal:

ContentType                 Price                ItemName/SKU                  PriceType        

'Game - In App'

’1.99’

'Weapon 1’

’First Download’

'Game - In App'

’2.99’

'Weapon 2’

’First Download’

'Game - In App'

’3.99’

'Weapon 3’

’First Download’

'Game - In App'

’9.99’

’Big Weapon’

’First Download’

'Game - In App'

’1.99’

'Level 6'

’Subscription’

'Game - In App'

’4.99’

'Level 7'

’Subscription’

'Game - In App'

’5.99’

'Level 8'

’Subscription’



Maintaining a mapping of contentType->price->name->SKU is critical since the final purchase call to Verizon backend systems requires passing these values back to our servers. Name and price are charged to the customer (name & price appears on customer bill) while the SKU serves as the keyword that’s used when calling checkInAppContentLicense to ensure a valid license exists for that piece of content.

ITEM NAMING REQUIREMENTS FOR BILLING

When an end user purchases an In-App item, you will make an call to purchaseInAppContent, providing back to Verizon Wireless details about the transaction that were collected during the flow of API calls as well as the final price and itemName.  These values are will be sent back to Verizon billing servers and eventually will end up on end users monthly statement as a line-item. 

 

You must use descriptive itemNames for In-App content so end users will be able to identify what they purchased on their bill.  There is a limitation of 23 characters that are available for use and itemNames should use the following schema when generating the string:

 

<App Name>-<Descriptive Item Name>

 

Continuing the example from Section 3, if the app name is ‘RadicalApp’, examples of valid itemNames for billing might be:

                RadicalApp-Weapon1

                RadicalApp-Big Weapon

                RadicalApp-Level6

                ….

The final items on the customer statement are prefixed with ‘In-App:’+itemName.

Note: Failure to follow naming guidelines could result in your app being pulled from Verizon Apps

STEP-BY-STEP IN-APP CALL FLOW

The sections below describe the call flows step-by-step to display a list of all purchasable in-app content to the end user.  Reference the call flow diagram in Section 6.3.1  as well as javadocs in the SDK for additional details. Unless otherwise specified, the method calls and objects referenced below are contained in the InAppPurchasor class.  Whenever a license is checked, methods will reside in the LicenseAuthenticator class.

 

At a high-level the In-App call flow consists of:

  • Discover items that were submitted in the VDC Portal
  • Access developer catalog of items
  • Match the contentType, price of catalog items with VDC portal content
  • Display to end user for purchase.
  •  Call Verizon server with itemName, price to complete transaction.

A typical scenario would be displaying your options for purchase via a menu or button; for example a “Buy More Items” button.  The following steps to your catalog, displaying to the end user, and calling for purchase.  The flows are easily customizable to your specific requirements.  (Note that some of the values that are returned during the “Discovery Process, Step 1 below, should be saved and used in the final purchase call.  These items are indicated in italics):

 

  1. Get list of In-App Items available from the VDC Portal

a)  Call getInAppContents method with the app keyword and DiscoveryParameters object.  DiscoveryParameters object defines how the In-App content is returned (ascending order, sorting options, pagination options etc.)  Default will return all In-App content, and for ease of use is recommended.

b) getInAppContents returns an InAppContents object.  InAppContents contains an array of Items representing the content submitted in the portal.  Each item has the following fields:

  • itemID: unique identifier of the In-App content.  Required for final purchase call.
  • itemName: Content Type as specified during submission (Virtual good (Games), Virtual good (Applications), eBooks, eZines (digital magazines), Newspapers (digital), Wallpapers.  The itemName is used in matching your content however in the final purchase step please modify this value using the recommendations in Section 6.3.4 above
  •  itemDescription: currently not available, for future In-App functionality

c)  Call getInAppContentOffer method with the app keyword and itemID from (a) above.  Returns an InAppContentOffers object that contains an array of the Offer objects that are available for that content type.  Each Offer object also contains:

  • offerID: unique string created automatically by the backend.  Required for final purchase call
  • maxPrice: set by developer
  • minPrice: set by developer
  • priceType: set by developer during submission  Required for final purchase call.
  • priceLine: based on priceType, Required for final purchase call.
  • pricingTerms: based on priceType, Required for final purchase call.

The priceLine, priceType, and pricingTerms are all required for making the final purchase transaction and are accessible from the Offer object.  After matching of Item/Offer (refer to #2 below), the correct Offer object should be maintained and used to grab these values when making the final purchase request (refer to #3 below).

Below is the mapping of the various options that are returned from the Verizon server for PriceLine, PriceType, and Pricing Terms:

Name                                                      String Returned from VZW Server to App, per price option

PriceLine

$0.00 monthly subscription

$0.00 for X day(s)

$0.00 one time charge

$0.00 per download

PriceType

Subscription

Per Period

First Download

Recurring Download

Pricing Terms

monthly subscription, Offer

use for X day(s), Offer

unlimited uses, Offer

unlimited uses of 1 download, Offer

   

 

Important Notes:

  • itemName is the ContentType of In-App item in order for developers to specify type of in-app purchase, refer to #2 below for the mapping of values that are returned.  The purpose is to ensure correct billing codes are implemented on backend.  Also, some developers may offer different pricing ranges/types for several content types in-app (e.g., e-book app that also sells magazines and wallpaper)
  • From the calls above the arrays of Items and Offer objects are used to construct the purchase request by pulling out the offerID, itemID, priceLine, priceType, and pricingTerms

  2. Access developer catalog of In-App items, match to VDC Items and Offers, display to user

 

  • Logic in your app should match the VDC content to the developer catalog items.  When matching itemNames, developers must track their submissions in the VDC Portal and maintain their own mapping of ContentType, price, unique name of item, and a specific developer defined SKU
    • The following shows what is returned in the itemName parameter based on contentType submitted for matching purposes:

VDC Portal Submission 

itemName String returned for matching

Virtual Goods – Game       

Game - In App

Virtual Goods – App          

Application - In App

Wallpaper                        

Wallpaper - In App

eBook                             

eBooks - In App

eMagazine                       

eZines - In App

eNewspaper                     

eNews - In App

  • Match developer catalog to Item itemName (contentType) and Offer price range(min/max) and priceType
  • Display all items that match to the contentType and fall within the price range to the end user.

3. User selects item for purchase, submit purchase request

  • Activities that make the purchase request must subclass the InAppActivity abstract class and override the onPurchaseResult method.  All code that handles return values from purchase request should be implemented in this method, refer to sample app SampleApp1Activity in the sample application from the SDK for an example implementation.
    • Overriding InAppActivity is required because the architecture of the purchase function requires the Verizon Client to display a pop-up purchase confirmation window in front of the developer application context. 
  • After extending the InAppActivity class in your purchase activity, call the purchaseInAppContent method, passing the app keyword, itemID from (1b) above, and a PurchaseParameters object that contains details of the item being purchased
    • PurchaseParameters: developer creates the object and defines the SKU (developer  defined ID for item, can be any value), price of content, and the name of the In-App item being purchased. Set the PurchaseParameters field “offerID” to same value as (1c) above.  Remember to follow the guidelines for naming as proposed in the section "Item Naming Requirements for Billing" above.

 

InAppPurchasor.PurchaseParameters purchaseParameters = inAppPurchasor.new PurchaseParameters();

// Name that is billed on customer’s monthly statement

purchaseParameters.setInAppName(finalItem.getItemName());

          purchaseParameters.setPriceLine(finalOffer.getPriceLine());

          purchaseParameters.setPriceType(finalOffer.getPriceType());

          purchaseParameters.setPricingTerms(finalOffer.getPricingTerms());

          purchaseParameters.setOfferID(finalOffer.getOfferID());

          purchaseParameters.setSku(sku);

          purchaseParameters.setPrice(price);

Note: SKU will be the unique string used to check the valid In-App content license so must be unique for each piece of In-App content you will offer in your app.

 

4. Purchase request returns and stores a license

  • Request is passed through Verizon Apps client to Verizon billing system and delivered to customer bill at the price and with the name of the item specified in the PurchaseParameters object from #3 above.
  • Handling of return values should only be done in the overridden method onPurchaseResult of the developer activity that extends InAppActivity .
    • The parameter ‘purchaseResult’ is of type PurchaseInAppContentResult  and contains the following methods (among others):
      • getResult(): used to see if the purchase was successful (InAppPurchasor.PURCHASE_OK) or failed (InAppPurchasor.PURCHASE_FAILED).  Adds each In-App purchase to the existing signed license stored on the device.(refer to appendix for additional license details).  Based on the result, content can be immediately unlocked.
      • getLicense(): used if developers would like to programmatically check the license (refer to appendix) or send it off to remote server for further processing
  • Use the onPurchaseResult callback method to either unlock the content or prevent access – return values other than PURCHASE_OK indicate a PIN issue or transactional error, refer to sample code below:

 

@Override

protected void onPurchaseResult(PurchaseInAppContentResult

                                purchaseResult)

{

      if(purchaseResult.getResult() == InAppPurchasor.PURCHASE_OK){

            // Change dialog message to show end user success          

            pd.setMessage("Purchase Successful.  Hit OK to continue");

            //Unlock content and permit user to continue               

      }

      else{

                       

            pd.setMessage("There was a transaction error purchasing hit

                      OK to continue  ", "Transaction Error");

      }    

     

5. Check License of unlocked In-App Content

  • How often licenses are checked for in-app content is up to individual developer however the likely scenario is each time a user accesses the unlocked content the license is checked.  Create an instance of the LicenseAuthenticator class and call the LicenseAuthenticator.checkInAppContentLicense method with the keyword of the
    “parent”app and the developer defined SKU(defined when purchaseInAppContent method called) when In-App Content is accessed by user, refer to appendix for more license details.
  • Handle LicenseReturn codes as described in the Subscriptions Coding Recommendations section.
  • Note that any calls to checkInAppContentLicense must be called in a thread to ensure proper operation, refer to the Android Threading Requirements section below for more details.
ANDROID THREADING REQUIREMENTS

All calls that are shown in the diagram in the previous section must be called from a thread.  The discovery process and subsequent purchase calls are long running network calls and must never hold onto the main UI thread.  Refer to the sample app for examples of using the AsyncTask class to make these calls in the background of your app, specifically in the PurchaseLevels.java file where this functionality is defined.  If you don’t use threading you will likely risk testing failure and could result in delays of publication. 

Below is an example of using the AsyncTask class to call the various function, refer to the inline comments and sample app for more detail:

At a high-level, usage of AsyncTask works as follows, refer to inline comments:

Create the AsyncTask class that we will execute:

new AsyncTask<Object, Integer, InAppPurchasor.InAppContentOffers>()

{

ProgressDialog progressDialog;

Override the onPreExecute method in order to block the UI from allowing user to stop the network discovery call of In-App items (getInAppContents and getInAppContentOffers):

@Override

protected void onPreExecute()

{

// Show progress dialog.

      progressDialog = ProgressDialog.show(PurchaseLevel.this, "",

"Getting Next Level Information from server...",true);

}

Override the doInBackground method call that will perform the function of main In-App method calls and pass that data to the onPostExecute method for processing:

@Override

protected InAppPurchasor.InAppContentOffers doInBackground(Object... params){

try {                           

      // Check the license of the current level

      LicenseAuthenticator licAuth = new LicenseAuthenticator(c);

CheckLicenseResult licCheck =

licAuth.checkInAppContentLicense(getResources().getString(R.string.app_keyword), c.levelsManager.currentLevel.name);         

// Check the result

      int result = licCheck.result;                          

      // If the license is OK display set the currentLevel to the

// next level from the levelsManager

      // And prep for display of purchase options.

      if(result== 0 || result==1){

            // Show end user the level and return.......

      }                          

      // Else continue on with the discovery of In-App items                           

      // In this simple app we do not need any specific Discovery Parameters

   because we do not do paging or sorting of In Apps.

      // This is why we don't call any set methods of discoveryParams

      DiscoveryParameters discoveryParams = inAppPurchasor.new DiscoveryParameters();                          

      // Perform the call to get the available In Apps,

      InAppPurchasor.InAppContents availableLevelsInApps = inAppPurchasor.getInAppContents(keyword, discoveryParams);

      // Check the availableLevelsInApps InAppContents return and match it to what's being selected for display

                             

      // Get the Offer for the level we are purchasing.

      InAppPurchasor.InAppContentOffers levelOffers = inAppPurchasor.getInAppContentOffer(keyword, levelItem.getItemID());                         

      //Return the offers to onPostExecute for processing

      if(levelOffers.getResult() == InAppPurchasor.LIST_REQ_OK){

            return levelOffers;

      } else {

            return null;

      } 

}

Override the onPostExecute method call that will make the final call to complete the transaction:

@Override

protected void onPostExecute(final InAppPurchasor.InAppContentOffers levelOffers){

      // In case we have no offers to the user we display a message.

      if(levelOffers == null || levelOffers.getResult() != InAppPurchasor.LIST_REQ_OK || offerCount ==0){          

            //If no offers, return

      }   

      else {      

            for(int i=0; i<offerCount; i++){                  

      // Display the level details and pricing details for each button available for display to end user.

                  TextView purchaseOfferTextView = (TextView)offerHolder.findViewById(R.id.purchase_offer_title);

                                                      purchaseOfferTextView.setText(levelItem.getItemName());                        

      //set up the buy button

      Button buyButton = (Button)offerHolder.findViewById(R.id.buy_button);

      buyButton.setOnClickListener(new View.OnClickListener(){

      public void onClick(View v){

            // Setup the purchase call

            levelOffer = levelOffers.getOffers()[index];

            InAppPurchasor.PurchaseParameters purchaseParameters = inAppPurchasor.new PurchaseParameters( 

//Setup the final PurchaseParameters object with the proper name, price, offerID, itemID, etc.                                   

      purchaseParameters.setContentSize(1);

      purchaseParameters.setInAppName(levelItem.getItemName());

      purchaseParameters.setOfferID(levelOffer.getOfferID());

      purchaseParameters.setSku(c.levelsManager.currentLevel.name");

           

               purchaseParameters.setPriceLine(levelOffer.getPriceLine());

              purchaseParameters.setPriceType(levelOffer.getPriceType());

                                                           purchaseParameters.setPricingTerms(levelOffer.getPricingTerms());                                           

      purchaseParameters.setPrice(levelOffer.getMinPrice());

      int result = inAppPurchasor.purchaseInAppContent(keyword, levelItem.getItemID(), purchaseParameters);         

      if(result == InAppPurchasor.PURCHASE_INITIATION_OK)

      {

            return;

      }

      else

      {

           showOKDialog(PurchaseLevel.this, "Purchase Initiation Failed", new DialogInterface.OnClickListener()

            {

            public void onClick(DialogInterface dialog, int which)

            {

                 

                  return;                            

    

            ………….

            ………..                        

           

            }.execute();

      }