diff options
author | Maurice Kalinowski <maurice.kalinowski@theqtcompany.com> | 2016-01-15 18:55:48 +0100 |
---|---|---|
committer | Maurice Kalinowski <maurice.kalinowski@qt.io> | 2016-07-06 12:44:09 +0000 |
commit | cb3910f7afaa48fb6093c784a83fa89486236c2b (patch) | |
tree | f32f111be9d383a300351f59706be80d113f582f | |
parent | 63b8d2ee2b6cc59df56562c248a51e8b101887a8 (diff) |
Add WinRT backend
[ChangeLog][Purchasing] Added WinRT backend.
Task-number: QTBUG-50550
Change-Id: I28eba8b5a57ef89351537b79e59aedd003ab0c1a
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
-rw-r--r-- | examples/purchasing/qthangman/qthangman.pro | 8 | ||||
-rw-r--r-- | examples/purchasing/qthangman/winrt/QtStoreSimulation.xml | bin | 0 -> 3136 bytes | |||
-rw-r--r-- | examples/purchasing/qthangman/winrt/winrt.qrc | 5 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/inapppurchase.pri | 4 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/qinapppurchasebackendfactory.cpp | 4 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinappproduct.cpp | 59 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinappproduct_p.h | 69 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend.cpp | 774 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend_p.h | 78 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinapptransaction.cpp | 59 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/qwinrtinapptransaction_p.h | 75 | ||||
-rw-r--r-- | src/purchasing/inapppurchase/winrt/winrt.pri | 11 |
12 files changed, 1146 insertions, 0 deletions
diff --git a/examples/purchasing/qthangman/qthangman.pro b/examples/purchasing/qthangman/qthangman.pro index 1cd0aec..5280416 100644 --- a/examples/purchasing/qthangman/qthangman.pro +++ b/examples/purchasing/qthangman/qthangman.pro @@ -41,3 +41,11 @@ mac { #QMAKE_TARGET_BUNDLE_PREFIX = "org.qtproject.qt.iosteam" #TARGET = qthangman } + +winrt { + # Add a StoreSimulation.xml file to the root resource folder + # and Qt Purchasing will switch into simulation mode. + # Remove the file in the publishing process. + RESOURCES += \ + winrt/winrt.qrc +} diff --git a/examples/purchasing/qthangman/winrt/QtStoreSimulation.xml b/examples/purchasing/qthangman/winrt/QtStoreSimulation.xml Binary files differnew file mode 100644 index 0000000..3469ed8 --- /dev/null +++ b/examples/purchasing/qthangman/winrt/QtStoreSimulation.xml diff --git a/examples/purchasing/qthangman/winrt/winrt.qrc b/examples/purchasing/qthangman/winrt/winrt.qrc new file mode 100644 index 0000000..b4e16d9 --- /dev/null +++ b/examples/purchasing/qthangman/winrt/winrt.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/"> + <file>QtStoreSimulation.xml</file> + </qresource> +</RCC> diff --git a/src/purchasing/inapppurchase/inapppurchase.pri b/src/purchasing/inapppurchase/inapppurchase.pri index 85bab80..6b5f396 100644 --- a/src/purchasing/inapppurchase/inapppurchase.pri +++ b/src/purchasing/inapppurchase/inapppurchase.pri @@ -26,3 +26,7 @@ android { mac { include ($$PWD/mac/mac.pri) } + +winrt { + include ($$PWD/winrt/winrt.pri) +} diff --git a/src/purchasing/inapppurchase/qinapppurchasebackendfactory.cpp b/src/purchasing/inapppurchase/qinapppurchasebackendfactory.cpp index 757b78a..a5f868a 100644 --- a/src/purchasing/inapppurchase/qinapppurchasebackendfactory.cpp +++ b/src/purchasing/inapppurchase/qinapppurchasebackendfactory.cpp @@ -32,6 +32,8 @@ # include "qandroidinapppurchasebackend_p.h" #elif defined(Q_OS_MAC) # include "qmacinapppurchasebackend_p.h" +#elif defined(Q_OS_WINRT) +# include "qwinrtinapppurchasebackend_p.h" #else # include "qinapppurchasebackend_p.h" #endif @@ -44,6 +46,8 @@ QInAppPurchaseBackend *QInAppPurchaseBackendFactory::create() return new QAndroidInAppPurchaseBackend; #elif defined (Q_OS_MAC) return new QMacInAppPurchaseBackend; +#elif defined (Q_OS_WINRT) + return new QWinRTInAppPurchaseBackend; #else return new QInAppPurchaseBackend; #endif diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinappproduct.cpp b/src/purchasing/inapppurchase/winrt/qwinrtinappproduct.cpp new file mode 100644 index 0000000..0801270 --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinappproduct.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qwinrtinappproduct_p.h" +#include "qwinrtinapppurchasebackend_p.h" + +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPurchasingProduct, "qt.purchasing.product") + +QWinRTInAppProduct::QWinRTInAppProduct(QWinRTInAppPurchaseBackend *backend, + const QString &price, + const QString &title, + const QString &description, + ProductType productType, + const QString &identifier, + QObject *parent) + : QInAppProduct(price, title, description, productType, identifier, parent) + , m_backend(backend) +{ + qCDebug(lcPurchasingProduct) << __FUNCTION__; +} + + +void QWinRTInAppProduct::purchase() +{ + qCDebug(lcPurchasingProduct) << __FUNCTION__; + m_backend->purchaseProduct(this); +} + +QT_END_NAMESPACE + diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinappproduct_p.h b/src/purchasing/inapppurchase/winrt/qwinrtinappproduct_p.h new file mode 100644 index 0000000..5dd271f --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinappproduct_p.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QWINRTINAPPPRODUCT_P_H +#define QWINRTINAPPPRODUCT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qinappproduct.h" + +QT_BEGIN_NAMESPACE + +class QWinRTInAppPurchaseBackend; +class QWinRTInAppProduct : public QInAppProduct +{ + Q_OBJECT +public: + explicit QWinRTInAppProduct(QWinRTInAppPurchaseBackend *backend, + const QString &price, + const QString &title, + const QString &description, + ProductType productType, + const QString &identifier, + QObject *parent = 0); + + + void purchase() override; + +private: + QWinRTInAppPurchaseBackend *m_backend; +}; + +QT_END_NAMESPACE + +#endif // QWINRTINAPPPRODUCT_P_H diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend.cpp b/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend.cpp new file mode 100644 index 0000000..396123d --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend.cpp @@ -0,0 +1,774 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qwinrtinapppurchasebackend_p.h" +#include "qwinrtinappproduct_p.h" +#include "qwinrtinapptransaction_p.h" +#include "qinappstore.h" + +#include <QLoggingCategory> +#include <QtCore/QDir> +#include <QtCore/QFileInfo> +#include <QtCore/QFile> +#include <QtCore/QStandardPaths> +#include <QtCore/QXmlStreamReader> + +#include <private/qeventdispatcher_winrt_p.h> +#include <qfunctions_winrt.h> +#include <functional> + +#include <Windows.ApplicationModel.store.h> +#include <Windows.Applicationmodel.Activation.h> +#include <wrl.h> + +using namespace ABI::Windows::ApplicationModel::Store; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Storage; +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; + +typedef IAsyncOperationCompletedHandler<ListingInformation *> ListingInformationHandler; + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPurchasingBackend, "qt.purchasing.backend") + +const QString qt_win_app_identifier = QLatin1String("app"); + +class QWinRTAppBridge { +public: + HRESULT activate(); + HRESULT LoadListingInformationAsync(ComPtr<IAsyncOperation<ListingInformation *>> &op); + HRESULT GetAppReceiptAsync(ComPtr<IAsyncOperation<HSTRING>> &op); + HRESULT RequestAppPurchaseAsync(bool receipt, ComPtr<IAsyncOperation<HSTRING>> &op); + HRESULT RequestProductPurchaseAsync(HSTRING productId, bool receipt, ComPtr<IAsyncOperation<HSTRING>> &op); + HRESULT RequestProductPurchaseWithResultsAsync(HSTRING productId, ComPtr<IAsyncOperation<PurchaseResults *>> &purchaseOp); + HRESULT ReportConsumableFulfillmentAsync(HSTRING productId, GUID transactionId, ComPtr<IAsyncOperation<FulfillmentResult>> &op); + HRESULT get_LicenseInformation(ComPtr<ILicenseInformation> &licenseInfo); +private: + HRESULT qt_winrt_load_simulator_config(const QString &fileName, ComPtr<ICurrentAppSimulator> &simulator); + ComPtr<ICurrentAppSimulator> m_simulator; + ComPtr<ICurrentApp> m_app; + bool m_simulate{false}; +}; + +HRESULT QWinRTAppBridge::activate() +{ + HRESULT hr; + const QString storeFilename = QLatin1String("QtStoreSimulation.xml"); + const QString dataPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1Char('/'); + const QString storeSourceName = QLatin1String(":/") + storeFilename; + const QString storeTargetName = dataPath + storeFilename; + if (QFileInfo::exists(storeSourceName)) { + qWarning("Found purchasing simulation file, disabling store connectivity.\n" + "Please note you will not be able to publish this package."); + bool success = true; + if (QFileInfo(storeTargetName).exists()) + success = QFile(storeTargetName).remove(); + + if (!success) + qWarning("Could not remove previous purchasing simulation file."); + + success = QFile(storeSourceName).copy(storeTargetName); + if (!success) + qWarning("Could not copy purchasing simulation file."); + + success = QFile::setPermissions(storeTargetName, + QFile::WriteOwner | QFile::ReadOwner | QFile::WriteUser | QFile::ReadUser); + if (!success) + qWarning("Could not set readwrite flags for purchasing simulation file"); + + m_simulate = true; + } + + if (m_simulate) { + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_ApplicationModel_Store_CurrentAppSimulator).Get(), + IID_PPV_ARGS(&m_simulator)); + qt_winrt_load_simulator_config(storeTargetName, m_simulator); + } else { + hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_ApplicationModel_Store_CurrentApp).Get(), + IID_PPV_ARGS(&m_app)); + } + return hr; +} + +HRESULT QWinRTAppBridge::LoadListingInformationAsync(ComPtr<IAsyncOperation<ListingInformation *> > &op) +{ + HRESULT hr; + if (m_simulate) + hr = m_simulator->LoadListingInformationAsync(&op); + else + hr = m_app->LoadListingInformationAsync(&op); + return hr; +} + +HRESULT QWinRTAppBridge::GetAppReceiptAsync(ComPtr<IAsyncOperation<HSTRING> > &op) +{ + HRESULT hr; + if (m_simulate) + hr = m_simulator->GetAppReceiptAsync(&op); + else + hr = m_app->GetAppReceiptAsync(&op); + return hr; +} + +HRESULT QWinRTAppBridge::RequestAppPurchaseAsync(bool receipt, ComPtr<IAsyncOperation<HSTRING> > &op) +{ + HRESULT hr; + if (m_simulate) + hr = m_simulator->RequestAppPurchaseAsync(receipt, op.GetAddressOf()); + else + hr = m_app->RequestAppPurchaseAsync(receipt, op.GetAddressOf()); + return hr; +} + +HRESULT QWinRTAppBridge::RequestProductPurchaseAsync(HSTRING productId, bool receipt, ComPtr<IAsyncOperation<HSTRING> > &op) +{ + HRESULT hr; + if (m_simulate) + hr = m_simulator->RequestProductPurchaseAsync(productId, receipt, op.GetAddressOf()); + else + hr = m_app->RequestProductPurchaseAsync(productId, receipt, op.GetAddressOf()); + return hr; +} + +HRESULT QWinRTAppBridge::RequestProductPurchaseWithResultsAsync(HSTRING productId, ComPtr<IAsyncOperation<PurchaseResults *> > &purchaseOp) +{ + HRESULT hr; + if (m_simulate) { + ComPtr<ICurrentAppSimulatorWithConsumables> consumApp; + hr = m_simulator.As(&consumApp); + Q_ASSERT_SUCCEEDED(hr); + hr = consumApp->RequestProductPurchaseWithResultsAsync(productId, purchaseOp.GetAddressOf()); + } else { + ComPtr<ICurrentAppWithConsumables> consumApp; + hr = m_app.As(&consumApp); + Q_ASSERT_SUCCEEDED(hr); + hr = consumApp->RequestProductPurchaseWithResultsAsync(productId, purchaseOp.GetAddressOf()); + } + return hr; +} + +HRESULT QWinRTAppBridge::ReportConsumableFulfillmentAsync(HSTRING productId, GUID transactionId, ComPtr<IAsyncOperation<FulfillmentResult> > &op) +{ + HRESULT hr; + if (m_simulate) { + ComPtr<ICurrentAppSimulatorWithConsumables> consumApp; + hr = m_simulator.As(&consumApp); + Q_ASSERT_SUCCEEDED(hr); + hr = consumApp->ReportConsumableFulfillmentAsync(productId, transactionId, op.GetAddressOf()); + } else { + ComPtr<ICurrentAppWithConsumables> consumApp; + hr = m_app.As(&consumApp); + Q_ASSERT_SUCCEEDED(hr); + hr = consumApp->ReportConsumableFulfillmentAsync(productId, transactionId, op.GetAddressOf()); + } + return hr; +} + +HRESULT QWinRTAppBridge::get_LicenseInformation(ComPtr<ILicenseInformation> &licenseInfo) +{ + HRESULT hr; + if (m_simulate) { + hr = m_simulator->get_LicenseInformation(&licenseInfo); + } else { + hr = m_app->get_LicenseInformation(&licenseInfo); + } + return hr; +} + +HRESULT QWinRTAppBridge::qt_winrt_load_simulator_config(const QString &fileName, ComPtr<ICurrentAppSimulator> &simulator) +{ + qCDebug(lcPurchasingBackend) << __FUNCTION__ << fileName; + + HRESULT hr; + const QString nativeFilename = QDir::toNativeSeparators(fileName); + HString nativeName; + hr = nativeName.Set(reinterpret_cast<PCWSTR>(nativeFilename.utf16()), nativeFilename.size()); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IStorageFileStatics> fileStatics; + hr = GetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Storage_StorageFile).Get(), &fileStatics); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IAsyncOperation<StorageFile*>> op; + hr = fileStatics->GetFileFromPathAsync(nativeName.Get(), &op); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IStorageFile> storeFile; + hr = QWinRTFunctions::await(op, storeFile.GetAddressOf()); + RETURN_HR_IF_FAILED("Could not find purchasing simulator xml description."); + + ComPtr<ABI::Windows::Foundation::IAsyncAction> reloadAction; + hr = simulator->ReloadSimulatorAsync(storeFile.Get(), &reloadAction); + Q_ASSERT_SUCCEEDED(hr); + + hr = QWinRTFunctions::await(reloadAction); + RETURN_HR_IF_FAILED("Failed to load purchasing description."); + + return hr; +} + +struct NativeProductInfo +{ + NativeProductInfo() { } + HString productID; + HString formatPrice; + HString productName; + ProductType type; +}; + +inline bool compareProductTypes(QInAppProduct::ProductType qtType, ProductType nativeType) { + if (qtType == QInAppProduct::Consumable && nativeType == ProductType_Consumable) + return true; + else if (qtType == QInAppProduct::Unlockable && nativeType == ProductType_Durable) + return true; + else + return false; +} + +QWinRTInAppTransaction* createTransaction(AsyncStatus status, + QWinRTInAppProduct *product, + QWinRTInAppPurchaseBackend *backend) +{ + QInAppTransaction::TransactionStatus qStatus = (status == AsyncStatus::Completed) ? + QInAppTransaction::PurchaseApproved : QInAppTransaction::PurchaseFailed; + + QInAppTransaction::FailureReason reason; + switch (status) { + case AsyncStatus::Completed: + reason = QInAppTransaction::NoFailure; + break; + case AsyncStatus::Canceled: + reason = QInAppTransaction::CanceledByUser; + break; + case AsyncStatus::Error: + default: + reason = QInAppTransaction::ErrorOccurred; + break; + } + + auto transaction = new QWinRTInAppTransaction(qStatus, product, reason, backend); + return transaction; +} + +class QWinRTInAppPurchaseBackendPrivate +{ +public: + explicit QWinRTInAppPurchaseBackendPrivate(QWinRTInAppPurchaseBackend *p) + : q_ptr(p) + { } + + HRESULT onListingInformation(IAsyncOperation<ListingInformation*> *args, + AsyncStatus status); + + ComPtr<IProductLicense> findProductLicense(const QString &identifier); + QWinRTAppBridge m_bridge; + bool m_waitingForList = false; + + QMap<QString, NativeProductInfo*> nativeProducts; + + QWinRTInAppPurchaseBackend *q_ptr; + Q_DECLARE_PUBLIC(QWinRTInAppPurchaseBackend) +}; + +QWinRTInAppPurchaseBackend::QWinRTInAppPurchaseBackend(QObject *parent) + : QInAppPurchaseBackend(parent) +{ + d_ptr.reset(new QWinRTInAppPurchaseBackendPrivate(this)); + qCDebug(lcPurchasingBackend) << __FUNCTION__; +} + +void QWinRTInAppPurchaseBackend::initialize() +{ + Q_D(QWinRTInAppPurchaseBackend); + qCDebug(lcPurchasingBackend) << __FUNCTION__; + + HRESULT hr; + hr = QEventDispatcherWinRT::runOnXamlThread([d]() { + HRESULT hr; + hr = d->m_bridge.activate(); + Q_ASSERT_SUCCEEDED(hr); + + // ### Keep for later usage. + // ComPtr<ILicenseInformation> licenseInfo; + // hr = d->m_app->get_LicenseInformation(&licenseInfo); + // RETURN_HR_IF_FAILED("Could not acquire license information."); + + ComPtr<IAsyncOperation<ListingInformation*>> op; + hr = d->m_bridge.LoadListingInformationAsync(op); + RETURN_HR_IF_FAILED("Purchasing: Could not load listing information."); + + hr = op->put_Completed(Callback<ListingInformationHandler>(d, &QWinRTInAppPurchaseBackendPrivate::onListingInformation).Get()); + Q_ASSERT_SUCCEEDED(hr); + d->m_waitingForList = true; + return S_OK; + + }); + RETURN_VOID_IF_FAILED("Could not initialize purchase backend"); +} + +bool QWinRTInAppPurchaseBackend::isReady() const +{ + Q_D(const QWinRTInAppPurchaseBackend); + qCDebug(lcPurchasingBackend) << __FUNCTION__; + return !d->m_waitingForList && !d->nativeProducts.isEmpty(); +} + +void QWinRTInAppPurchaseBackend::restorePurchases() +{ + qCDebug(lcPurchasingBackend) << __FUNCTION__; + Q_D(QWinRTInAppPurchaseBackend); + + HRESULT hr; + ComPtr<IAsyncOperation<HSTRING>> op; + hr = d->m_bridge.GetAppReceiptAsync(op); + + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "Failed to query receipts"; + return; + } + + HString receipt; + hr = QWinRTFunctions::await(op, receipt.GetAddressOf()); + + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "Could not wait for receipt"; + return; + } + + quint32 length; + QString parse = QString::fromWCharArray(receipt.GetRawBuffer(&length)); + + QXmlStreamReader reader(parse); + while (reader.readNextStartElement()) { + if (reader.name() == QLatin1String("Receipt")) + break; + } + if (reader.name() != QLatin1String("Receipt")) { + qCDebug(lcPurchasingBackend) << "Could not parse app receipt xml"; + return; + } + + reader.readNextStartElement(); + if (reader.name() != QLatin1String("AppReceipt")) { + qCDebug(lcPurchasingBackend) << "Expected AppReceipt in receipt, got:" << reader.name(); + return; + } + + while (!reader.atEnd()) { + reader.readNext(); + if (reader.attributes().hasAttribute(QLatin1String("ProductId"))) { + const QString id = reader.attributes().value(QLatin1String("ProductId")).toString(); + if (d->nativeProducts.contains(id)) { + qCDebug(lcPurchasingBackend) << "Restoring:" << id; + + QInAppProduct *product = store()->registeredProduct(id); + if (!product) { + qCDebug(lcPurchasingBackend) << "Product " << id << "has been bought, but is unknown"; + continue; + } + + auto transaction = new QWinRTInAppTransaction(QInAppTransaction::PurchaseRestored, + product, + QInAppTransaction::NoFailure, + this); + + emit transactionReady(transaction); + } + } + } + + ComPtr<ILicenseInformation> appLicense; + hr = d->m_bridge.get_LicenseInformation(appLicense); + Q_ASSERT_SUCCEEDED(hr); + boolean active; + hr = appLicense->get_IsActive(&active); + Q_ASSERT_SUCCEEDED(hr); + boolean trial; + hr = appLicense->get_IsTrial(&trial); + Q_ASSERT_SUCCEEDED(hr); + if (active && !trial) { + qCDebug(lcPurchasingBackend) << "Restoring app product"; + + QInAppProduct *product = store()->registeredProduct(qt_win_app_identifier); + + auto transaction = new QWinRTInAppTransaction(QInAppTransaction::PurchaseRestored, + product, + QInAppTransaction::NoFailure, + this); + emit transactionReady(transaction); + } + + // Using GetProductReceiptAsync is the better solution as one can + // query for specific products instead of parsing through a xml. + // However, this returns E_NOTIMPL when using the ICurrentAppSimulator, + // so it could not be used during development phase. +#if 0 + const QStringList keys = d->nativeProducts.keys(); + for (auto item : keys) { + HRESULT hr; + HString productId; + + hr = productId.Set(reinterpret_cast<LPCWSTR>(item.utf16()), item.size()); + + ComPtr<IAsyncOperation<HSTRING>> op; + hr = d->m_app->GetProductReceiptAsync(productId.Get(), &op); + + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "No receipt available for:" << item; + continue; + } + + HString receiptString; + hr = QWinRTFunctions::await(op, receiptString.GetAddressOf()); + + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "Failed to wait for receipt:" << item; + continue; + } + + quint32 length; + QString receipt = QString::fromWCharArray(receiptString.GetRawBuffer(&length)); + qDebug() << "Received receipt:" << receipt; + + // Create new transaction with status == Restored and emit + //emit transactionReady(); + } +#endif +} + +void QWinRTInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value) +{ + qCDebug(lcPurchasingBackend) << __FUNCTION__ << ":" << propertyName << ":" << value; +} + +void QWinRTInAppPurchaseBackend::queryProducts(const QList<Product> &products) +{ + qCDebug(lcPurchasingBackend) << __FUNCTION__ << " Size:" << products.size(); + + for (auto p : products) + queryProduct(p.productType, p.identifier); +} + +void QWinRTInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType, + const QString &identifier) +{ + Q_D(QWinRTInAppPurchaseBackend); + qCDebug(lcPurchasingBackend) << __FUNCTION__ << ":" << productType << ":" << identifier; + + if (!d->nativeProducts.contains(identifier) || !compareProductTypes(productType, d->nativeProducts.value(identifier)->type)) { + qCDebug(lcPurchasingBackend) << "No native product called:" << identifier; + emit productQueryFailed(productType, identifier); + return; + } + + ComPtr<IProductLicense> productLicense = d->findProductLicense(identifier); + if (!productLicense && identifier != qt_win_app_identifier) { + qCDebug(lcPurchasingBackend) << "Could not find product license even though available in listing:" << identifier; + emit productQueryFailed(productType, identifier); + return; + } + + quint32 length; + NativeProductInfo *cachedInfo = d->nativeProducts.value(identifier); + QString price = QString::fromWCharArray(cachedInfo->formatPrice.GetRawBuffer(&length)); + QString name = QString::fromWCharArray(cachedInfo->productName.GetRawBuffer(&length)); + QWinRTInAppProduct *appProduct = new QWinRTInAppProduct(this, + price, + name, + QString(), + productType, + identifier, + this); + + emit productQueryDone(appProduct); +} + +void QWinRTInAppPurchaseBackend::purchaseProduct(QWinRTInAppProduct *product) +{ + qCDebug(lcPurchasingBackend) << __FUNCTION__ << product; + Q_D(QWinRTInAppPurchaseBackend); + HRESULT hr; + + HString productId; + hr = productId.Set(reinterpret_cast<LPCWSTR>(product->identifier().utf16()), + product->identifier().size()); + Q_ASSERT_SUCCEEDED(hr); + + if (product->identifier() == qt_win_app_identifier) { + // Buy the app itself + hr = QEventDispatcherWinRT::runOnXamlThread([d, product, this]() { + HRESULT hr; + ComPtr<IAsyncOperation<HSTRING>> appOp; + hr = d->m_bridge.RequestAppPurchaseAsync(false, appOp); + Q_ASSERT_SUCCEEDED(hr); + auto purchaseCallback = Callback<IAsyncOperationCompletedHandler<HSTRING>>([d, product, this](IAsyncOperation<HSTRING> *, AsyncStatus status) + { + auto transaction = createTransaction(status, product, this); + emit transactionReady(transaction); + return S_OK; + }); + hr = appOp->put_Completed(purchaseCallback.Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + } else if (product->productType() == QInAppProduct::Unlockable) { + hr = QEventDispatcherWinRT::runOnXamlThread([d, product, &productId, this]() { + ComPtr<IAsyncOperation<HSTRING>> purchaseOp; + HRESULT hr; + hr = d->m_bridge.RequestProductPurchaseAsync(productId.Get(), false, purchaseOp); + Q_ASSERT_SUCCEEDED(hr); + auto purchaseCallback = Callback<IAsyncOperationCompletedHandler<HSTRING>>([d, product, this](IAsyncOperation<HSTRING> *, AsyncStatus status) + { + auto transaction = createTransaction(status, product, this); + emit transactionReady(transaction); + return S_OK; + }); + hr = purchaseOp->put_Completed(purchaseCallback.Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + } else { + hr = QEventDispatcherWinRT::runOnXamlThread([d, product, &productId, this]() { + HRESULT hr; + + ComPtr<IAsyncOperation<PurchaseResults*>> purchaseOp; + hr = d->m_bridge.RequestProductPurchaseWithResultsAsync(productId.Get(), purchaseOp); + Q_ASSERT_SUCCEEDED(hr); + + auto purchaseCallback = Callback<IAsyncOperationCompletedHandler<PurchaseResults*>>([d, product, this](IAsyncOperation<PurchaseResults*> *op, AsyncStatus status) + { + auto transaction = createTransaction(status, product, this); + + if (status == AsyncStatus::Completed) { + HRESULT hr; + hr = op->GetResults(&transaction->m_purchaseResults); + Q_ASSERT_SUCCEEDED(hr); + } + emit transactionReady(transaction); + return S_OK; + }); + + hr = purchaseOp->put_Completed(purchaseCallback.Get()); + Q_ASSERT_SUCCEEDED(hr); + return S_OK; + }); + } +} + +void QWinRTInAppPurchaseBackend::fulfillConsumable(QWinRTInAppTransaction *transaction) +{ + Q_D(QWinRTInAppPurchaseBackend); + qCDebug(lcPurchasingBackend) << __FUNCTION__ << transaction; + HRESULT hr; + + GUID transactionId; + hr = transaction->m_purchaseResults->get_TransactionId(&transactionId); + Q_ASSERT_SUCCEEDED(hr); + + HString productId; + const QString identifier = transaction->product()->identifier(); + hr = productId.Set(reinterpret_cast<LPCWSTR>(identifier.utf16()), identifier.size()); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IAsyncOperation<FulfillmentResult>> op; + hr = d->m_bridge.ReportConsumableFulfillmentAsync(productId.Get(), transactionId, op); + RETURN_VOID_IF_FAILED("Could not report consumable to be fulfilled."); + + FulfillmentResult res; + hr = QWinRTFunctions::await(op, &res); + RETURN_VOID_IF_FAILED("Operation to fulfill consumable failed."); + + qCDebug(lcPurchasingBackend) << "Fulfilled:" << (res == FulfillmentResult_Succeeded); +} + +HRESULT QWinRTInAppPurchaseBackendPrivate::onListingInformation(IAsyncOperation<ListingInformation *> *args, + AsyncStatus status) +{ + Q_UNUSED(status); + Q_Q(QWinRTInAppPurchaseBackend); + + qCDebug(lcPurchasingBackend) << __FUNCTION__; + + ComPtr<IListingInformation> info; + HRESULT hr = args->GetResults(&info); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IMapView<HSTRING, ABI::Windows::ApplicationModel::Store::ProductListing *>> productListings; + hr = info->get_ProductListings(&productListings); + + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "Could not get IMapView"; + return S_OK; + } + + typedef Collections::IKeyValuePair<HSTRING, ProductListing *> ValueItem; + typedef Collections::IIterable<ValueItem *> ValueIterable; + typedef Collections::IIterator<ValueItem *> ValueIterator; + + ComPtr<ValueIterable> iterable; + ComPtr<ValueIterator> iterator; + + hr = productListings.As(&iterable); + if (FAILED(hr)) + return S_OK; + + boolean current = false; + hr = iterable->First(&iterator); + if (FAILED(hr)) + return S_OK; + hr = iterator->get_HasCurrent(¤t); + if (FAILED(hr)) + return S_OK; + + while (SUCCEEDED(hr) && current){ + ComPtr<ValueItem> currentItem; + hr = iterator->get_Current(¤tItem); + if (FAILED(hr)) { + qCDebug(lcPurchasingBackend) << "Could not get currentItem:" << hr; + continue; + } + + HString nativeKey; + QString productKey; + ComPtr<IProductListing> value; + hr = currentItem->get_Key(nativeKey.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + productKey = QString::fromWCharArray(nativeKey.GetRawBuffer(nullptr)); + qCDebug(lcPurchasingBackend) << "ProductKey:" << productKey; + hr = currentItem->get_Value(&value); + Q_ASSERT_SUCCEEDED(hr); + + auto nativeInfo = new NativeProductInfo; + + hr = value->get_ProductId(nativeInfo->productID.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + hr = value->get_FormattedPrice(nativeInfo->formatPrice.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + hr = value->get_Name(nativeInfo->productName.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IProductListingWithConsumables> converted; + hr = value.As(&converted); + Q_ASSERT_SUCCEEDED(hr); + hr = converted->get_ProductType(&nativeInfo->type); + Q_ASSERT_SUCCEEDED(hr); + + qCDebug(lcPurchasingBackend) << "Detailed info:" + << " ID:" << QString::fromWCharArray(nativeInfo->productID.GetRawBuffer(nullptr)) + << " Price:" << QString::fromWCharArray(nativeInfo->formatPrice.GetRawBuffer(nullptr)) + << " Name:" << QString::fromWCharArray(nativeInfo->productName.GetRawBuffer(nullptr)) + << " Type:" << (nativeInfo->type == ProductType_Consumable ? QLatin1String("Consumable") : QLatin1String("Unlockable")); + + nativeProducts.insert(QString::fromWCharArray(nativeInfo->productID.GetRawBuffer(nullptr)), nativeInfo); + + hr = iterator->MoveNext(¤t); + Q_ASSERT_SUCCEEDED(hr); + } + + ComPtr<ILicenseInformation> appLicense; + hr = m_bridge.get_LicenseInformation(appLicense); + Q_ASSERT_SUCCEEDED(hr); + boolean active; + hr = appLicense->get_IsActive(&active); + Q_ASSERT_SUCCEEDED(hr); + boolean trial; + hr = appLicense->get_IsTrial(&trial); + Q_ASSERT_SUCCEEDED(hr); + if (active) { + auto appInfo = new NativeProductInfo; + hr = appInfo->productID.Set(reinterpret_cast<LPCWSTR>(qt_win_app_identifier.utf16()), qt_win_app_identifier.size()); + Q_ASSERT_SUCCEEDED(hr); + // We store trial information inside the Name itself, as a kind of + // app State, as the public API does not support trial mode + const QString licenseType = trial ? QLatin1String("Trial period") : QLatin1String("Fully licensed"); + appInfo->productName.Set(reinterpret_cast<LPCWSTR>(licenseType.utf16()), licenseType.size()); + appInfo->type = ProductType_Durable; + nativeProducts.insert(qt_win_app_identifier, appInfo); + qCDebug(lcPurchasingBackend) << "App license valid, adding to iap items"; + } + + m_waitingForList = false; + emit q->ready(); + + return S_OK; +} + +ComPtr<IProductLicense> QWinRTInAppPurchaseBackendPrivate::findProductLicense(const QString &identifier) +{ + ComPtr<ILicenseInformation> licenseInfo; + HRESULT hr; + hr = m_bridge.get_LicenseInformation(licenseInfo); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IMapView<HSTRING,ABI::Windows::ApplicationModel::Store::ProductLicense*>> licenses; + hr = licenseInfo->get_ProductLicenses(&licenses); + Q_ASSERT_SUCCEEDED(hr); + + typedef Collections::IKeyValuePair<HSTRING, ProductLicense *> ValueItem; + typedef Collections::IIterable<ValueItem *> ValueIterable; + typedef Collections::IIterator<ValueItem *> ValueIterator; + + ComPtr<ValueIterable> iterable; + ComPtr<ValueIterator> iterator; + + hr = licenses.As(&iterable); + Q_ASSERT_SUCCEEDED(hr); + boolean current = false; + hr = iterable->First(&iterator); + Q_ASSERT_SUCCEEDED(hr); + hr = iterator->get_HasCurrent(¤t); + Q_ASSERT_SUCCEEDED(hr); + + while (SUCCEEDED(hr) && current) { + ComPtr<ValueItem> currentItem; + hr = iterator->get_Current(¤tItem); + Q_ASSERT_SUCCEEDED(hr); + + ComPtr<IProductLicense> productLicense; + hr = currentItem->get_Value(&productLicense); + Q_ASSERT_SUCCEEDED(hr); + + HString productId; + hr = productLicense->get_ProductId(productId.GetAddressOf()); + Q_ASSERT_SUCCEEDED(hr); + + quint32 length; + QString qProductId = QString::fromWCharArray(productId.GetRawBuffer(&length)); + if (qProductId == identifier) { + return productLicense; + } + hr = iterator->MoveNext(¤t); + Q_ASSERT_SUCCEEDED(hr); + } + return ComPtr<IProductLicense>(); +} + +QT_END_NAMESPACE diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend_p.h b/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend_p.h new file mode 100644 index 0000000..ce83b79 --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinapppurchasebackend_p.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QWINRTINAPPPURCHASEBACKEND_P_H +#define QWINRTINAPPPURCHASEBACKEND_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qinapppurchasebackend_p.h" +#include "qinappproduct.h" +#include "qinapptransaction.h" + +QT_BEGIN_NAMESPACE + +class QWinRTInAppProduct; +class QWinRTInAppPurchaseBackendPrivate; +class QWinRTInAppTransaction; + +class QWinRTInAppPurchaseBackend : public QInAppPurchaseBackend +{ + Q_OBJECT +public: + explicit QWinRTInAppPurchaseBackend(QObject *parent = 0); + + void initialize() override; + bool isReady() const override; + + void queryProducts(const QList<Product> &products) override; + void queryProduct(QInAppProduct::ProductType productType, const QString &identifier) override; + void restorePurchases() override; + + void setPlatformProperty(const QString &propertyName, const QString &value) override; + + void purchaseProduct(QWinRTInAppProduct *product); + + void fulfillConsumable(QWinRTInAppTransaction *transaction); +private: + QScopedPointer<QWinRTInAppPurchaseBackendPrivate> d_ptr; + Q_DECLARE_PRIVATE(QWinRTInAppPurchaseBackend) +}; + +QT_END_NAMESPACE + +#endif // QWINRTINAPPPURCHASEBACKEND_P_H diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction.cpp b/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction.cpp new file mode 100644 index 0000000..5e83530 --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction.cpp @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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 "qwinrtinapptransaction_p.h" +#include "qwinrtinapppurchasebackend_p.h" +#include "qinappproduct.h" + +#include <QLoggingCategory> + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcPurchasingTransaction, "qt.purchasing.transaction") + +QWinRTInAppTransaction::QWinRTInAppTransaction(TransactionStatus status, + QInAppProduct *product, FailureReason reason, + QObject *parent) + : QInAppTransaction(status, product, parent) + , m_failureReason(reason) +{ + qCDebug(lcPurchasingTransaction) << __FUNCTION__; + m_backend = qobject_cast<QWinRTInAppPurchaseBackend *>(parent); +} + +void QWinRTInAppTransaction::finalize() +{ + qCDebug(lcPurchasingTransaction) << __FUNCTION__; + if (product()->productType() == QInAppProduct::Consumable && + status() == QInAppTransaction::PurchaseApproved) { + m_backend->fulfillConsumable(this); + } + deleteLater(); +} + +QT_END_NAMESPACE diff --git a/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction_p.h b/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction_p.h new file mode 100644 index 0000000..4a1a752 --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/qwinrtinapptransaction_p.h @@ -0,0 +1,75 @@ +/**************************************************************************** +** +** Copyright (C) 2016 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$ +** +****************************************************************************/ + +#ifndef QWINRTINAPPTRANSACTION_P_H +#define QWINRTINAPPTRANSACTION_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qinapptransaction.h" +#include "qwinrtinapppurchasebackend_p.h" + +#include <QtCore/qobject.h> +#include <QtCore/qdatetime.h> + +#include <Windows.ApplicationModel.store.h> +#include <wrl.h> + +QT_BEGIN_NAMESPACE + +class QWinRTInAppTransaction : public QInAppTransaction +{ + Q_OBJECT +public: + explicit QWinRTInAppTransaction(TransactionStatus status, + QInAppProduct *product, + FailureReason reason, + QObject *parent = Q_NULLPTR); + + FailureReason failureReason() const override { return m_failureReason; } + + void finalize() override; + + Microsoft::WRL::ComPtr<ABI::Windows::ApplicationModel::Store::IPurchaseResults> m_purchaseResults; +private: + QWinRTInAppPurchaseBackend *m_backend; + FailureReason m_failureReason; +}; + +QT_END_NAMESPACE + +#endif // QWINRTINAPPTRANSACTION_P_H diff --git a/src/purchasing/inapppurchase/winrt/winrt.pri b/src/purchasing/inapppurchase/winrt/winrt.pri new file mode 100644 index 0000000..f7eabe8 --- /dev/null +++ b/src/purchasing/inapppurchase/winrt/winrt.pri @@ -0,0 +1,11 @@ +INCLUDEPATH += $$PWD +SOURCES += \ + $$PWD/qwinrtinapppurchasebackend.cpp \ + $$PWD/qwinrtinapptransaction.cpp \ + $$PWD/qwinrtinappproduct.cpp +HEADERS += \ + $$PWD/qwinrtinapppurchasebackend_p.h \ + $$PWD/qwinrtinapptransaction_p.h \ + $$PWD/qwinrtinappproduct_p.h + +QT += core-private |