| /**************************************************************************** |
| ** |
| ** 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"); |
| |
| inline QString hStringToQString(const HString &h) |
| { |
| unsigned int length; |
| const wchar_t* raw = h.GetRawBuffer(&length); |
| return QString::fromWCharArray(raw, length); |
| } |
| |
| class QWinRTAppBridge { |
| public: |
| HRESULT activate(); |
| HRESULT LoadListingInformationAsync(ComPtr<IAsyncOperation<ListingInformation *>> &op); |
| HRESULT GetAppReceiptAsync(ComPtr<IAsyncOperation<HSTRING>> &op); |
| HRESULT GetProductReceiptAsync(HSTRING product, 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::GetProductReceiptAsync(HSTRING product, ComPtr<IAsyncOperation<HSTRING> > &op) |
| { |
| HRESULT hr; |
| if (m_simulate) |
| hr = m_simulator->GetProductReceiptAsync(product, &op); |
| else |
| hr = m_app->GetProductReceiptAsync(product, &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, QWinRTFunctions::YieldThread); |
| 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; |
| } |
| |
| void QWinRTInAppPurchaseBackend::createTransactionDelayed(qt_WinRTTransactionData data) |
| { |
| QInAppTransaction::TransactionStatus qStatus = (data.status == AsyncStatus::Completed) ? |
| QInAppTransaction::PurchaseApproved : QInAppTransaction::PurchaseFailed; |
| |
| QInAppTransaction::FailureReason reason; |
| switch (data.status) { |
| case AsyncStatus::Completed: { |
| reason = QInAppTransaction::NoFailure; |
| if (!data.purchaseResults) |
| break; |
| ProductPurchaseStatus purchaseStatus; |
| HRESULT hr = data.purchaseResults->get_Status(&purchaseStatus); |
| if (FAILED(hr)) { |
| qWarning("Could not query purchase status for transaction."); |
| break; |
| } |
| switch (purchaseStatus) { |
| case ProductPurchaseStatus_Succeeded: |
| reason = QInAppTransaction::NoFailure; |
| break; |
| case ProductPurchaseStatus_NotFulfilled: |
| case ProductPurchaseStatus_NotPurchased: |
| reason = QInAppTransaction::CanceledByUser; |
| qStatus = QInAppTransaction::PurchaseFailed; |
| break; |
| case ProductPurchaseStatus_AlreadyPurchased: |
| default: |
| reason = QInAppTransaction::ErrorOccurred; |
| qStatus = QInAppTransaction::PurchaseFailed; |
| break; |
| } |
| |
| break; |
| } |
| case AsyncStatus::Canceled: |
| reason = QInAppTransaction::CanceledByUser; |
| break; |
| case AsyncStatus::Error: |
| default: |
| reason = QInAppTransaction::ErrorOccurred; |
| break; |
| } |
| |
| auto transaction = new QWinRTInAppTransaction(qStatus, data.product, reason, data.receipt, this); |
| transaction->m_purchaseResults = data.purchaseResults; |
| qCDebug(lcPurchasingBackend) << "Emitting Transaction:" << qStatus << "/" |
| << reason << " Receipt:" << data.receipt; |
| emit transactionReady(transaction); |
| |
| return; |
| } |
| |
| 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)); |
| |
| qRegisterMetaType<qt_WinRTTransactionData>("qt_WinRTTransactionData"); |
| |
| 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_bridge.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(); |
| } |
| |
| inline QString createStringForSubReceipt(const QXmlStreamReader &reader) |
| { |
| QString result; |
| QXmlStreamWriter writer(&result); |
| writer.writeStartDocument(); |
| writer.writeCurrentToken(reader); |
| writer.writeEndDocument(); |
| return result; |
| } |
| |
| 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 app receipt query"; |
| return; |
| } |
| |
| const QString parse = hStringToQString(receipt); |
| |
| qCDebug(lcPurchasingBackend) << "Receipt:" << parse; |
| |
| 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; |
| } |
| |
| const QString appReceipt = createStringForSubReceipt(reader); |
| |
| while (!reader.atEnd()) { |
| reader.readNext(); |
| if (reader.attributes().hasAttribute(QLatin1String("ProductId"))) { |
| const QString id = reader.attributes().value(QLatin1String("ProductId")).toString(); |
| qCDebug(lcPurchasingBackend) << " Found Product " << id << "to restore"; |
| if (d->nativeProducts.contains(id)) { |
| qCDebug(lcPurchasingBackend) << "Restoring:" << id; |
| |
| QUuid uuid(reader.attributes().value(QLatin1String("Id")).toString()); |
| if (uuid.isNull()) { |
| qCDebug(lcPurchasingBackend) << "Product " << id << " restoration failed due to " |
| << "no transaction id"; |
| continue; |
| } |
| QInAppProduct *product = store()->registeredProduct(id); |
| if (!product) { |
| qCDebug(lcPurchasingBackend) << "Product " << id << "has been bought, but is unknown"; |
| continue; |
| } |
| |
| |
| const QString receipt = createStringForSubReceipt(reader); |
| |
| auto transaction = new QWinRTInAppTransaction(QInAppTransaction::PurchaseRestored, |
| product, |
| QInAppTransaction::NoFailure, |
| receipt, |
| this); |
| transaction->m_uuid = uuid; |
| 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); |
| // App is special and needs explicit registration |
| if (!product) { |
| queryProduct(QInAppProduct::Unlockable, qt_win_app_identifier); |
| product = store()->registeredProduct(qt_win_app_identifier); |
| } |
| |
| auto transaction = new QWinRTInAppTransaction(QInAppTransaction::PurchaseRestored, |
| product, |
| QInAppTransaction::NoFailure, |
| appReceipt, |
| 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 |
| qCDebug(lcPurchasingBackend) << "Experimenting with Product Receipts"; |
| 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_bridge.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; |
| } |
| |
| const QString receipt = hStringToQString(receiptString); |
| qDebug() << "Received receipt for " << item << ":" << 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; |
| } |
| |
| // With the latest Windows Store Updates, product licenses seem to be not working |
| // anymore. Hence, we have to rely on the listing being correct. |
| // 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; |
| // } |
| |
| NativeProductInfo *cachedInfo = d->nativeProducts.value(identifier); |
| const QString price = hStringToQString(cachedInfo->formatPrice); |
| const QString name = hStringToQString(cachedInfo->productName); |
| 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(true, appOp); |
| Q_ASSERT_SUCCEEDED(hr); |
| auto purchaseCallback = Callback<IAsyncOperationCompletedHandler<HSTRING>>([d, product, this](IAsyncOperation<HSTRING> *op, AsyncStatus status) |
| { |
| HString receiptH; |
| QString receiptQ; |
| HRESULT hr; |
| hr = op->GetResults(receiptH.GetAddressOf()); |
| if (SUCCEEDED(hr)) |
| receiptQ = hStringToQString(receiptH); |
| else |
| qWarning("Could not receive transaction receipt."); |
| |
| qt_WinRTTransactionData tData(status, product, receiptQ); |
| QMetaObject::invokeMethod(this, "createTransactionDelayed", Qt::QueuedConnection, |
| Q_ARG(qt_WinRTTransactionData, tData)); |
| |
| return S_OK; |
| }); |
| hr = appOp->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) |
| { |
| ComPtr<IPurchaseResults> purchaseResults; |
| QString receiptQ; |
| |
| if (status == AsyncStatus::Completed) { |
| HRESULT hr; |
| hr = op->GetResults(&purchaseResults); |
| Q_ASSERT_SUCCEEDED(hr); |
| HString receiptH; |
| hr = purchaseResults->get_ReceiptXml(receiptH.GetAddressOf()); |
| Q_ASSERT_SUCCEEDED(hr); |
| receiptQ = hStringToQString(receiptH); |
| } |
| |
| qt_WinRTTransactionData tData(status, product, receiptQ, purchaseResults); |
| QMetaObject::invokeMethod(this, "createTransactionDelayed", Qt::QueuedConnection, |
| Q_ARG(qt_WinRTTransactionData, tData)); |
| |
| 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; |
| if (transaction->m_uuid.isNull()) { |
| hr = transaction->m_purchaseResults->get_TransactionId(&transactionId); |
| Q_ASSERT_SUCCEEDED(hr); |
| } else |
| transactionId = transaction->m_uuid; |
| |
| 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_Q(QWinRTInAppPurchaseBackend); |
| |
| qCDebug(lcPurchasingBackend) << __FUNCTION__; |
| |
| if (status != AsyncStatus::Completed) { |
| qCDebug(lcPurchasingBackend) << "Loading of listing information failed."; |
| return S_OK; |
| } |
| |
| 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; |
| } |
| |
| unsigned int amount = 0; |
| productListings->get_Size(&amount); |
| qCDebug(lcPurchasingBackend) << " Found " << amount << " products registered in the store."; |
| |
| 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 = hStringToQString(nativeKey); |
| 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); |
| if (SUCCEEDED(hr)) { |
| hr = converted->get_ProductType(&nativeInfo->type); |
| Q_ASSERT_SUCCEEDED(hr); |
| } else { |
| qWarning("Could not acquire product type. Assuming Unlockable"); |
| nativeInfo->type = ProductType_Durable; |
| } |
| |
| 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); |
| |
| qCDebug(lcPurchasingBackend) << "App registration: Is Active: " << active << " Is Trial: " << trial; |
| 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 |