blob: 0dcba9cad93736cb451c5305aec3f7f82d3dc5c2 [file] [log] [blame]
/****************************************************************************
**
** 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