Getting Started with Qt Purchasing in QML

This guide assumes that you have registered the in-app products for your application in the external store. For more information about registering products, see Registering Products in Google Play and Registering Products in App Store

Preparing The Application

Use the following import statement in the QML files to access the Qt Purchasing QML types:

import QtPurchasing 1.14

Add the following statement to your .pro file to link against the Qt Purchasing library:

QT += purchasing

Registering Products

Before you can operate on the products in your code, they must be registered in the QML graph. You start by making a Store item, and then create each product as a child of this.

Store {
    Product {
        identifier: "consumableProduct"
        type: Product.Consumable

        // ...
    }

    Product {
        identifier: "unlockableProduct"
        type: Product.Unlockable

        // ...
    }
}

As you can see, there are consumable products and unlockable products. The former can be purchased any number of times by the same user, while the latter can only be purchased once.

The Product Declaration

For each product you must fill out the identifier, before the product can be queried from the external store. You should also always add a onPurchaseSucceeded and a onPurchaseFailed handler if you intend to provide the option to purchase the products. If you are also using the restore functionality, you should add a onPurchaseRestored handler to your unlockable products.

The signal handlers should handle the incoming transaction. Once the transaction has been handled appropriately, it should be finalized. For instance, when a purchase has succeeded, it's appropriate to save information about the purchased product in persistent storage, so that this product can still be available the next time the application launches.

The following example calls custom methods to save data about a succeeded purchase so that it survives across application runs. After verifying that the data has been stored, it finalizes the transaction. When the transaction has failed, it displays information about the failure to the user and finalizes the transaction.

Store {
    id: store
    Product {
        id: healthPotionProduct
        identifier: "healthPotion"
        type: Product.Consumable

        property bool purchasing: false

        onPurchaseSucceeded: {
            if (!hasAlreadyStoredTransaction(transaction.orderId)) {
                ++healthPotions
                if (!addHealthPotionToPersistentStorage(transaction.orderId)) {
                    popupErrorDialog(qsTr("Unable to write to persistent storage. Please make sure there is sufficient space and restart."))
                } else {
                    transaction.finalize()
                }
            }

            // Reset purchasing flag
            purchasing = false
        }

        onPurchaseFailed: {
            popupErrorDialog(qsTr("Purchase not completed."))
            transaction.finalize()

            // Reset purchasing flag
            purchasing = false
        }
    }
}

If a transaction is not finalized, it will be called again for the same transaction the next time the application starts up, providing another chance to store the data. The transaction for a consumable product has to be finalized before the product can be purchased again.

Purchasing A Product

In order to purchase a product, call the object's purchase() method. This launches a platform-specific, asynchronous process to purchase the product, for example by requesting the user's password and confirmation of the purchase. In most cases, you should make sure that the application UI is not accepting input while the purchasing request is being processed, as this is not handled automatically on all platforms.

The following example adds a button to be used with the example product in the previous section:

Rectangle {
    id: button
    width: 100
    height: 50

    Text {
        anchors.centerIn: parent
        text: qsTr("Buy health potion for only " + healthPotionProduct.price + "!")
    }

    MouseArea {
        enabled: !healthPotionProduct.purchasing && healthPotionProduct.status === Product.Registered
        anchors.fill: parent
        onClicked: {
            healthPotionProduct.purchasing = true
            healthPotionProduct.purchase()
        }
    }
}

When the button is clicked, the purchase process is started. At some point in the future, either the onPurchaseFailed handler will be called (for example if the user cancels the transaction), or the onPurchaseSucceeded handler will be called.

Note: The button is only enabled if the product's status is set to Registered. The registration process for a product is asynchronous, so purchases attempted on a product before it has been successfully registered will always fail.

Restoring Previously Purchased Products

If the application is uninstalled and subsequently reinstalled (or installed by the same user on a different device) you should provide a way to restore the previously purchased unlockable products in the external market place.

To start the process of restoring purchases, you should call the restorePurchases() method in the Store object. This will cause the onPurchaseRestored handler to be called in each of the application's unlockable products that has previously been purchased by the current user.

Continuing on the example from before, which could be some sort of role-playing computer game, lets imagine that the game has downloadable content that you can buy to expand the game further. This should be an unlockable product, because the user should not have to purchase it more than once.

Store {
    id: store

    // ... other products

    Product {
        id: dlcForestOfFooBarProduct
        identifier: "dlcForestOfFooBar"
        type: Product.Unlockable

        property bool purchasing: false

        onPurchaseSucceeded: {
            if (!hasMap("forestOfFooBar.map")) {
                if (!downloadExtraMap("forestOfFooBar.map")) {
                    popupErrorDialog(qsTr("Unable to download The Forest of FooBar map. Please make sure there is sufficient space and restart."))
                } else {
                    transaction.finalize()
                }
            }

            // Reset purchasing flag
            purchasing = false
        }

        onPurchaseFailed: {
            popupErrorDialog(qsTr("Purchase not completed."))
            transaction.finalize()

            // Reset purchasing flag
            purchasing = false
        }

        onPurchaseRestored: {
            if (!hasMap("forestOfFooBar.map")) {
                if (!downloadExtraMap("forestOfFooBar.map")) {
                    popupErrorDialog(qsTr("Unable to download The Forest of FooBar map. Please make sure there is sufficient space and restart."))
                } else {
                    transaction.finalize()
                }
            }
        }
    }
}

If a user buys the downloadable content and later either installs the game on another device or uninstalls and reinstalls the game, you can provide a way to restore the purchase, such as the following button:

Rectangle {
    id: restoreButton
    width: 100
    height: 50

    Text {
        anchors.centerIn: parent
        text: "Restore previously purchased content"
    }

    MouseArea {
        anchors.fill: parent
        onClicked: {
            store.restorePurchases()
        }
    }
}

Restoring purchases should always be done as a reaction to user input, as it may present a password dialog on some platforms. Calling the restorePurchases() method launches the restore process asynchronously. At some point in the future the onPurchaseRestored handler will be called if the product has previously been purchased.

Note: While the function behaves as documented on Android, this functionality is technically not needed there. The reason for this is that the Android device manages all unlockable purchases with no intervention from the application. If an application is uninstalled and reinstalled (or installed on a different device) on Android, then onPurchaseSucceeded will be called for each previously purchased, unlockable product when the application starts up.

© 2020 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.