In-App purchasing demo

A complete mobile application that demonstrates purchasing in-app products.

Note: The Qt Purchasing module was one of the Removed Modules in Qt 6.0, and the code is now contained within this example as a guide on how you can use Qt to integrate with common marketplaces.

What is this demo?

This demo is a mobile game application based on the classic word guessing game Hangman, where vowels can be purchased through the demo's internal store. In the game you will be presented with a row of dashes, representing letters of the word to guess. By guessing a correct letter that occurs in the given word, the letter will be placed on the corresponding dash or dashes in the word. By guessing every letter of the word or just guessing the whole word correctly at any time the game is over and you have won. If the guess is wrong, one element of a hanging stick figure is drawn. Once the figure is complete, you are out of guesses and you lose.

The demo shows how it is possible to offer in-app products inside a Qt application, for the Android and iOS platforms. In order to test the in-app purchase functionality in the the demo, you must first register the application and its products in the external store. For an introduction on how to do this, see the guides for Google Play and App Store respectively.

3rd party app stores

The in-app products must be registered in the target stores, before they can be queried or purchased in an application. We recommend using the same identifiers for the products in each store, as it simplifies the code to query and purchase the products.

How does the demo work

The demo is a QML application that registers QML types to access information about in-app products, as well as to request purchases for those products. These are registered in the external store for the target platform

In-app purchasing are added to application by first adding a Store object. In the demo the Store object is created by the MainView component that is loaded on application startup.

Store {
    id: iapStore
}

The demo defines a component for displaying a store for purchasing in-app products made available. These products must be first registered with the store object we created above in MainView. There are two products available, the first being a consumable type.

Product {
    id: product100Vowels
    store: iapStore
    type: Product.Consumable
    identifier: "qt.io.demo.hangman.100vowels"

    onPurchaseSucceeded: {
        console.log(identifier + " purchase successful");
        //Add 100 Vowels
        applicationData.vowelsAvailable += 100;
        transaction.finalize();
        pageStack.pop();
    }

    onPurchaseFailed: {
        console.log(identifier + " purchase failed");
        console.log("reason: "
                    + transaction.failureReason === Transaction.CanceledByUser ? "Canceled" : transaction.errorString);
        transaction.finalize();
    }
}

This consumable product provides 100 additional vowels to be used when guessing words in the game. When it is successfully purchased, we update the state of the application to include 100 additional vowels. Then we call finalize() on the transaction object to confirm to the platform store that the consumable product has been provided.

The second product is a non-consumable type that will unlock vowels permanently in the future. In addition to updating the application state on purchase, we must make sure to provide a way to restore this purchase on other devices used by the end user. In this case we create a signal handler for onPurchaseRestored.

Product {
    id: productUnlockVowels
    type: Product.Unlockable
    store: iapStore
    identifier: "qt.io.demo.hangman.unlockvowels"

    onPurchaseSucceeded: {
        console.log(identifier + " purchase successful");
        applicationData.vowelsUnlocked = true;
        transaction.finalize();
        pageStack.pop();
    }

    onPurchaseFailed: {
        console.log(identifier + " purchase failed");
        console.log("reason: "
                    + transaction.failureReason === Transaction.CanceledByUser ? "Canceled" : transaction.errorString);
        transaction.finalize();
    }

    onPurchaseRestored: {
        console.log(identifier + " purchase restored");
        applicationData.vowelsUnlocked = true;
        console.log("timestamp: " + transaction.timestamp);
        transaction.finalize();
        pageStack.pop();
    }
}

In addition to registering the products, the demo also provide an interface to actually purchase the registered product. The demo defines a custom component called StoreItem to display and handle the purchasing interaction.

Product {
    id: productUnlockVowels
    type: Product.Unlockable
    store: iapStore
    identifier: "qt.io.demo.hangman.unlockvowels"

    onPurchaseSucceeded: {
        console.log(identifier + " purchase successful");
        applicationData.vowelsUnlocked = true;
        transaction.finalize();
        pageStack.pop();
    }

    onPurchaseFailed: {
        console.log(identifier + " purchase failed");
        console.log("reason: "
                    + transaction.failureReason === Transaction.CanceledByUser ? "Canceled" : transaction.errorString);
        transaction.finalize();
    }

    onPurchaseRestored: {
        console.log(identifier + " purchase restored");
        applicationData.vowelsUnlocked = true;
        console.log("timestamp: " + transaction.timestamp);
        transaction.finalize();
        pageStack.pop();
    }
}

The StoreItem component will display the product data that is queried from the platform's store, and will call the purchase() method on the product when it is clicked by the user.

Text {
    id: titleText
    text: product.title
    font.bold: true
    anchors.right: priceText.left
    anchors.rightMargin: topLevel.globalMargin
    anchors.top: parent.top
    anchors.topMargin: topLevel.globalMargin
    anchors.left: parent.left
    anchors.leftMargin: topLevel.globalMargin
}

Text {
    id: descriptionText
    text: product.description
    anchors.right: priceText.left
    anchors.rightMargin: topLevel.globalMargin
    anchors.left: parent.left
    anchors.leftMargin: topLevel.globalMargin
    anchors.top: titleText.bottom
    anchors.topMargin: topLevel.globalMargin / 2
    wrapMode: Text.WordWrap
}

Text {
    id: priceText
    text: product.price
    anchors.right: parent.right
    anchors.rightMargin: topLevel.globalMargin
    anchors.verticalCenter: parent.verticalCenter
}

MouseArea {
    anchors.fill: parent
    onClicked: {
        pendingRect.visible = true;
        spinBox.visible = true;
        statusText.text = "Purchasing...";
        storeItem.state = "PURCHASING";
        product.purchase();
    }
    onPressed: {
        storeItem.state = "PRESSED";
    }
    onReleased: {
        storeItem.state = "NORMAL";
    }
}

Android and iOS use the base classes. From base classes there are derivative classes for android and ios:

In-App purchases

In-app purchases are a way to monetize an application. These purchases are made from inside the application and can include anything from unlocking content to virtual items. The demo uses the system APIs for in-app purchases, which means the purchase process is more familiar to the user, and the information already stored by the platform (such as credit card information) can be used to simplify the purchase process.

Licenses and attributions

In regards to deploying the demo on Android see Android GNU C++ Run-time Licensing for more information.

Files:

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