Would you like to tell us how we are doing?

You bet No thanks

Sorry about the red box, but we really need you to update your browser. Read this excellent article if you're wondering why we are no longer supporting this browser version. Go to Browse Happy for browser suggestions and how to update.

Developing your application

Retrieving available goods

You can call DigitalGoods.get() to retrieve a list of all the digital goods available for purchase within your application.

You can retrieve available SKUs for your application by invoking DigitalGoods.get(DigitalGoodsListingListener listener), which uses a callback listener. When invoking this asynchronous method, consider the following points:

  • To prevent garbage collection, assign the callback object to a class field so that the calling application maintains a reference for the listener. This will prevent garbage collection.
  • Do not use a synchronous wrapper because it blocks the event thread, and the callback does not return.

Invoking get() returns an array of DigitalGood objects through the Success method of the DigitalGoodsListingListener. Each result in the array contains details about a purchase, such as the SKU, vendor, name of the digital good, short and long descriptions, and the PriceSet of the digital good. Details of the digital good are stored locally on the BlackBerry device.

Code sample: Retrieving details about digital goods

private DigitalGoodsListingListener listener;
public void getChildItems() {
    listener = new DigitalGoodsListingListener() {
            
        public void error( String message, int errorCode ) {
            // Handle error
        }
        
        public void success( DigitalGood[] digitalGoods ) {
            // Handle the digital goods
        }
    };
        
    DigitalGoods.get(listener);
}

Process flow: Purchasing digital goods

The process for purchasing digital goods is designed to be consistent for each payment type and for all BlackBerry device users.

  1. A user clicks a purchase option for digital goods in your application.


  2. The application creates a PurchaseArguments object which contains information about the digital goods.
    PurchaseArgumentsBuilder arguments = new PurchaseArgumentsBuilder()
        .withDigitalGoodSku( "abc123" )
        .withDigitalGoodName( "Adventures of a BlackBerry Java Developer" )
        .withPurchasingAppName( "My eBooks" )
        .withMetadata ( "ISBN 34560202010" );
  3. The application invokes PaymentEngine.purchase(PurchaseArguments) to initiate the purchase request and send information about the digital goods to the Payment Service server.
    try 
    {
        PurchaseResult purchaseResult = engine.purchase(arguments.build());
    } 
    catch (PaymentException e) 
    {
        Dialog.inform(e.getMessage());
    }
  4. If the user isn't logged in using a BlackBerry ID account, the application prompts the user to provide login information without requiring that the user leave the application.


  5. The application prompts the user to confirm the purchase using the default payment type. The user can change the payment type or set up a new payment type.


  6. The Payment Service server verifies that the digital goods are valid and processes the purchase through the payment provider.
  7. Depending on the results of the purchase attempt, one of the following events occurs:
    • If the purchase request is successful, the Payment Service server returns a PurchaseResult object to the application that contains details about the successful purchase. If the application utilizes a content server, the Payment Service server can send information about the successful purchase to the content server as well.
    • If the purchase request is unsuccessful, the application throws an exception. For information about handling exceptions, view the API reference for the Payment Service SDK.
  8. If the purchase attempt is successful, the application provides the digital goods to the user in the manner that you designed.

Verifying that in-app purchases are supported on the device

Before you present BlackBerry device users with purchase options, you can verify that in-app purchases are available. You can use the PaymentEngine.isAppWorldInstalledAndAtCorrectVersion() method to check for the presence of the minimum required version of the BlackBerry World storefront. If the required version isn't available, your application can't invoke any of the Payment Service APIs.

You may want to design your application to restrict users from seeing purchase options for digital goods if they don't have BlackBerry World 3.1 installed on their devices. You can also catch the AppWorldUpdateRequired exception that can be thrown, and then prompt the user to upgrade their BlackBerry World client to the latest version by calling the PaymentEngine.upDateAppWorld() method.

The following code sample demonstrates how to create a PaymentEngine object, and verify that in-app purchases are supported on the device.

try{
	if (PaymentEngine.isAppWorldInstalledAndAtCorrectVersion()) {
		PaymentEngine engine = PaymentEngine.getInstance();
		//code that is executed if in-app payments are available
    }
} catch(AppWorldUpdateRequired awur) {
	//launches browser for user to upgrade their BlackBerry App World client
    PaymentEngine.upDateAppWorld();
}

Retrieving information about past purchases

You can retrieve information about past purchases by invoking PaymentEngine.getExistingPurchases(boolean allowRefresh) for a synchronous call, or PurchaseHistory.get(PurchaseHistoryListingListener listener) for an asynchronous call. A record of past purchases is necessary if you want to customize your application to reflect past purchases, or if you want to implement a subscription model that initiates recurring purchases.

Invoking getExistingPurchases() returns an ExistingPurchasesResult object. You can retrieve an array of Purchase objects that represent existing purchases by invoking ExistingPurchasesResult.getPurchases(). Each result in the array contains details about a purchase, such as the SKU, digital good ID, metadata and the date of the purchase.

The allowRefresh parameter is a Boolean value that indicates whether the application can retrieve the records of purchases from the Payment Service server, or that the application can only retrieve records that are stored on the BlackBerry device. To retrieve records from the Payment Service, log in using a BlackBerry ID. To retrieve records from the BlackBerry device, you do not need to log in. You may choose to query the Payment Service server after a device switch because records of purchases might not be stored on the BlackBerry device. An indeterminate progress dialog appears while the user's purchase history is retrieved from the Payment Service server.

Code sample: Using a synchronous call to retrieve information about past purchases

try
{
    ExistingPurchasesResult result = engine.getExistingPurchases(true);
    Purchase[] resultPurchases = result.getPurchases();
}
catch (PaymentException e)
{
    //code that is executed if an exception is thrown
}

Code sample: Using an asynchronous call to retrieve information about past purchases

private PurchaseHistoryListingListener listener;
public void getPurchaseHistory() {
    listener = new PurchaseHistoryListingListener() {
            
        public void error( String message, int errorCode ) {
            // Handle error
        }
        
        public void success( Purchase[] purchases ) {
            // Handle the purchases
        }
    };
    
    PurchaseHistory.get(listener);
}

Initiating a purchase

Before initiating a purchase by invoking PaymentEngine.purchase(), create a PurchaseArguments object to represent the digital goods. The Payment Service API provides a builder class for creating a PurchaseArguments object on the device.

You can invoke the appropriate methods on the PurchaseArgumentsBuilder object depending on the information that you provide with the purchase request. For a purchase to be authenticated, you must provide either the SKU or the ID for the digital goods. The SKU is a value that you specify when you register the digital goods in the vendor portal for the BlackBerry World storefront. You can invoke DigitalGoods.get() to retrieve the SKUs for the digital goods. To determine which SKUs are available during runtime, call DigitalGoods.get(). The ID is an identifier that is assigned to your digital goods in the vendor portal.

If the purchase request is successful, a PurchaseResult object is returned, which contains information about the purchase. If the purchase is unsuccessful, an exception is thrown.

Code sample: Creating a PurchaseArguments object and initiating a purchase

PurchaseArgumentsBuilder arguments = new PurchaseArgumentsBuilder()
    .withDigitalGoodId( "1234" )
    .withDigitalGoodName( "My Product" )
    .withDigitalGoodSku( "Ab34t2eC" )
    .withPurchasingAppName( "My Application" );
try 
{
    PurchaseResult result = engine.purchase(arguments.build());
} 
catch (IllegalArgumentException iae) 
{
    //code that is executed if an exception is thrown
} 
catch (PaymentException pe) 
{
    //code that is executed if an exception is thrown
}

Arguments for purchases

The following table describes the arguments that you can pass to PaymentEngine.purchase().

Argument

Description

digitalGoodSKU

  • Required argument (can be substituted with the ID).
  • An identifier that you assign to digital goods when you register the digital goods in the BlackBerry App World vendor portal.
  • An alphanumeric string that can contain hyphens (-) and underscores (_), must not contain only numbers, and is 5 to 100 characters in length.
  • If specified along with the ID, the SKU is ignored.

digitalGoodID

  • Required argument (can be substituted with the SKU).
  • An identifier that the vendor portal assigns to your digital goods after you register the digital goods in the vendor portal.
  • If specified along with the SKU, the SKU is ignored.

digitalGoodName

  • Optional argument.
  • A name that overrides the name that you specified for the digital goods when you registered the digital goods in the vendor portal (the registered name might be a generic name that describes an entire batch of digital goods).
  • Appears on the purchase confirmation dialogs in your application to provide users with context for what they are purchasing.

metadata

  • Optional argument.
  • A unique identifier that is required if you need to differentiate between the digital goods that reference the same SKU (for example, if your application sells eBooks, the identifier could be an ISBN number).
  • Returned with each Purchase object when you make a getExistingPurchases() call.

purchaseAppName

  • Optional argument.
  • Overrides the application name retrieved from the application descriptor.
  • Should always be specified if the name of the application, as it appears on the Home screen of the device, can change dynamically (for example, if the application name changes when the icon for the application receives focus).
  • Appears on the purchase confirmation dialogs in your application to provide users with context for what they are purchasing.

purchaseAppIcon

  • Optional argument
  • Overrides the icon for the application that the purchase request originates from
  • Should always be specified if the icon for the application, as it appears on the Home screen of the device, can change dynamically (for example, if the icon changes when new content is available in the application).
  • Appears on the purchase confirmation dialogs in your application to provide users with context for what they are purchasing.

Managing subscriptions

If you choose to offer subscription-based digital goods in your application, you can use the Payment Service API to manage the subscriptions.

If you sell your application as a subscription on the BlackBerry World storefront, the Payment Service confirms the validity of each user subscription. You can invoke purchase history methods to return objects that represent the parent application. These methods verify the user's current subscription status to determine whether the user should continue using the application.

Verifying application subscriptions

If you list your application as a subscription, you can verify the validity of a user's subscription by invoking PaymentEngine.checkExisting(), with the application's SKU as the parameter. This method returns a Boolean value that displays the validity of a user's subscription. If a user's subscription is not valid, you can use the following code sample to direct users to re-purchase the application in BlackBerry World:
Browser.getDefaultSession().displayPage(“http://appworld.blackberry.com/webstore/clientlaunch/<content ID>/”);

Verifying digital good subscriptions

If you list digital goods as a subscription, you can verify the validity of a user's subscription by invoking PaymentEngine.checkExisting(), with the digital good's SKU as the parameter. This method returns a Boolean value that displays the validity of a user's subscription. If a user's subscription is not valid, you can prompt users to re-purchase the digital good by invoking PaymentEngine.Purchase().

Determining the status of a subscription

You can invoke Purchase.getItemState() to determine the status of a purchase. This method returns a constant that indicates the current status. These constants are included in the Purchase interface, and are described in the following table:

Constant

Description

SUBSCRIBED

This constant indicates that the subscription is active. Users can use the digital goods.

CANCELED

This constant indicates that the subscription was canceled. Users can still use the digital goods until the next renewal date.

REFUNDED

This constant indicates that the subscription was purchased, but subsequently refunded. Users cannot use the digital goods.

RENEW

This constant indicates that the subscription was renewed and is currently active. Users can use the digital goods.

OWNED

This constant indicates that the digital goods are not subscription-based, and that the user owns the digital goods.

In addition to the constants that are listed above, there are two additional methods in the PaymentEngine class that can help you determine the status of a subscription. You can invoke PaymentEngine.checkExisting() and provide an SKU as an argument. This method checks to see whether the user who is currently logged in using a BlackBerry ID is eligible to use the digital goods that are associated with the SKU that you provide. For example, if a subscription has a status of CANCELED, this method returns true until the next renewal date, because the user is eligible to use the digital goods until that date. After that date, this method returns false. If a subscription has a status of REFUNDED, this method returns false, because the user is not eligible to use the digital goods. After you invoke checkExisting(), you can invoke Purchase.get() with the same SKU to retrieve the Purchase object that corresponds to the SKU.

Subscription-based digital goods include both an initial time period and a renewal time period. For some digital goods, you might want to specify a custom initial time period that has a lower price (for example, if your digital goods include a trial period at a reduced cost). You can specify a custom initial time period when you register your digital goods on the vendor portal, and you can retrieve this time period by invoking Purchase.getInitialSubscriptionPeriod(). You can also invoke Purchase.getStartDate() to retrieve the date that a subscription starts, and you can invoke Purchase.getEndDate() to retrieve the date that a subscription expires.

Retrieving the price of a subscription

You can invoke PaymentEngine.getPrice() to retrieve the price of a subscription. You must provide an SKU as an argument to getPrice(), and this method returns a PriceSet object that contains the price of the purchase that the SKU corresponds to. After you retrieve the PriceSet object, you can invoke PriceSet.getPriceSetValue() and pass one of the following constants in the PriceSet interface to retrieve the corresponding information:

  • PRICE: The constant price of digital goods that are not subscription-based.
  • SUBSCRIPTION_INITIAL_PRICE: The initial price of subscription-based digital goods.
  • SUBSCRIPTION_RENEWAL_PRICE: The renewal price of subscription-based digital goods.
  • SUBSCRIPTION_PERIOD_NAME: The renewal time period of subscription-based digital goods (for example, "30 days").
  • SUBSCRIPTION_INITIAL_PERIOD: The initial time period of subscription-based digital goods.

Canceling a subscription

You can invoke PurchaseEngine.cancel() to cancel an existing subscription. You must provide the transaction ID of the subscription-based digital goods as an argument to this method. After you cancel a subscription, the status of the subscription changes to CANCELED. However, the user who purchased the subscription is eligible to use the digital goods until the next renewal date. After that date, the user can no longer use the digital goods.

Handling exceptions

The Payment Service API provides a number of exceptions that help give you fine-grained control over how your application handles errors that might occur during the in-app purchase process. You should make sure to catch the appropriate exceptions when you invoke the purchase() and getExistingPurchases() methods. Each method can throw a number of different exceptions, but you might not need to catch all of them unless you want to process each exception differently. The UserCancelException class, PaymentServerException class, DigitalGoodNotFoundException class, and IllegalApplicationException class are all subclasses of the PaymentException class, so if you configure your application to catch only PaymentException, you can process all the exceptions the same way.

There are cases where you will want to handle exceptions differently. For example, if a BlackBerry device user cancels a purchase, you may want to display a particular message, or if a purchase fails because of a problem with the Payment Service, you might want to retry the purchase. Each exception has a human-readable error message that you can display by invoking getMessage() on the exception.

For descriptions of all the exceptions that can occur, view the API reference for the Payment Service SDK.

Create an application that features in-app purchases

The following task demonstrates how to create an application that displays an in-app purchase option on the BlackBerry World storefront 3.1. When the user clicks Buy, the application initiates a purchase request using the provided SKU and name. When the user clicks Display Purchases, the application displays a record of past purchases.
  1. Import the required classes and interfaces.
    import net.rimlib.blackberry.api.paymentsdk.*;
    import net.rim.device.api.ui.*;
    import net.rim.device.api.ui.component.*;
    import net.rim.device.api.ui.container.*;
  2. Create the application framework by extending the UiApplication class. In main(), create an instance of the new class and invoke enterEventDispatcher() to enable the application to receive events. In the application constructor, invoke pushScreen() to display the custom screen for the application. In the following code sample, the PurchaseDemoScreen class, described in step 3, represents the custom screen.
    public class PurchaseDemo extends UiApplication
    {
        public static void main(String[] args) 
        {
            PurchaseDemo theApp = new PurchaseDemo();
            app.enterEventDispatcher();  
        }
        public PurchaseDemo() 
        {   
            pushScreen(new PurchaseDemoScreen());
        }
    }
  3. Create the framework for the custom screen by extending the MainScreen class and implementing FieldChangeListener.
    private static class PurchaseDemoScreen extends MainScreen implements FieldChangeListener
    {
        public PurchaseDemoScreen()
        {
    
        }
    }
  4. In PurchaseDemoScreen, declare the application's instance variables and create an instance of the PaymentEngine class by invoking getInstance().
    private ButtonField buyButton;
    private ButtonField displayButton;
    private BasicEditField digitalGoodName;
    private BasicEditField digitalGoodSku;
    
    private PaymentEngine engine = PaymentEngine.getInstance();
  5. In the screen constructor, create an if statement to check whether the PaymentEngine object is created successfully (meaning that in-app purchases are available). Within the else statement, create an error message to indicate that in-app purchases are unavailable.
    if (engine != null)
    {
    
    }
    else
    {
        errorMessage = new RichTextField("Sorry, in-app purchases are unavailable");
        add(errorMessage);
    }
  6. Within the if statement, create the UI for the application. The UI features two BasicEditField objects that allow the user to type a SKU and a name for digital goods, and two ButtonField objects that either initiate a purchase or display a record of past purchases. Set change listeners on the buttons.
    digitalGoodName = new BasicEditField("Digital Good Name:  ", "Sample Good");
    add(digitalGoodName);
    
    digitalGoodSku = new BasicEditField("Digital Good SKU:   ", "abc123");
    add(digitalGoodSku);
            		
    HorizontalFieldManager hfm = new HorizontalFieldManager();
    add(hfm);
     		
    buyButton = new ButtonField("Buy");
    buyButton.setChangeListener(this);
    hfm.add(buyButton);
            		
    displayButton = new ButtonField("Display Purchases");
    displayButton.setChangeListener(this);
    hfm.add(displayButton);
  7. In PurchaseDemoScreen, override fieldChanged() to customize the behavior that is invoked when a user clicks a button.
    public void fieldChanged(Field field, int context)
    {
    
    }
  8. Within fieldChanged() create an if statement that checks which button is clicked.
    if (field == buyButton)
    {
    
    }
    else if (field == displayButton)
    {
    
    }
  9. Within the if statement, which is executed if the user clicks Buy, invoke getText() on the BasicEditField objects to obtain the current SKU and name. Create a PurchaseArguments object by using the PurchaseArgumentsBuilder class and invoke the appropriate methods to pass the SKU and the name as arguments.
    String name = digitalGoodName.getText();
    String sku = digitalGoodSku.getText();
            		
    PurchaseArgumentsBuilder arguments = new PurchaseArgumentsBuilder()
        .withDigitalGoodSku( sku )
        .withDigitalGoodName( name )
        .withMetadata( name )
        .withPurchasingAppName( "Payment Service SDK Demo" );
  10. In a try/catch block, invoke purchase() and pass the PurchaseArguments object as a parameter. Catch the appropriate exceptions.
    try 
    {
        PurchaseResult purchase = engine.purchase(arguments.build());
        Dialog.inform("Purchase of " +purchase.getMetadata()+ " is successful.");
    }
    catch (IllegalArgumentException e) 
    {
        Dialog.inform(e.getMessage());
    }
    catch (PaymentException e) 
    {
        Dialog.inform(e.getMessage());
    }
  11. Within the else if statement, which is executed if the user clicks Display Purchases, invoke PurchaseHistoryListingListener with an anonymous class to execute the method's callback. Verify that the Purchase objects are not empty by invoking getExistingPurchases(). Create an if statement that deletes any records of past purchases that are already displayed on the screen. Create a for loop that prints the SKU and the name of the digital goods from the updated record of past purchases. Invoking PurchaseHistory.get() returns an array of purchase history objects through the success method of the PurchaseHistoryListeningListener. Pass the listener to PurchaseHistory.get().

    purchaseHistoryListener = new PurchaseHistoryListingListener() {
    
      public void error( String message, int errorCode ) {
        Dialog.inform( message );
      }
    
      public void success( Purchase[] purchases ) {
        if( purchases.length != 0 ) {
          if( getFieldCount() > 3 ) {
            deleteRange( 3, ( getFieldCount() - 3 ) );
          }
          for( int i = 0; i < purchases.length; i++ ) {
            RichTextField purchaseRecord = new RichTextField( "Name: " +purchases[ i ].getMetadata()+ " SKU: " + purchases[ i ].getDigitalGoodSku() );
            add( purchaseRecord );
          }
        } else {
          Dialog.inform( "No existing purchases" );
        }
      }
    };
    
    PurchaseHistory.get(purchaseHistoryListener);
    

Code sample: Creating an application that features in-app purchases

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rimlib.blackberry.api.paymentsdk.*;
import net.rimlib.blackberry.api.paymentsdk.purchaseHistory.*;

public class PurchaseDemo extends UiApplication {
    public static void main( String[] args ) {
        PurchaseDemo app = new PurchaseDemo();
        app.enterEventDispatcher();
    }

    public PurchaseDemo() {
        pushScreen( new PurchaseDemoScreen() );
    }

    private static class PurchaseDemoScreen extends MainScreen implements FieldChangeListener {
        private BasicEditField parentSku;
        private ButtonField buyButton;
        private ButtonField displayButton;
        private BasicEditField digitalGoodName;
        private BasicEditField digitalGoodSku;
        private PurchaseHistoryListingListener purchaseHistoryListener;
        private PaymentEngine engine = PaymentEngine.getInstance();

        public PurchaseDemoScreen() {
            setTitle( "Payment Service SDK Demo" );
            if( engine != null ) {
                parentSku = new BasicEditField( "Parent SKU: ", "abc123" );
                add( parentSku );
                digitalGoodName = new BasicEditField( "Digital Good Name: ", "Sample Good" );
                add( digitalGoodName );
                digitalGoodSku = new BasicEditField( "Digital Good SKU: ", "abc123" );
                add( digitalGoodSku );
                HorizontalFieldManager hfm = new HorizontalFieldManager();
                add( hfm );
                buyButton = new ButtonField( "Buy" );
                buyButton.setChangeListener( this );
                hfm.add( buyButton );
                displayButton = new ButtonField( "Display Purchases" );
                displayButton.setChangeListener( this );
                hfm.add( displayButton );
            } else {
                RichTextField errorMessage = new RichTextField( "Sorry, in-app purchases are unavailable" );
                add( errorMessage );
            }
        }

        public void fieldChanged( Field field, int context ) {
            if( field == buyButton ) {
                String name = digitalGoodName.getText();
                String sku = digitalGoodSku.getText();
                PurchaseArgumentsBuilder arguments = new PurchaseArgumentsBuilder().withDigitalGoodSku( sku )
                        .withDigitalGoodName( name ).withMetadata( name ).withPurchasingAppName( "Payment Service SDK Demo" );
                try {
                    PurchaseResult purchase = engine.purchase( arguments.build() );
                    Dialog.inform( "Purchase of " + purchase.getMetadata() + " is successful." );
                } catch( IllegalArgumentException e ) {
                    Dialog.inform( e.getMessage() );
                } catch( PaymentException e ) {
                    Dialog.inform( e.getMessage() );
                }
            } else if( field == displayButton ) {
                purchaseHistoryListener = new PurchaseHistoryListingListener() {

                    public void error( String message, int errorCode ) {
                        Dialog.inform( message );
                    }

                    public void success( Purchase[] purchases ) {
                        if( purchases.length != 0 ) {
                            if( getFieldCount() > 3 ) {
                                deleteRange( 3, ( getFieldCount() - 3 ) );
                            }
                            for( int i = 0; i < purchases.length; i++ ) {
                                RichTextField purchaseRecord = new RichTextField( "Name: " + purchases[ i ].getMetadata()
                                        + " SKU: " + purchases[ i ].getDigitalGoodSku() );
                                add( purchaseRecord );
                            }
                        } else {
                            Dialog.inform( "No existing purchases" );
                        }
                    }
                };

                PurchaseHistory.get(purchaseHistoryListener);
            }
        }
    }
}