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.
- A user clicks a purchase
option for digital goods in your application.

- 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" ); - 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()); } - 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.

- 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.

- The Payment Service server verifies that the digital goods are valid and processes the purchase through the payment provider.
- 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.
- 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 |
|
|
digitalGoodID |
|
|
digitalGoodName |
|
|
metadata |
|
|
purchaseAppName |
|
|
purchaseAppIcon |
|
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
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
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);
}
}
}
}