Selling digital goods

The Payment Service classes are included in the BlackBerry 10 Native SDK. Before you set up the Payment Service, you must register your app and its digital goods in the BlackBerry World storefront. To learn more, see Registering with BlackBerry World.

To get started adding digital goods to your app, see Setting up the Payment Service.

Initiating a purchase

After you create a payment instance in your app, you can initiate a purchase in your app. You receive a response to this request when a user completes the purchase process (which may include steps such as signing in to a BlackBerry ID and setting up a preferred payment method).

When you request a purchase, only the ID or SKU of the digital good to be purchased is required; you don't need to provide both. If the ID and SKU are both provided, then the ID is used. To use the SKU, you must pass an empty string for the digitalGoodId.

The following QML code sample uses a Container to represent a digital good that can be sold. A PaymentManager object is attached to the Container to handle purchase requests and a TapHandler is attached to the Container to trigger purchases.

When the user taps the control that represents the digital good, the tapped() signal is emitted and requestPurchase() is called, passing in the SKU and the name of the digital good. When the purchase is complete, the purchaseFinished() signal is emitted and the purchase is stored locally to track digital goods that the user already owns.

import bb.cascades 1.0
import bb.platform 1.2

// This Container represents a DigitalGood that can be sold. 

Container {
    attachedObjects: [
        PaymentManager {
            id: digitalGoodPaymentManager
            onPurchaseFinished: {
                if (reply.errorCode == 0) {
                    // Store this purchase to keep track
                    // of the purchases made by the user.
                } else {
                    console.log("Error: " + reply.errorInfo);
                }
            }
        }
    ]

    // Giving this control an ID allows it to be referenced
    // explicitly from methods where context is lost.

    id: digitalGood

    // Handle taps by the user as a purchase attempt.
    gestureHandlers: TapHandler {
        onTapped: {
            if (digitalGood.owned) {
                // Display a message to the user, or 
                // display details about the digital good 
                // that they already own.
            } else {
                digitalGoodPaymentManager.requestPurchase("", 
                    digitalGood.sku, 
                    digitalGood.name);
            }
        }
    }
}

The following C++ code sample requests a purchase, passing in an ID and a SKU to requestPurchase(). The response is returned as a PurchaseReply. The function connects the finished() signal to a handlePurchase() function that is declared in your MyPaymentApp.hpp file and implemented in your MyPaymentApp.cpp file.

When requestPurchase() finishes, the  PaymentManager::purchaseFinished() signal is emitted for your PaymentManager instance and the PaymentReply::finished()  signal is emitted on the PurchaseReply instance.

The PurchaseReply class inherits the finished() signal from the PaymentReply class.

To analyze the results of the requestPurchase(), you can use PaymentReply::isError() to check if the request was successful.

If isError() returns true, you can use errorCode and errorText to present a dialog box or toast to your user. If isError() returns false, you can use the PurchaseReply class and the receipt() function or the purchaseMetadata() function to present information about the successful purchase to the user.

void MyPaymentApp::purchase(const QString &id, 
    const QString &sku)
{
    // Request a purchase using the ID and SKU of the
    // digital good. Only one is required, but the ID 
    // may be empty and the SKU will be used. 
    const PurchaseReply *reply = 
        m_paymentManager->requestPurchase(id, sku);
    
    // Connect the finished() signal to a 
    // slot to handle the purchase.

    bool success = QObject::connect(reply, 
         SIGNAL(finished()),
         this, 
         SLOT(handlePurchase()));

    if (success) {
       // Signal was successfully connected.

    } else {
       // Failed to connect to signal.
    }
}

You need a handlePurchase() function, also known as a slot, that checks if the purchase finished and succeeded, and emits the appropriate signals.

You should not delete the PurchaseReply object in the slot connected to the finished() signal because you won't be able to access the receipt and metadata in the calling function. You can use QObject::deleteLater() to schedule the PurchaseReply for deletion later.

void MyPaymentApp::handlePurchase()
{
    bb::platform::PurchaseReply *reply = 
        qobject_cast<bb::platform::PurchaseReply*>(sender());
    Q_ASSERT(reply);

    // Check to see if the request finished. 
    if (reply->isFinished()) {
    	
    	// Emit an error signal if there were errors.
        if (reply->isError()) {

            emit infoResponseError(reply->errorCode(), 
                    reply->errorText());

        // Emit a success signal if there were no errors. 
        } else {
            const QString displayString = 
                receiptToString(reply->receipt());

            emit purchaseResponseSuccess(displayString);
        }
    } else {
        qDebug() << "Purchase request did not finish.";
    }

    reply->deleteLater();
}

When you request a purchase, the digital good name and the purchase metadata are optional parameters, and you might use them in the following scenarios.

Using a single ID, name, or SKU to represent multiple digital goods

If it is impractical to create a unique name and SKU for each of your digital goods, you can use the same name and SKU for multiple digital goods. Any digital good that is part of a group of digital goods with the same name and SKU must be registered as consumable in the BlackBerry World vendor portal. Static digital goods can be sold only once, consumable digital goods can be sold many times. For more information about registering different types of digital goods, see Registering with BlackBerry World.

Information that differentiates each digital good in the group (for example, which level was purchased) must be placed in the metadata field.

The technique of creating a single name and SKU for multiple digital goods lets you update the purchase options without submitting a new version of your app.

For example, if a game sells additional levels at a single price point, you can use a generic digital good with the name "My game level" for all such levels. When a user makes a purchase, the game app should override "My game level" with the name of the level that the user purchased. This approach makes sure that the users are aware of exactly what they are purchasing on the purchase confirmation screen.

Not available

void MyPaymentApp::purchaseSpecificGood(const QString &id, 
    const QString &sku, 
    const QString @name, 
    Const QString @metadata )
{
    // Request a purchase using the ID, SKU, name, and metadata.
    // The metadata specifies unique information about this
    // digital good, which is in group of digital goods that 
    // share the same ID and SKU. You must pass a metadata 
    // argument that allows you to differentiate between digital 
    // goods that reference the same SKU.

    const PurchaseReply *reply = 
      m_paymentManager->requestPurchase(id, sku, name, metadata);
    
    // Connect the finished() signal to a 
    // slot to handle the purchase.

    bool success = QObject::connect(reply, 
         SIGNAL(finished()),
         this, 
         SLOT(handlePurchase()));

    if (success) {
       // Signal was successfully connected.

    } else {
       // Failed to connect to signal.
    }
}

Using specific purchase metadata

You can use the same name and SKU for multiple digital goods and use the metadata to handle purchases of this digital good. When you request a purchase in your app, you can pass a metadata argument that allows you to differentiate between digital goods that reference the same SKU.

For example, if a book vendor offers multiple titles at a single price point ($4.99) and represents the titles on the vendor portal as a single digital good ("Romance novel"), the ISBN of the book and the title of the book can be retrieved as purchase metadata and displayed on the purchase confirmation screen instead of the digital good name.

Responding to a purchase

Not available

Your app can use the PurchaseReply class to let your user know what happened during a PaymentManager::requestPurchase(). The PurchaseReply contains a PurchaseReceipt that you can use to display a purchase receipt.

The PurchaseReply class is a subclass of the DigitalGoodReply class, which is a subclass of the PaymentReply class. The functions and signals that are inherited from the PaymentReply class are used for checking if a request to PaymentManager finished and succeeded. The following information can be found in the PurchaseReply:

  • To check if the requestPurchase() finished, you can use the isFinished() function.

  • To check if the requestPurchase() succeeded, you can use the isError() function. If the purchase was successful, the PurchaseReply object contains the data you requested, and data that is related to the result of the request that you can display in your UI.

  • If the purchase was not successful, the errorCode() and errorText() functions can be used to present a dialog box or toast to your user.

The functions that are inherited from the DigitalGoodReply class are used to find the ID and SKU of the digital good.

  • To find the ID of a digital good that you supplied to requestPurchase(), you can use the digitalGoodId() function. The ID is empty if you did not supply an ID to requestPurchase().

  • To find the SKU of a digital good that you supplied to requestPurchase(), you can use the digitalGoodSku() function. The SKU is empty if you did not supply a SKU to requestPurchase().

The functions in the PurchaseReply class are used to find the receipt for a successful purchase and the metadata associated with the successful purchase.

  • If isError() returns false, you can use the receipt() function or the purchaseMetadata() function to present information about the successful purchase to the user.

The following C++ code sample requests a purchase, passing in an ID and a SKU to requestPurchase(). The response is returned as a PurchaseReply. Then it connects the finished() signal to a handlePurchase() function that is declared in your MyPaymentApp.hpp file and implemented in your MyPaymentApp.cpp file.

void MyPaymentApp::handlePurchase()
{
    bb::platform::PurchaseReply *reply = 
        qobject_cast<bb::platform::PurchaseReply*>(sender());
    Q_ASSERT(reply);

    // Check to see if the request finished 
    if (reply->isFinished()) {
    	
    	// Emit an error signal if there were errors.
        if (reply->isError()) {

            emit infoResponseError(reply->errorCode(), 
                reply->errorText());

        //Emit a success signal if there were no errors. 
        } else {
            const QString displayString = 
                receiptToString(reply->receipt());

            emit purchaseResponseSuccess(displayString);
        }
    } else {
        qDebug() << "Purchase request did not finish.";
    }

    reply->deleteLater();
}

Checking for past purchases

If your app needs a list of digital goods that a user already purchased (for example, to avoid offering a digital good that the user already owns), you can retrieve the existing purchases that a user made. These purchases include the purchases that a user made before an upgrade and the purchases that a user made after your app was removed and downloaded again.

In the following QML sample, a PaymentManager instance is added as an attached object. When the NavigationPane that represents the UI of the app emits the creationCompleted() signal, existing purchases are retrieved from the local cache and then requestExistingPurchases(false) is called to request purchases from the BlackBerry World cache.

You can see this technique in more detail in the Freemium sample app.

import bb.cascades 1.2
import bb.platform 1.2

NavigationPane {
    id: navigationPane

    attachedObjects: [
        PaymentManager {
            id: rootPaymentManager
            applicationIconUrl: "http://mycompany.com/100x100.png"
            onExistingPurchasesFinished: {
                if (reply.errorCode == 0) {
                    // Store the purchases
                } else {
                    console.log("Error: " + reply.errorText);
                }
            }
        }
    ]
    Page {
        // Set up the UI of your app with a store
        // and a game to play.
    }
    onCreationCompleted: {
        rootPaymentManager.setConnectionMode(0);
        // As soon as the page loads, retrieve any 
        // past purchases from local store then cache.
        PurchaseStore.retrieveLocalPurchases();
        rootPaymentManager.requestExistingPurchases(false);
    }
}

The code sample above uses a function defined in C++ to retrieve existing purchases from the local cache.

// Grab all purchases from cache and emit a signal for each. 
// The signal makes it very easy for our app to listen for
// each good found.

void PurchaseStore::retrieveLocalPurchases() {
	QStringList listOfDigitalGoodSkus = 
        m_digitalGoodsStore.value(
			"digitalGoods").toStringList();
	if (!listOfDigitalGoodSkus.isEmpty()) {
	foreach(QString digitalGoodSku, listOfDigitalGoodSkus) {
		emit purchaseRetrieved(digitalGoodSku);
		}
	}
}

The following C++ code sample requests the list of existing purchases by using the requestExistingPurchases() function, passing in a Boolean value to refresh the data from the server. The response is returned as an ExistingPurchasesReply. Then the function connects the finished() signal to a handleExistingPurchases() function to handle the finished() signal. The handleExistingPurchases() function should be declared in your MyPaymentApp.hpp file and implemented in your MyPaymentApp.cpp file.

You can see this technique in more detail in the Payment Service sample app.

void MyPaymentApp::getExistingPurchases(bool refresh)
{
    //Request the existing purchases from the server.
    const ExistingPurchasesReply *reply = paymentManager->
        requestExistingPurchases(refresh);

    // Connect the finished() signal to a slot to 
    // handle the existing purchases.
        
    bool success = QObject::connect(reply, 
         SIGNAL(finished()),
         this,
         SLOT(handleExistingPurchases())); 

    if (success) {
       // Signal was successfully connected.

    } else {
       // Failed to connect to signal.
    }                          
}

You can generate a receipt for each purchase by using the ExistingPurchasesReply class. Here's a code sample of a function that finds existing purchases and produces a readable string of each receipt. The function that converts the PurchaseReceipt to a displayable string is the same function that was shown in Responding to a purchase.

void MyPaymentApp::handleExistingPurchases()
{
    bb::platform::ExistingPurchasesReply *reply = 
        qobject_cast<bb::platform::ExistingPurchasesReply*>(sender());
    Q_ASSERT(reply);

    // Check to see if the request finished.
    if (reply->isFinished()) {

    	// Emit an error signal if there were errors.
        if (reply->isError()) {
            emit infoResponseError(reply->errorCode(), 
                reply->errorText());

        //Emit a success signal if there were no errors.
        } else {

            const QList<PurchaseReceipt> receipts = 
                reply->purchases();

            if (receipts.isEmpty()) {
                emit existingPurchasesResponseSuccess("(No purchases)");
            } else {
                // For each purchase, emit a signal that 
                // represents the receipt for that purchase.
                Q_FOREACH(PurchaseReceipt r, receipts) {
                    emit skuPurchased(r);
                }
            }
        }
    } else {
       qDebug() << "Request existing purchases request didn't finish.";
    }
    reply->deleteLater();
}

Best practices

Minimize network requests To minimize the number of requests to the Payment Service and limit your app's network and data usage, you can provide a way for the user to refresh existing purchases from the server in your UI. You can implement a button or action that allows the user to restore past purchases. You can request existing purchases with a option to get purchases from the BlackBerry World server when the button is tapped.

Use the local cache If you can't add a refresh option to your UI, you can request existing purchases from the BlackBerry World local cache (on the device) when your app starts. If the local cache is stale, the purchase history is automatically retrieved from the server.

Store purchases locally when your app requires them You should store a record of the purchases that a user makes because digital goods represent features or goods in your app. You can use a QSettings object. The QSettings class provides persistent platform-independent application settings across sessions. You can use the values in QSettings to determine which features of your app should be available. You should update these settings when successful purchases are made, so that the user can use the new features right away.

Keep your stored purchases in sync There are a few scenarios where your QSettings can become out of sync. For example, the user makes a purchase on one device (such as a BlackBerry PlayBook) then tries to use the purchase on another device (such as a BlackBerry 10 device). Or, the user restores a device to a backup made before certain purchases were made. It's a good practice to either provide a manual way for users to retrieve past purchases (by using a button or menu item on the screen) or perform periodic requests to get purchases from the BlackBerry World server.

Last modified: 2014-11-17



Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus