Selling digital goods

Cascades provides a set of Payment Service classes that you can use to manage purchases and subscriptions directly in your app. You can use the Payment Service to:

You can use the PaymentManager class to initiate a purchase or find out about past purchases or existing subscriptions. You can use the PaymentReply class to understand whether a PaymentManager request succeeds or fails by calling the isError() function. If a request fails, errorCode() and errorText() can be used to determine why the request failed. The PaymentConnectionMode class allows you to specify a test connection mode to avoid accidental purchases.

The following classes are responses to PaymentManager transactions and other supporting classes.

A base class for responses to PaymentManager requests. PurchaseReply, PriceReply, and SubscriptionStatusReply are subclasses of DigitalGoodReply, which is a subclass of PaymentReply.

A response to PaymentManager::requestCancelSubscription(). CancelSubscriptionReply is a subclass of PaymentReply.

A response to PaymentManager::requestExistingPurchases(). ExistingPurchasesReply is a subclass of PaymentReply.

A response to PaymentManager::requestPrice(). PriceReply is a subclass of DigitalGoodReply.

A response to PaymentManager::requestPurchase() that contains a PurchaseReceipt. PurchaseReply is a subclass of DigitalGoodReply.

A response to PaymentManager::requestSubscriptionStatus(). SubscriptionStatusReply is a subclass of DigitalGoodReply.

A response to PaymentManager::requestSubscriptionTerms(). SubscriptionTermsReply is a subclass of PriceReply.

A receipt issued when the purchase of a digital good is successful. This receipt includes the updated DigitalGoodState.

An enumerated type that provides the possible states of a digital good, including whether the good is owned or a subscription, and details about the state of the subscription.

Setting up the Payment Service

The Payment Service classes are included in the BlackBerry 10 Native SDK. To download the tools that you need, visit Downloads. To learn how to create, build, and run your first Cascades project, see Create your first app. You'll also need to understand how slots and signals work because the Payment Service uses them to notify and provide results for any requests you make. To learn more about signals and slots, see Signals and slots. Before you begin, you must register your app and its digital goods in BlackBerry World. To learn more, see Registering with BlackBerry World.

The following sections describe everything you need to create in-app purchase opportunities. The code samples in these sections are based on the Payment sample app that's included on the Sample Apps page. To learn more about how to import an existing project into the QNX Momentics IDE, see Import an existing project.

Payment Service classes are included in the bb::platform library. To use these classes in your app, you must add the following to the .pro file in your project:

LIBS += -lbbplatform
payment libbplatform

You also need to include the Payment Service classes in your main.cpp file:

#include <bb/platform/PaymentManager>
#include <bb/platform/PaymentConnectionMode>
#include <bb/platform/PaymentReply>
#include <bb/platform/PurchaseReply>
#include <bb/platform/CancelSubscriptionReply>
#include <bb/platform/DigitalGoodReply>
#include <bb/platform/ExistingPurchasesReply>
#include <bb/platform/PriceReply>
#include <bb/platform/SubscriptionStatusReply>
#include <bb/platform/SubscriptionTermsReply>
#include <bb/platform/PurchaseReceipt>
#include <bb/platform/DigitalGoodState>

Before you can create in-app purchases, there are a few things you should do first:

Set the connection mode

By default, your app will contact the Payment Service server for any transactions that are made as if your app is in production. If you want to test your app (and you don't want any accidental charges), you should set the connection mode to Test. In test mode, your app won't contact the Payment Service server for any transactions. For more information, see Testing your app. The connection mode affects all Payment Service requests and is not linked to a PaymentManager instance. It's a good practice to set the connection mode in your main.cpp file when your application starts, before any PaymentManager instances are created.

PaymentManager::setConnectionMode(PaymentConnectionMode::Test);

You must not change the connection mode while Payment Service operations are outstanding. This will result in undefined behavior.

Create a payment instance

To create a purchase in your app, you must set up an instance of the PaymentManager. You can request purchases or manage subscriptions in your app with this instance of the PaymentManager.

// Instantiate a new PaymentManager instance 

PaymentManager* m_paymentManager = new PaymentManager(this);

Set the window group ID

You must set the window group ID of your PaymentManager instance so that the Payment Service can display dialog boxes. The window group ID of your Cascades application is set when your application starts. You can retrieve the window groupId and set the window group ID for your PaymentManager instance using the setWindowGroupId() function.

// Get the window group ID and pass it to the PaymentManager instance.
const QString windowGroupId = 
    bb::cascades::Application::instance()->mainWindow()->groupId();
m_paymentManager->setWindowGroupId(windowGroupId);

Set the app name

If an app is in production mode, a banner is displayed along the top of the purchase. This banner shows the name and icon of the app that the purchase is being made from (that is, your app). You can set the name and icon on the banner so that your app name is visible on the purchase screens.

It's a good practice to specify the name of the purchasing application. If an app name is not provided, the application name that was registered in BlackBerry World is used. If you need the application name later, you can use setApplicationName() to retrieve the application name.

You cannot see this banner if you have set the connection mode to Test because all transactions are simulated.

// Set the application name so that it shows up in the purchase banner.
m_paymentManager->setApplicationName("The Awesome Store");

Specify an application icon URL

The application icon is also displayed on a banner during the purchase process when an app is in production mode. The icon must be available through an external website, with a URL that uses the "http://..." format. It's a good practice to supply the icon. If an icon is not provided, the icon that was registered in BlackBerry World is used. If you need the icon later, you can use applicationIconUrl() to retrieve the icon URL.

// Set the application URL so that it shows up in the purchase banner.
m_paymentManager->setApplicationIconUrl(QUrl("http:///mycompany.com"));

Initiating a purchase

You can initiate a purchase in your app using the requestPurchase() function. You receive a response to this function when a user completes the purchase process (which may include steps such as signing in to a BlackBerry ID account, setting up a preferred payment method, and so on).

When you call the requestPurchase() function, only the ID or SKU of the digital good to be purchased is required; you don't need to provide both. If you do provide both, the ID takes precedence.

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.

In order to analyze the results of the requestPurchase(), you must first use the PaymentReply::isFinished() function to determine if the request finished. If the Payment Service server is unable to finish the request, false is returned. If the Payment Service server is able to finish the request, true is returned and you can check PaymentReply::isError() to determine 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.

The following 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 has been declared in your MyPaymentApp.hpp and implemented in your MyPayment.cpp to handle the finished() signal that is emitted when the purchase is completed.

void myPaymentApp::purchase(const QString &id, const QString &sku)
{
    //Request a purchase using the ID and SKU of the digital good. 
    const PurchaseReply *reply = m_paymentManager->requestPurchase(id, sku);
    
    // Connect the finished() signal to a slot to handle the purchase.
    connect(reply, 
            SIGNAL(finished()), 
            SLOT(handlePurchase()));
}

You will need a handlePurchase() function, also known as a slot, that determines 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 will no longer 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 call requestPurchase() the digital good name and the purchase metadata are also optional parameters, and you might use them in the following cases.

Display a specific name when a single ID or SKU represents multiple digital goods

If a game sells additional levels at a single price point, you can use a generic digital good called "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 user is aware of exactly what they are purchasing on the purchase confirmation screen.

void myPaymentApp::purchase(
   const QString &id, const QString &sku, const QString &name)
{
   //Request a purchase using the ID, SKU, and name of the digital good.
   const PurchaseReply *reply = m_paymentManager->requestPurchase(id, sku, name);

   // Connect the finished() signal to a slot to handle the purchase.
   connect(reply,
           SIGNAL(finished()),
           SLOT(handlePurchaseWithName()));
}

Display specific purchase metadata that you stored about a purchase

If a book vendor offers multiple titles at a single price point and represents them on the vendor portal as a single digital good, the ISBN of the book can be retrieved as purchase metadata and displayed on the purchase confirmation screen.

void myPaymentApp::purchase(
    const QString &id, const QString &sku, const QString &metadata)
{
    //Request a purchase using the ID, SKU, and metadata of the digital good. 
    const PurchaseReply *reply = m_paymentManager->
        requestPurchase(id, sku, metadata);

    // Connect the finished() signal to a slot to handle the purchase.
    connect(reply, 
            SIGNAL(finished()), 
            SLOT(handlePurchaseWithMetadata()));
}

Responding to a purchase

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:

  • The isFinished() function is used to determine if the requestPurchase() finished.

  • The isError() function is used to determine if the requestPurchase() succeeded. 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.

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

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

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

  • You can use the digitalGoodSku to find the SKU of a digital good that you supplied to requestPurchase(). 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 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 has been declared in your MyPaymentApp.hpp and implemented in your MyPayment.cpp to handle the finished() signal that is emitted when the purchase is completed.

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

You can extract the information from the PurchaseReceipt with a receiptToString function:

static QString receiptToString(bb::platform::PurchaseReceipt r)
{
  const QString initialPeriod = QString::number(r.initialPeriod());
  const QDateTime startDate = r.startDate();
  const QString startDateStr = startDate.isNull() ? "N/A" : startDate.toString();
  const QDateTime endDate = r.endDate();
  const QString endDateStr = endDate.isNull() ? "N/A" : endDate.toString();
  const QString isSubscr = r.isSubscription() ? "true" : "false";
  const QString itemStateStr = QString::number(static_cast<int>(r.state()));

  const QString displayString = "Date: " + r.date().toString() +
      "\nID/SKU: " + r.digitalGoodId() + "/" + r.digitalGoodSku() +
      "\nPurchaseID/licenseKey: " + r.purchaseId() + "/" + r.licenseKey() +
      "\nMetadata: " + r.purchaseMetadata() +
      "\nItemState/isSubscription?: " + itemStateStr + "/" + isSubscr +
      "\nStart/End: " + startDateStr + "/" + endDateStr +
      "\nInitialPeriod: " + initialPeriod + "\n";

    return displayString;
}

Checking for past purchases

If your app needs a list of digital goods that a user already purchased (to avoid offering a digital good that the user already owns for sale), you can use the PaymentManager::requestExistingPurchases() function. This function retrieves all the purchases that a user made as an ExistingPurchasesReply including purchases that a user made before an upgrade, or purchases that a user made after your app was removed and downloaded again.

The ExistingPurchasesReply class 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 ExistingPurchasesReply:

  • The isFinished() function is used to determine if the requestPurchase() finished.

  • The isError() function is used to determine whether the requestPurchase() succeeded or not. 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.

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

The functions in the ExistingPurchasesReply can be used to get a list of user purchases. This list is returned as a Qlist of PurchaseReceipt objects.

The requestExistingPurchases() function accepts a parameter called forceServerRefresh, which is a Boolean value that indicates whether the list of past purchases should be refreshed from the server when a request is made. A value of true indicates that data is refreshed from the server, and a value of false means that a cached list of purchases is returned.

When requestExistingPurchases() finishes, the  existingPurchasesFinished() signal is emitted for your PaymentManager  instance and the PaymentReply::finished()  signal is emitted on the ExistingPurchasesReply instance.

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

The following 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 a ExistingPurchasesReply. Then it connects the finished() signal to a handleExistingPurchases() function to handle the finished() signal that is emitted when the request is completed. The handleExistingPurchases() function should be declared in your MyPaymentApp.hpp and implemented in your MyPayment.cpp.

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

    // Connect the finished() signal to a slot to handle the existing purchases.
    connect(reply, 
            SIGNAL(finished()), 
            SLOT(handleExistingPurchases()));
}

You can also generate a receipt for each purchase using the ExistingPurchasesReply class. Here's a code sample of a function created to find existing purchases and produce a readable string of each receipt. The function which 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, format a user readable string 
                // that represents the receipt for that purchase.

                QString displayString;
                Q_FOREACH(PurchaseReceipt r, receipts) {
                    displayString += (receiptToString(r) + "\n");
                }
                emit existingPurchasesResponseSuccess(displayString);
            }
        }
    } else {
        qDebug() << "Request existing purchases request did not finish.";
    }

    reply->deleteLater();
}

Managing a subscription

You can use the following functions in PaymentManager to manage subscriptions in your app:

Request the terms of a digital good subscription

You can use the SubscriptionTermsReply class to interpret the results of a call to requestSubscriptionTerms(). The requestSubscriptionTerms() function is similar to all other PaymentManager requests. You can check that the requestSubscriptionTerms call finished using the SubscriptionTermsReply::finished() signal or the PaymentManager::subscriptionTermsFinished()  signal.

When you call the requestSubscriptionTerms() function, only the ID or SKU of the digital good to be purchased is required; you don't need to provide both. If you do provide both, the ID will take precedence.

void MyPaymentApp::getSubscriptionTerms(const QString &id, const QString &sku)
{
    if (id.isEmpty())
        return;

    const SubscriptionTermsReply *reply = 
        m_paymentManager->requestSubscriptionTerms(id, sku);

    connect(reply, 
            SIGNAL(finished()), 
            SLOT(subscriptionTermsReply()));
}

Here's a code sample that shows how you can handle the SubscriptionTermsReply. You can display the subscriptions terms in your UI when the subscriptionTermsResponseSuccess() signal is emitted or you can display an error with the error code and text in your UI when the infoResponseError() signal is emitted.

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

    // Emit an error signal if there were errors.

    if (reply->isError()) {

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

    // Emit a success signal if the request succeeded.

    } else {
        qDebug() << "Sub terms response success. Price: " << reply->price() <<
            "\nInitialPeriod: " << reply->initialPeriod() <<
            "\nRenewalPrice: " << reply->renewalPrice() <<
            "\nRenewalPeriod: " << reply->renewalPeriod();

        emit subscriptionTermsResponseSuccess(reply->price(), 
                                              reply->initialPeriod(), 
                                              reply->renewalPrice(), 
                                              reply->renewalPeriod());
    }

    reply->deleteLater();
}

Check the status of a subscription

You can use the SubscriptionStatusReply class to interpret the results of your call to requestSubscriptionStatus().

When you call the requestSubscriptionStatus() function, only the ID or SKU of the digital good to be purchased is required; you don't need to provide both. If you do provide both, the ID will take precedence.

You can use a new constant, PaymentManager::Subscription_App, as the digitalGoodId in a call to the requestSubscriptionStatus() function to check the status of an app that is priced as a subscription item in BlackBerry World.

void MyPaymentApp::checkSubscriptionStatus(const QString &id, 
                                           const QString &sku)
{
    if (id.isEmpty())
        return;

    const SubscriptionStatusReply *reply =
        m_paymentManager->requestSubscriptionStatus(id, sku);

    connect(reply,
            SIGNAL(finished()),
            SLOT(subscriptionStatusResponse()));
}

Here's a code sample that shows how you can handle the SubscriptionStatusReply:

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

    // Emit an error signal if there were errors.

    if (reply->isError()) {

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

    // Emit a success signal if the request succeeded.

    } else {

        emit checkStatusResponseSuccess(reply->isActive());
    }

    reply->deleteLater();
}

Cancel a subscription

To cancel a subscription to a digital good, you have to use the ID of the subscription from the receipt of the purchase or from the list of previous successful purchases. You can retrieve previous purchases using PaymentManager::RequestExistingPurchases(). You can use the CancelSubscriptionReply class to interpret the results of your call to RequestCancelSubscription().

void MyPaymentApp::cancelSubscription(const QString &purchaseId)
{
    if (purchaseId.isEmpty())
        return;

    const CancelSubscriptionReply *reply = 
        m_paymentManager->requestCancelSubscription(purchaseId);

    connect(reply, 
            SIGNAL(finished()), 
            SLOT(cancelSubscriptionResponse()));
}

Here's a code sample that shows how you can handle the CancelSubscriptionReply:

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

    // Emit an error signal if there were errors.

    if (reply->isError()) {

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

    // Emit a success signal if the request succeeded.
    } else {

        emit cancelSubscriptionResponseSuccess(reply->isCanceled());
    }

    reply->deleteLater();
}

Last modified: 2013-03-21