blob: 5d5dab47c405f42b6bc136d3fb825b9436f95824 [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 "qmacinapppurchasebackend_p.h"
#include "qmacinapppurchaseproduct_p.h"
#include "qmacinapppurchasetransaction_p.h"
#include <QtCore/QString>
#import <StoreKit/StoreKit.h>
@interface QT_MANGLE_NAMESPACE(InAppPurchaseManager) : NSObject <SKProductsRequestDelegate, SKPaymentTransactionObserver>
{
QMacInAppPurchaseBackend *backend;
NSMutableArray<SKPaymentTransaction *> *pendingTransactions;
}
-(void)requestProductData:(NSString *)identifier;
-(void)processPendingTransactions;
@end
@implementation QT_MANGLE_NAMESPACE(InAppPurchaseManager)
-(id)initWithBackend:(QMacInAppPurchaseBackend *)iapBackend {
if (self = [super init]) {
backend = iapBackend;
pendingTransactions = [[NSMutableArray<SKPaymentTransaction *> alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
qRegisterMetaType<QMacInAppPurchaseProduct*>("QMacInAppPurchaseProduct*");
qRegisterMetaType<QMacInAppPurchaseTransaction*>("QMacInAppPurchaseTransaction*");
}
return self;
}
-(void)dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[pendingTransactions release];
[super dealloc];
}
-(void)requestProductData:(NSString *)identifier
{
NSSet<NSString *> *productId = [NSSet<NSString *> setWithObject:identifier];
SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId];
productsRequest.delegate = self;
[productsRequest start];
}
-(void)processPendingTransactions
{
NSMutableArray<SKPaymentTransaction *> *registeredTransactions = [NSMutableArray<SKPaymentTransaction *> array];
for (SKPaymentTransaction *transaction in pendingTransactions) {
QInAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];
QMacInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));
if (product) {
//It is possible that the product doesn't exist yet (because of previous restores).
QMacInAppPurchaseTransaction *qtTransaction = new QMacInAppPurchaseTransaction(transaction, status, product);
if (qtTransaction->thread() != backend->thread()) {
qtTransaction->moveToThread(backend->thread());
QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
}
[registeredTransactions addObject:transaction];
QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseTransaction*, qtTransaction));
}
}
//Remove registeredTransactions from pendingTransactions
[pendingTransactions removeObjectsInArray:registeredTransactions];
}
//SKProductsRequestDelegate
-(void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
NSArray<SKProduct *> *products = response.products;
SKProduct *product = [products count] == 1 ? [[products firstObject] retain] : nil;
if (product == nil) {
//Invalid product ID
NSString *invalidId = [response.invalidProductIdentifiers firstObject];
QMetaObject::invokeMethod(backend, "registerQueryFailure", Qt::AutoConnection, Q_ARG(QString, QString::fromNSString(invalidId)));
} else {
//Valid product query
//Create a QMacInAppPurchaseProduct
QMacInAppPurchaseProduct *validProduct = new QMacInAppPurchaseProduct(product, backend->productTypeForProductId(QString::fromNSString([product productIdentifier])));
if (validProduct->thread() != backend->thread()) {
validProduct->moveToThread(backend->thread());
QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, validProduct));
}
QMetaObject::invokeMethod(backend, "registerProduct", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseProduct*, validProduct));
}
[request release];
}
+(QInAppTransaction::TransactionStatus)statusFromTransaction:(SKPaymentTransaction *)transaction
{
QInAppTransaction::TransactionStatus status;
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchasing:
//Ignore the purchasing state as it's not really a transaction
//And its important that it doesn't need to be finalized as
//Calling finishTransaction: on a transaction that is
//in the SKPaymentTransactionStatePurchasing state throws an exception
status = QInAppTransaction::Unknown;
break;
case SKPaymentTransactionStatePurchased:
status = QInAppTransaction::PurchaseApproved;
break;
case SKPaymentTransactionStateFailed:
status = QInAppTransaction::PurchaseFailed;
break;
case SKPaymentTransactionStateRestored:
status = QInAppTransaction::PurchaseRestored;
break;
default:
status = QInAppTransaction::Unknown;
break;
}
return status;
}
//SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
Q_UNUSED(queue);
for (SKPaymentTransaction *transaction in transactions) {
//Create QMacInAppPurchaseTransaction
QInAppTransaction::TransactionStatus status = [QT_MANGLE_NAMESPACE(InAppPurchaseManager) statusFromTransaction:transaction];
if (status == QInAppTransaction::Unknown)
continue;
QMacInAppPurchaseProduct *product = backend->registeredProductForProductId(QString::fromNSString(transaction.payment.productIdentifier));
if (product) {
//It is possible that the product doesn't exist yet (because of previous restores).
QMacInAppPurchaseTransaction *qtTransaction = new QMacInAppPurchaseTransaction(transaction, status, product);
if (qtTransaction->thread() != backend->thread()) {
qtTransaction->moveToThread(backend->thread());
QMetaObject::invokeMethod(backend, "setParentToBackend", Qt::AutoConnection, Q_ARG(QObject*, qtTransaction));
}
QMetaObject::invokeMethod(backend, "registerTransaction", Qt::AutoConnection, Q_ARG(QMacInAppPurchaseTransaction*, qtTransaction));
} else {
//Add the transaction to the pending transactions list
[pendingTransactions addObject:transaction];
}
}
}
@end
QT_BEGIN_NAMESPACE
QMacInAppPurchaseBackend::QMacInAppPurchaseBackend(QObject *parent)
: QInAppPurchaseBackend(parent)
, m_iapManager(0)
{
}
QMacInAppPurchaseBackend::~QMacInAppPurchaseBackend()
{
[m_iapManager release];
}
void QMacInAppPurchaseBackend::initialize()
{
m_iapManager = [[QT_MANGLE_NAMESPACE(InAppPurchaseManager) alloc] initWithBackend:this];
emit QInAppPurchaseBackend::ready();
}
bool QMacInAppPurchaseBackend::isReady() const
{
if (m_iapManager)
return true;
return false;
}
void QMacInAppPurchaseBackend::queryProduct(QInAppProduct::ProductType productType, const QString &identifier)
{
Q_UNUSED(productType)
if (m_productTypeForPendingId.contains(identifier)) {
qWarning("Product query already pending for %s", qPrintable(identifier));
return;
}
m_productTypeForPendingId[identifier] = productType;
[m_iapManager requestProductData:(identifier.toNSString())];
}
void QMacInAppPurchaseBackend::restorePurchases()
{
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
void QMacInAppPurchaseBackend::setPlatformProperty(const QString &propertyName, const QString &value)
{
Q_UNUSED(propertyName);
Q_UNUSED(value);
}
void QMacInAppPurchaseBackend::registerProduct(QMacInAppPurchaseProduct *product)
{
QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(product->identifier());
Q_ASSERT(it != m_productTypeForPendingId.end());
m_registeredProductForId[product->identifier()] = product;
emit productQueryDone(product);
m_productTypeForPendingId.erase(it);
[m_iapManager processPendingTransactions];
}
void QMacInAppPurchaseBackend::registerQueryFailure(const QString &productId)
{
QHash<QString, QInAppProduct::ProductType>::iterator it = m_productTypeForPendingId.find(productId);
Q_ASSERT(it != m_productTypeForPendingId.end());
emit QInAppPurchaseBackend::productQueryFailed(it.value(), it.key());
m_productTypeForPendingId.erase(it);
}
void QMacInAppPurchaseBackend::registerTransaction(QMacInAppPurchaseTransaction *transaction)
{
emit QInAppPurchaseBackend::transactionReady(transaction);
}
QInAppProduct::ProductType QMacInAppPurchaseBackend::productTypeForProductId(const QString &productId)
{
return m_productTypeForPendingId[productId];
}
QMacInAppPurchaseProduct *QMacInAppPurchaseBackend::registeredProductForProductId(const QString &productId)
{
return m_registeredProductForId[productId];
}
void QMacInAppPurchaseBackend::setParentToBackend(QObject *object)
{
object->setParent(this);
}
QT_END_NAMESPACE
#include "moc_qmacinapppurchasebackend_p.cpp"