| /**************************************************************************** |
| ** |
| ** Copyright (C) 2015 The Qt Company Ltd. |
| ** Contact: http://www.qt.io/licensing/ |
| ** |
| ** This file is part of the Purchasing module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL3-COMM$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see http://www.qt.io/terms-conditions. For further |
| ** information use the contact form at http://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPLv3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qinappproductqmltype_p.h" |
| #include "qinappstoreqmltype_p.h" |
| #include <QtPurchasing/qinapptransaction.h> |
| #include <QtPurchasing/qinappstore.h> |
| #include <QtCore/qcoreevent.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| /*! |
| \qmltype Product |
| \inqmlmodule QtPurchasing |
| \since QtPurchasing 1.0 |
| \ingroup qtpurchasing |
| \brief A product for in-app purchasing. |
| |
| Product contains information about a product in the external market place. Once |
| the product's \l identifier and \l type are set, the product will be queried from |
| the external market place. Properties such as \l price will then be set, and |
| it will be possible to purchase the product. The \l status property holds information |
| on the registration process. |
| |
| \note It is not possible to change the identifier and type once they have both been set |
| and the product has been registered. |
| */ |
| |
| QInAppProductQmlType::QInAppProductQmlType(QObject *parent) |
| : QObject(parent) |
| , m_status(Uninitialized) |
| , m_type(QInAppProductQmlType::ProductType(-1)) |
| , m_componentComplete(false) |
| , m_store(0) |
| , m_product(0) |
| { |
| } |
| |
| /*! |
| \qmlproperty object QtPurchasing::Product::store |
| |
| This property holds the store containing the product. When the product is created as |
| a child of the store, this is set automatically to the parent, as in the following |
| example: |
| |
| \qml |
| Store { |
| Product { |
| // No need to set the store explicitly here, as it will automatically be |
| // bound to the parent |
| identifier: "myConsumableProduct" |
| type: Product.Consumable |
| } |
| Product { |
| // No need to set the store explicitly here, as it will automatically be |
| // bound to the parent |
| identifier: "myUnlockableProduct" |
| type: Product.Unlockable |
| } |
| } |
| \endqml |
| |
| However, in some advanced use cases, for example when products are created based on |
| a model, it's also possible to create the product anywhere in the QML document |
| and set the store explicitly, like in the following example: |
| |
| \code |
| ListModel { |
| id: productModel |
| ListElement { |
| productIdentifier: "myConsumableProduct" |
| productType: Product.Consumable |
| } |
| ListElement { |
| productIdentifier: "myUnlockableProduct" |
| productType: Product.Unlockable |
| } |
| } |
| |
| Store { |
| id: myStore |
| } |
| |
| Instantiator { |
| model: productModel |
| delegate: Product { |
| identifier: productIdentifier |
| type: productType |
| store: myStore |
| } |
| } |
| \endcode |
| */ |
| void QInAppProductQmlType::setStore(QInAppStoreQmlType *store) |
| { |
| if (m_store == store) |
| return; |
| |
| if (m_store != 0) |
| m_store->store()->disconnect(this); |
| |
| m_store = store; |
| connect(m_store->store(), &QInAppStore::productRegistered, |
| this, &QInAppProductQmlType::handleProductRegistered); |
| connect(m_store->store(), &QInAppStore::productUnknown, |
| this, &QInAppProductQmlType::handleProductUnknown); |
| connect(m_store->store(), &QInAppStore::transactionReady, |
| this, &QInAppProductQmlType::handleTransaction); |
| |
| updateProduct(); |
| |
| emit storeChanged(); |
| } |
| |
| QInAppStoreQmlType *QInAppProductQmlType::store() const |
| { |
| return m_store; |
| } |
| |
| void QInAppProductQmlType::componentComplete() |
| { |
| if (!m_componentComplete) { |
| m_componentComplete = true; |
| updateProduct(); |
| } |
| } |
| |
| /*! |
| \qmlproperty string QtPurchasing::Product::identifier |
| This property holds the identifier of the product in the external market place. It must match the |
| identifier used to register the product externally before-hand. |
| |
| When both the identifier and \l type is set, the product is queried from the external market place, |
| and its other properties are updated asynchronously. At this point, the identifier and type |
| can no longer be changed. |
| |
| The following example queries an unlockable product named "myUnlockableProduct" from the external |
| market place. |
| \qml |
| Store { |
| Product { |
| identifier: "myUnlockableProduct" |
| type: Product.Unlockable |
| |
| // ... |
| } |
| } |
| \endqml |
| */ |
| void QInAppProductQmlType::setIdentifier(const QString &identifier) |
| { |
| if (m_identifier == identifier) |
| return; |
| |
| if (m_status != Uninitialized) { |
| qWarning("A product's identifier cannot be changed once the product has been initialized."); |
| return; |
| } |
| |
| m_identifier = identifier; |
| if (m_componentComplete) |
| updateProduct(); |
| emit identifierChanged(); |
| } |
| |
| void QInAppProductQmlType::updateProduct() |
| { |
| if (m_store == 0) |
| return; |
| |
| Status oldStatus = m_status; |
| QInAppProduct *product = 0; |
| if (m_identifier.isEmpty() || m_type == QInAppProductQmlType::ProductType(-1)) { |
| m_status = Uninitialized; |
| } else { |
| product = m_store->store()->registeredProduct(m_identifier); |
| if (product != 0 && product == m_product) |
| return; |
| |
| if (product == 0) { |
| m_status = PendingRegistration; |
| m_store->store()->registerProduct(QInAppProduct::ProductType(m_type), m_identifier); |
| } else if (product->productType() != QInAppProduct::ProductType(m_type)) { |
| qWarning("Product registered multiple times with different product types."); |
| product = 0; |
| m_status = Uninitialized; |
| } else { |
| m_status = Registered; |
| } |
| } |
| |
| setProduct(product); |
| if (oldStatus != m_status) |
| emit statusChanged(); |
| } |
| |
| /*! |
| \qmlmethod QtPurchasing::Product::resetStatus() |
| |
| Resets the \l status of this product and retries querying it from the external |
| market place. |
| |
| This method can be used when querying the product failed for some reason |
| (such as network timeouts). |
| |
| \since QtPurchasing 1.0.2 |
| */ |
| void QInAppProductQmlType::resetStatus() |
| { |
| updateProduct(); |
| } |
| |
| QString QInAppProductQmlType::identifier() const |
| { |
| return m_identifier; |
| } |
| |
| /*! |
| \qmlproperty string QtPurchasing::Product::type |
| This property holds the type of the product in the external market place. |
| |
| It can hold one of the following values: |
| \list |
| \li Product.Consumable The product is consumable and can be purchased more than once |
| by the same user, granted that the transaction for the previous purchase has been finalized. |
| \li Product.Unlockable The product can only be purchased once per user. If the application |
| is uninstalled and reinstalled on the device (or installed on a new device by the same user), |
| purchases of unlockable products can be restored using the store's |
| \l{QtPurchasing::Store::restorePurchases()}{restorePurchases()} method. |
| \endlist |
| |
| When both the \l identifier and type is set, the product is queried from the external market place, |
| and its other properties are updated asynchronously. At this point, the identifier and type |
| can no longer be changed. |
| |
| The following example queries an unlockable product named "myUnlockableProduct" from the external |
| market place. |
| \qml |
| Store { |
| Product { |
| identifier: "myUnlockableProduct" |
| type: Product.Unlockable |
| |
| // ... |
| } |
| } |
| \endqml |
| */ |
| void QInAppProductQmlType::setType(QInAppProductQmlType::ProductType type) |
| { |
| if (m_type == type) |
| return; |
| |
| if (m_status != Uninitialized) { |
| qWarning("A product's type cannot be changed once the product has been initialized."); |
| return; |
| } |
| |
| m_type = type; |
| if (m_componentComplete) |
| updateProduct(); |
| |
| emit typeChanged(); |
| } |
| |
| QInAppProductQmlType::ProductType QInAppProductQmlType::type() const |
| { |
| return m_type; |
| } |
| |
| /*! |
| \qmlproperty enumeration QtPurchasing::Product::status |
| This property holds the current status of the product in the registration sequence. |
| |
| \list |
| \li Product.Uninitialized - This is initial status, before the identifier property has been set. |
| \li Product.PendingRegistration - Indicates that the product is currently being queried from the |
| external market place. The product gets this status when its identifier is set. |
| \li Product.Registered - Indicates that the product was successfully found in the external market |
| place. Its price can now be queried and the product can be purchased. |
| \li Product.Unknown - The product could not be found in the external market place. This could |
| for example be due to misspelling the product identifier. |
| \endlist |
| |
| \qml |
| Store { |
| Product { |
| identifier: "myConsumableProduct" |
| type: Product.Consumable |
| onStatusChanged: { |
| switch (status) { |
| case Product.PendingRegistration: console.debug("Registering " + identifier); break |
| case Product.Registered: console.debug(identifier + " registered with price " + price); break |
| case Product.Unknown: console.debug(identifier + " was not found in the market place"); break |
| } |
| } |
| |
| // ... |
| } |
| } |
| \endqml |
| */ |
| QInAppProductQmlType::Status QInAppProductQmlType::status() const |
| { |
| return m_status; |
| } |
| |
| /*! |
| \qmlproperty string QtPurchasing::Product::price |
| This property holds the price of the product once it has been successfully queried from the |
| external market place. The price is a string consisting of both currency and value, and is |
| usually localized to the current user. |
| |
| For example, the following example displays the price of the unlockable product named |
| "myUnlockableProduct": |
| \code |
| Store { |
| Product { |
| id: myUnlockableProduct |
| identifier: "myUnlockableProduct" |
| type: Product.Unlockable |
| |
| // ... |
| } |
| } |
| |
| Text { |
| text: myUnlockableProduct.status === Product.Registered |
| ? "Price is " + myUnlockableProduct.price |
| : "Price unknown at the moment" |
| } |
| \endcode |
| |
| When run in a Norwegian locale, this code could for instance display "Price is kr 6,00" for a one-dollar product. |
| */ |
| QString QInAppProductQmlType::price() const |
| { |
| return m_product != 0 ? m_product->price() : QString(); |
| } |
| |
| /*! |
| \qmlproperty string QtPurchasing::Product::title |
| This property holds the title of the product once it has been successfully queried from the |
| external market place. The title is localized if the external market place has defined a title |
| in the current users locale. |
| */ |
| QString QInAppProductQmlType::title() const |
| { |
| return m_product != 0 ? m_product->title() : QString(); |
| } |
| |
| /*! |
| \qmlproperty string QtPurchasing::Product::description |
| This property holds the description of the product once it has been successfully queried from the |
| external market place. The title is localized if the external market place has defined a description |
| in the current users locale. |
| */ |
| QString QInAppProductQmlType::description() const |
| { |
| return m_product != 0 ? m_product->description() : QString(); |
| } |
| |
| void QInAppProductQmlType::setProduct(QInAppProduct *product) |
| { |
| if (m_product == product) |
| return; |
| |
| QString oldPrice = price(); |
| QString oldTitle = title(); |
| QString oldDescription = description(); |
| m_product = product; |
| if (price() != oldPrice) |
| emit priceChanged(); |
| if (title() != oldTitle) |
| emit titleChanged(); |
| if (description() != oldDescription) |
| emit descriptionChanged(); |
| } |
| |
| void QInAppProductQmlType::handleProductRegistered(QInAppProduct *product) |
| { |
| if (product->identifier() == m_identifier) { |
| Q_ASSERT(product->productType() == QInAppProduct::ProductType(m_type)); |
| setProduct(product); |
| if (m_status != Registered) { |
| m_status = Registered; |
| emit statusChanged(); |
| } |
| } |
| } |
| |
| void QInAppProductQmlType::handleProductUnknown(QInAppProduct::ProductType, const QString &identifier) |
| { |
| if (identifier == m_identifier) { |
| setProduct(0); |
| if (m_status != Unknown) { |
| m_status = Unknown; |
| emit statusChanged(); |
| } |
| } |
| } |
| |
| void QInAppProductQmlType::handleTransaction(QInAppTransaction *transaction) |
| { |
| if (transaction->product()->identifier() != m_identifier) |
| return; |
| |
| if (transaction->status() == QInAppTransaction::PurchaseApproved) |
| emit purchaseSucceeded(transaction); |
| else if (transaction->status() == QInAppTransaction::PurchaseRestored) |
| emit purchaseRestored(transaction); |
| else |
| emit purchaseFailed(transaction); |
| } |
| |
| /*! |
| \qmlmethod QtPurchasing::Product::purchase() |
| |
| Launches the purchasing process for this product. The purchasing process is asynchronous. |
| When it completes, either the \l onPurchaseSucceeded or the \l onPurchaseFailed handler |
| in the object will be called with the resulting transaction. |
| */ |
| void QInAppProductQmlType::purchase() |
| { |
| if (m_product != 0 && m_status == Registered) |
| m_product->purchase(); |
| else |
| qWarning("Attempted to purchase unregistered product"); |
| } |
| |
| /*! |
| \qmlsignal QtPurchasing::Product::onPurchaseSucceeded(object transaction) |
| |
| This handler is called when a product has been purchased successfully. It is triggered |
| when the application has called purchase() on the product and the user has subsequently |
| confirmed the purchase, for example by entering their password. |
| |
| All products should have a handler for onPurchaseSucceeded. This handler should in turn |
| save information about the purchased product and when the information has been stored |
| and verified, it should call finalize() on the \a transaction object. |
| |
| The handler should support being called multiple times for the same purchase. For example, |
| the application execution might by accident be interrupted after saving the purchase |
| information, but before finalizing the transaction. In this case, the handler should |
| verify that the information is already stored in the persistent storage and then finalize |
| the transaction. |
| |
| The following example attempts to store the purchase state of a consumable |
| product using a custom made function. It only finalizes the transaction if saving the |
| data was successful. Otherwise, it calls another custom function to display an error |
| message to the user. |
| |
| \qml |
| Store { |
| Product { |
| id: myConsumableProduct |
| identifier: "myConsumableProduct" |
| type: Product.Consumable |
| |
| onPurchaseSucceeded: { |
| if (myStorage.savePurchaseInformation(identifier)) { |
| transaction.finalize() |
| } else { |
| myDisplayHelper.message("Failed to store purchase information. Is there available storage?") |
| } |
| } |
| |
| // ... |
| } |
| } |
| \endqml |
| |
| If the transaction is not finalized, the onPurchaseSucceeded handler will be called again |
| the next time the product is registered (on application startup.) This means that if saving |
| the information failed, the user will have the opportunity of rectifying the problem (for |
| example by deleting something else to make space for the data) and the transaction will |
| be completed once they restart the application and the problem has been solved. |
| |
| \note A purchased, consumable product can not be purchased again until its previous transaction |
| is finalized. |
| */ |
| |
| /*! |
| \qmlsignal QtPurchasing::Product::onPurchaseFailed(object transaction) |
| |
| This handler is called when a purchase was requested for a given product, but the purchase |
| failed. This will typically happen if the application calls purchase() on a product, and |
| the user subsequently cancels the purchase. It could also happen under other circumstances, |
| for example if there is no suitable network connection. |
| |
| All products should have an \c onPurchaseFailed handler. |
| |
| After a proper reaction is taken, the finalize() function should be called on the \a transaction |
| object. If this is not done, the handler may be called again the next time the product is registered. |
| |
| The following example reacts to a failed purchase attempt by calling a custom function to display a |
| message to the user. |
| \qml |
| Store { |
| Product { |
| id: myConsumableProduct |
| identifier: "myConsumableProduct" |
| type: Product.Consumable |
| |
| onPurchaseFailed: { |
| myDisplayHelper.message("Product was not purchased. You have not been charged.") |
| transaction.finalize() |
| } |
| |
| // ... |
| } |
| } |
| \endqml |
| */ |
| |
| /*! |
| \qmlsignal QtPurchasing::Product::onPurchaseRestored(object transaction) |
| |
| This handler is called when a previously purchased unlockable product is restored. This |
| can happen when the \l{QtPurchasing::Store::restorePurchases()}{restorePurchases()} function |
| in the current \l Store is called. |
| The \c onPurchaseRestored handler will then be called for each unlockable product which |
| has previously been purchased by the user. |
| |
| Applications which uses the \l{QtPurchasing::Store::restorePurchases()}{restorePurchases()} |
| function should include this handler |
| in all unlockable products. In the handler, the application should make sure information |
| about the purchase is stored and call \l{QtPurchasing::Transaction::finalize()}{finalize()} |
| on the \a transaction object if |
| the information has been successfully stored (or has been verified to already be stored). |
| |
| The following example calls a custom function which either saves the information about |
| the purchase or verifies that it is already saved. When the data has been verified, it |
| finalizes the transaction. If it could not be verified, it calls another custom function |
| to display an error message to the user. If the transaction is not finalized, the handler |
| will be called again for the same transaction the next time the product is registered |
| (on application start-up). |
| |
| \qml |
| Store { |
| Product { |
| id: myUnlockableProduct |
| identifier: "myUnlockableProduct" |
| type: Product.Unlockable |
| |
| onPurchaseRestored: { |
| if (myStorage.savePurchaseInformation(identifier)) { |
| transaction.finalize() |
| } else { |
| myDisplayHelper.message("Failed to store purchase information. Is there available storage?") |
| } |
| } |
| |
| // ... |
| } |
| } |
| \endqml |
| */ |
| |
| QT_END_NAMESPACE |