Developing your application

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.

    This graphic contains a custom screen that displays a purchase option for digital goods.

  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 
    {
        Purchase purchase = 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.

    This graphic displays a BlackBerry ID login screen.

  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.

    This graphic displays a purchase confirmation screen.

  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 Purchase object to the application which 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, see the API reference for the Payment Service SDK at www.blackberry.com/developers/docs/payment/1.0api.
  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 your users with any purchase options, you can verify if in-app purchases are available to a BlackBerry® device user. You can use the PaymentEngine.getInstance method to check for the presence of the BlackBerry App World™ storefront 2.1 (a requirement for in-app purchases) and to create a PaymentEngine object if the check is successful. If unsuccessful, your application can't invoke any of the Payment Service APIs.

You might want to design your application to restrict users from seeing purchase options for digital goods if they don't have BlackBerry App World 2.1 installed on their devices.

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

PaymentEngine engine = PaymentEngine.getInstance();
if (engine != null)
{
    //code that is executed if in-app payments are available.
}    

Retrieving information about past purchases

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

Invoking getExistingPurchases() returns an array of Purchase objects. Each result in the array contains details about a purchase, such as the SKU, digital good ID, metadata to differentiate between digital goods that reference the same SKU, and the date of the purchase.

The allowRefresh parameter is a Boolean value that indicates that 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 requires the BlackBerry device user to log in using a BlackBerry® ID. To retrieve records from the device does not require that the user log in. In some cases, such as after a device switch, records of purchases might not be stored on the device, so it might necessary to query the Payment Service server. While the operation is running, the application displays a progress bar that indicates the estimated time remaining.

If you're testing your application locally, the application can only pull records from the device.

Code sample: Retrieving information about past purchases

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

Initiating a purchase

Before your application can initiate purchases by invoking PaymentEngine.purchase(), you must create a PurchaseArguments object to represent the digital goods. The Payment Service API provides a builder class for creating a PurchaseArguments object.

You can invoke the appropriate methods on the PurchaseArgumentsBuilder object depending on the information that you need to provide with the purchase request. At the minimum, you must provide either the ID or the SKU for the digital goods, for a purchase to be authenticated. The SKU is a value that you specify when you register the digital goods in the vendor portal for the BlackBerry App World™ storefront. The ID is an identifier that is assigned to your digital goods through the vendor portal.

If the purchase request is successful, a Purchase 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 
{
    Purchase purchase = 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 purchase requests

The following table describes the arguments that you can pass to the Payment Service server with a purchase request.

Argument

Description

SKU

  • Required argument (can be substituted with the ID)
  • An identifier that you assign to the digital goods when you register the digital goods in the vendor portal for the BlackBerry App World™ storefront
  • An alphanumeric string that can contain hyphens (-)and underscores (_), must not contain only numbers, and is 5 to 100 characters in length
  • If specified for a PurchaseArguments object along with the ID, the SKU is ignored
  • Added to the purchase arguments by using withDigitalGoodSku(String)

ID

  • Required argument (can be substituted with the SKU)
  • An identifier that the vendor portal for BlackBerry App World assigns to your digital goods after you register the digital goods in the vendor portal
  • If specified for a PurchaseArguments object along with the SKU, the SKU is ignored
  • Added to the purchase arguments by using withDigitalGoodId(String)

Name

  • Optional argument
  • A unique name that overrides the name that you specified for the digital goods when you registered the digital goods in the vendor portal for BlackBerry App World (the registered name might be a generic name that describes the entire batch of digital goods with the same SKU)
  • Should be specified to differentiate between the digital goods that reference the same SKU
  • Appears on the Confirm Purchase screen in your application to provide BlackBerry® device users with information about what they are purchasing
  • Added to the purchase arguments by using withDigitalGoodName(String)

Metadata

  • Optional argument
  • A unique identifier that you should specify 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)
  • Used by the Payment Service server to store the metadata value with a purchase record so that when you invoke PaymentEngine.getExistingPurchases the Purchase objects that you retrieve include the metadata value
  • Added to the purchase arguments by using withMetadata(String)

Application name

  • Optional argument
  • Overrides the name of the application that the purchase request originates from
  • 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 in the Purchase Confirmation dialog box in your application to provide users with context for what is they are purchasing
  • Added to the purchase arguments by using withPurchasingAppName(String)

Application icon

  • 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 in the Purchase Confirmation dialog box in your application to provide users with context for what they are purchasing
  • Added to the purchase arguments by using withPurchasingAppIcon(Bitmap)

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 likely want to handle exceptions differently. For example, if a BlackBerry® device user cancels a purchase, you might 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, see the API Reference for the Payment Service SDK at www.blackberry.com/developers/docs/payment/1.0api.

Create an application that features in-app purchases

The following task demonstrates how to create an application that displays a purchase option if the BlackBerry App World™ storefront 2.1 is installed on the BlackBerry® device (a requirement for in-app purchases). The application permits the BlackBerry device user to provide a SKU and name for digital goods. When the user clicks the Buy button, the application initiates a purchase request using the provided SKU and name. When the user clicks the Display Purchases button, the application displays a record of the past purchases.

  1. Import the required classes and interfaces.
    import net.rimlib.blackberry.api.payment.*;
    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 if the PaymentEngine object is created successfully (meaning that in-app purchases are available). If you want to enable local testing, invoke setConnectionMode(PaymentEngine.CONNECTION_MODE_LOCAL). Within the else statement, create an error message to display if in-app purchases are unavailable.
    if (engine != null)
    {
        engine.setConnectionMode(PaymentEngine.CONNECTION_MODE_LOCAL);
    }
    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 to see which button is pressed.
    if (field == buyButton)
    {
    
    }
    else if (field == displayButton)
    {
    
    }
  9. Within the if statement, which is executed if the Buy button is pressed, 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 in 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 in the PurchaseArguments object as a parameter. Catch the appropriate exceptions.
    try 
    {
        Purchase 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 Display Purchases button is pressed, in a try/catch block, invoke getExistingPurchases() and verify that the array of Purchase objects is not empty. 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. Catch the appropriate exceptions.
    Try
    {
        Purchase[] purchases = engine.getExistingPurchases(true);
                    	
        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");
        } 		
    } 
    catch (PaymentException e) 
    {
        Dialog.inform(e.getMessage());
    }

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

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

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 ButtonField buyButton;
        private ButtonField displayButton;
        private BasicEditField digitalGoodName;
        private BasicEditField digitalGoodSku;
    	
        private PaymentEngine engine = PaymentEngine.getInstance();
    	
        public PurchaseDemoScreen()
        {
            setTitle("Payment Service SDK Demo");

            if (engine != null)
            {
                engine.setConnectionMode(PaymentEngine.CONNECTION_MODE_LOCAL);
 
                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 
                {
                    Purchase 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)
            {
                try 
                {
                    Purchase[] purchases = engine.getExistingPurchases(true);
                	
                    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");
                    } 		
                } 
                catch (PaymentException e) 
                {
                    Dialog.inform(e.getMessage());
                }
            }
        }			    	
    }
}

Was this information helpful? Send us your comments.