From aa28a4db6e3842935f24b393aa4abbab20491c51 Mon Sep 17 00:00:00 2001 From: Peter Petrik Date: Wed, 11 Oct 2023 12:21:45 +0200 Subject: [PATCH] remove of purchasing backend --- CMakeLists.txt | 25 +- app/CMakeLists.txt | 18 - app/inputhelp.cpp | 5 + app/ios/iospurchasing.h | 120 ----- app/ios/iospurchasing.mm | 354 ------------- app/main.cpp | 6 +- app/purchasing.cpp | 717 -------------------------- app/purchasing.h | 338 ------------ app/qml/AccountPage.qml | 33 +- app/qml/ProjectPanel.qml | 3 - app/qml/WorkspaceAccountPage.qml | 29 +- app/test/inputtests.cpp | 19 +- app/test/inputtests.h | 4 +- app/test/testingpurchasingbackend.cpp | 164 ------ app/test/testingpurchasingbackend.h | 103 ---- app/test/testmerginapi.cpp | 6 +- app/test/testmerginapi.h | 6 +- app/test/testpurchasing.cpp | 173 ------- app/test/testpurchasing.h | 45 -- app/test/testutils.cpp | 44 -- app/test/testutils.h | 9 - cmake/FindAppleFrameworks.cmake | 4 - cmake_templates/inputconfig.h.in | 4 - core/merginapi.h | 4 - test/CMakeLists.txt | 1 - 25 files changed, 16 insertions(+), 2218 deletions(-) delete mode 100644 app/ios/iospurchasing.h delete mode 100644 app/ios/iospurchasing.mm delete mode 100644 app/purchasing.cpp delete mode 100644 app/purchasing.h delete mode 100644 app/test/testingpurchasingbackend.cpp delete mode 100644 app/test/testingpurchasingbackend.h delete mode 100644 app/test/testpurchasing.cpp delete mode 100644 app/test/testpurchasing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bf353ed86..a2761c5b1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -81,14 +81,6 @@ else () set(HAVE_BLUETOOTH_DEFAULT TRUE) endif () -if (IOS) - set(HAVE_PURCHASING_DEFAULT TRUE) - set(HAVE_APPLE_PURCHASING_DEFAULT TRUE) -else () - set(HAVE_PURCHASING_DEFAULT ${ENABLE_TESTS_DEFAULT}) - set(HAVE_APPLE_PURCHASING_DEFAULT FALSE) -endif () - # on android in multi-ABI build, command line variables are NOT passed to # ExternalProject_Add called by QT. Therefore we need to pass the variables through ENV if (ANDROID) @@ -150,14 +142,7 @@ set(HAVE_BLUETOOTH ${HAVE_BLUETOOTH_DEFAULT} CACHE BOOL "Building with bluetooth position provider" ) -set(HAVE_PURCHASING - ${HAVE_PURCHASING_DEFAULT} - CACHE BOOL "Build with purchasing (e.g. for test of purchasing GUI)" -) -set(HAVE_APPLE_PURCHASING - ${HAVE_APPLE_PURCHASING_DEFAULT} - CACHE BOOL "Building with Apple's StoreKit support" -) + set(QT6_VERSION ${QT_VERSION_DEFAULT} CACHE STRING "QT6 version to use" @@ -343,14 +328,6 @@ if (ENABLE_TESTS) set(TEST_DATA_DIR "${CMAKE_CURRENT_BINARY_DIR}/test/test_data") endif () -if (HAVE_PURCHASING) - set(PURCHASING TRUE) -endif () - -if (HAVE_APPLE_PURCHASING) - set(APPLE_PURCHASING TRUE) -endif () - configure_file( ${CMAKE_SOURCE_DIR}/cmake_templates/inputconfig.h.in ${CMAKE_BINARY_DIR}/inputconfig.h ) diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b13513dbd..7f1080650 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -68,7 +68,6 @@ set(MM_SRCS projectsmodel.cpp projectsproxymodel.cpp projectwizard.cpp - purchasing.cpp qrdecoder.cpp relationfeaturesmodel.cpp relationreferencefeaturesmodel.cpp @@ -149,7 +148,6 @@ set(MM_HDRS projectsmodel.h projectsproxymodel.h projectwizard.h - purchasing.h qrdecoder.h relationfeaturesmodel.h relationreferencefeaturesmodel.h @@ -181,14 +179,12 @@ if (ENABLE_TESTS) test/testformeditors.cpp test/testidentifykit.cpp test/testimageutils.cpp - test/testingpurchasingbackend.cpp test/testlayertree.cpp test/testlinks.cpp test/testmaptools.cpp test/testmerginapi.cpp test/testmodels.cpp test/testposition.cpp - test/testpurchasing.cpp test/testrememberattributescontroller.cpp test/testscalebarkit.cpp test/testutils.cpp @@ -207,14 +203,12 @@ if (ENABLE_TESTS) test/testformeditors.h test/testidentifykit.h test/testimageutils.h - test/testingpurchasingbackend.h test/testlayertree.h test/testlinks.h test/testmaptools.h test/testmerginapi.h test/testmodels.h test/testposition.h - test/testpurchasing.h test/testrememberattributescontroller.h test/testscalebarkit.h test/testutils.h @@ -246,12 +240,6 @@ if (IOS) ) endif () -if (HAVE_APPLE_PURCHASING) - set(MM_HDRS ${MM_HDRS} ios/iospurchasing.h) - - set(MM_SRCS ${MM_SRCS} ios/iospurchasing.mm) -endif () - if (ANDROID) set(MM_HDRS ${MM_HDRS} position/tracking/androidtrackingbackend.h position/tracking/androidtrackingbroadcast.h @@ -538,12 +526,6 @@ if (IOS) target_link_libraries(Input PUBLIC AppleFrameworks::CoreLocation) endif () -if (HAVE_APPLE_PURCHASING) - target_link_libraries( - Input PUBLIC AppleFrameworks::StoreKit AppleFrameworks::Foundation - ) -endif () - if (ENABLE_TESTS) target_link_libraries(Input PUBLIC Qt6::Test) endif () diff --git a/app/inputhelp.cpp b/app/inputhelp.cpp index dff880a2d..ce55de6b5 100644 --- a/app/inputhelp.cpp +++ b/app/inputhelp.cpp @@ -164,6 +164,11 @@ QString InputHelp::whatsNewPostLink() const return inputWeb + "/blog/introducing-workspaces-simplified-collaboration" + utmTagOther; } +QString InputHelp::subscriptionBillingUrl() const +{ + +} + bool InputHelp::submitReportPending() const { return mSubmitReportPending; diff --git a/app/ios/iospurchasing.h b/app/ios/iospurchasing.h deleted file mode 100644 index e333c71c7..000000000 --- a/app/ios/iospurchasing.h +++ /dev/null @@ -1,120 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef IOSPURCHASING_H -#define IOSPURCHASING_H - -#include -#include -#include -#include - -#include "inputconfig.h" -#include "purchasing.h" - -class IosPurchasingBackend; -Q_FORWARD_DECLARE_OBJC_CLASS( InAppPurchaseManager ); -Q_FORWARD_DECLARE_OBJC_CLASS( SKProduct ); -Q_FORWARD_DECLARE_OBJC_CLASS( SKPaymentTransaction ); - -/** - * Wrapper around SKProduct class - */ -class IosPurchasingPlan: public PurchasingPlan -{ - Q_OBJECT - public: - SKProduct *nativeProduct() const; - IosPurchasingBackend *backend() const; - void setNativeProduct( SKProduct *nativeProduct ); - - private: - SKProduct *mNativeProduct; //this is objective-C instance -}; - -/** - * Wrapper around SKPaymentTransaction class - */ -class IosPurchasingTransaction: public PurchasingTransaction -{ - Q_OBJECT - public: - enum TransactionStatus - { - Unknown, - PurchaseApproved, - PurchaseFailed, - PurchaseRestored - }; - Q_ENUM( TransactionStatus ) - - IosPurchasingTransaction( SKPaymentTransaction *transaction, - TransactionStatus status, - QSharedPointer plan - ); - - IosPurchasingPlan *iosPlan() const; - - SKPaymentTransaction *nativeTransaction() const; - TransactionStatus status() const; - - QString receipt() const override; - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::AppleSubscriptionType; } - - /** - * Localized ios error message from the native framework, - * populated when TransactionStatus::PurchaseFailed - */ - QString errMsg() const; - - void finalizeTransaction() override; - - private: - TransactionType status2type( TransactionStatus status ); - - TransactionStatus mStatus; - SKPaymentTransaction *mNativeTransaction; //objective-C instance -}; - -/** - * Backend encapsulating the ios purchasing methods. - * Contains InAppPurchaseManager, - * which is Objective-C implementation of - * KProductsRequestDelegate and SKPaymentTransactionObserver - */ -class IosPurchasingBackend: public PurchasingBackend -{ - Q_OBJECT - public: - IosPurchasingBackend(); - ~IosPurchasingBackend() override; - - void init() override; - QSharedPointer createPlan() override {return QSharedPointer( new IosPurchasingPlan() );} - void registerPlan( QSharedPointer plan ) override; - - void createTransaction( QSharedPointer plan ) override; - void restore() override; - - QString subscriptionManageUrl() override {return "https://apps.apple.com/account/subscriptions"; } - QString subscriptionBillingUrl() override {return "https://apps.apple.com/account/billing"; } - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::AppleSubscriptionType; } - bool userCanMakePayments() const override; - bool hasManageSubscriptionCapability() const override { return false; } - QString getLocalizedPrice( const QString &planId ) const override; - - QSharedPointer findRegisteredPlan( const QString &productId ) const; - QSharedPointer findPendingPlan( const QString &productId ) const; - void processTransaction( QSharedPointer transaction ); - - private: - InAppPurchaseManager *mManager; //this is objective-C instance -}; - -#endif // IOSPURCHASING_H diff --git a/app/ios/iospurchasing.mm b/app/ios/iospurchasing.mm deleted file mode 100644 index cf84f67ff..000000000 --- a/app/ios/iospurchasing.mm +++ /dev/null @@ -1,354 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ -#include -#include "inpututils.h" -#include "coreutils.h" - -#import "iospurchasing.h" -#import - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -SKProduct *IosPurchasingPlan::nativeProduct() const -{ - return mNativeProduct; -} - -IosPurchasingBackend *IosPurchasingPlan::backend() const -{ - - return static_cast( purchasing()->backend() ); -} - -void IosPurchasingPlan::setNativeProduct( SKProduct *nativeProduct ) -{ - mNativeProduct = nativeProduct; -} - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -IosPurchasingTransaction::IosPurchasingTransaction( SKPaymentTransaction *transaction, TransactionStatus status, - QSharedPointer plan - ) - : PurchasingTransaction( status2type( status ), plan ) - , mStatus( status ) - , mNativeTransaction( transaction ) -{ -} - -IosPurchasingPlan *IosPurchasingTransaction::iosPlan() const -{ - return static_cast( plan() ); -} - -SKPaymentTransaction *IosPurchasingTransaction::nativeTransaction() const -{ - return mNativeTransaction; -} - -IosPurchasingTransaction::TransactionStatus IosPurchasingTransaction::status() const -{ - return mStatus; -} - -QString IosPurchasingTransaction::receipt() const -{ - NSData *dataReceipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]]; - NSString *receipt = [dataReceipt base64EncodedStringWithOptions:0]; - return QString::fromNSString( receipt ); -} - -QString IosPurchasingTransaction::errMsg() const -{ - NSString *nativeStr = [ mNativeTransaction.error localizedDescription ]; - QString err = QString::fromNSString( nativeStr ); - return err; -} - -void IosPurchasingTransaction::finalizeTransaction() -{ - [[SKPaymentQueue defaultQueue] finishTransaction:mNativeTransaction]; -} - -PurchasingTransaction::TransactionType IosPurchasingTransaction::status2type( IosPurchasingTransaction::TransactionStatus status ) -{ - if ( status == TransactionStatus::PurchaseRestored ) - return PurchasingTransaction::RestoreTransaction; - else - { - return PurchasingTransaction::PuchaseTransaction; - } -} - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - -@interface InAppPurchaseManager : NSObject -{ - IosPurchasingBackend *backend; - NSMutableArray *pendingTransactions; -} - --( void )requestProductData:( NSString * )identifier; --( void )processPendingTransactions; - -@end - -@implementation InAppPurchaseManager - --( id )initWithBackend:( IosPurchasingBackend * )iapBackend -{ - if ( ( self = [super init] ) ) - { - backend = iapBackend; - pendingTransactions = [[NSMutableArray alloc] init]; - [[SKPaymentQueue defaultQueue] addTransactionObserver:self]; - } - return self; -} - --( void )dealloc -{ - [[SKPaymentQueue defaultQueue] removeTransactionObserver:self]; - [pendingTransactions release]; - [super dealloc]; -} - --( void )requestProductData:( NSString * )identifier -{ - NSSet *productId = [NSSet setWithObject:identifier]; - SKProductsRequest *productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId]; - productsRequest.delegate = self; - [productsRequest start]; -} - --( void )productsRequest:( SKProductsRequest * )request didReceiveResponse:( SKProductsResponse * )response -{ - NSArray *products = response.products; - SKProduct *product = [products count] == 1 ? [[products firstObject] retain] : nil; - - if ( product == nil ) - { - // failed to fetch product id from store - NSString *invalidId = [response.invalidProductIdentifiers firstObject]; - QMetaObject::invokeMethod( backend, "planRegistrationFailed", Qt::AutoConnection, Q_ARG( QString, QString::fromNSString( invalidId ) ) ); - } - else - { - QSharedPointer plan = backend->findPendingPlan( QString::fromNSString( [product productIdentifier] ) ); - if ( plan ) - { - plan->setNativeProduct( product ); - - NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - NSString *formattedPrice = [numberFormatter stringFromNumber:product.price]; - plan->setPrice( QString::fromNSString( formattedPrice ) ); - - QMetaObject::invokeMethod( backend, "planRegistrationSucceeded", Qt::AutoConnection, Q_ARG( QString, plan->id() ) ); - } - else - { - QMetaObject::invokeMethod( backend, "planRegistrationFailed", Qt::AutoConnection, Q_ARG( QString, QString::fromNSString( [product productIdentifier] ) ) ); - } - } - - [request release]; -} - -+( IosPurchasingTransaction::TransactionStatus )statusFromTransaction:( SKPaymentTransaction * )transaction -{ - IosPurchasingTransaction::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 = IosPurchasingTransaction::Unknown; - break; - case SKPaymentTransactionStatePurchased: - status = IosPurchasingTransaction::PurchaseApproved; - break; - case SKPaymentTransactionStateFailed: - status = IosPurchasingTransaction::PurchaseFailed; - break; - case SKPaymentTransactionStateRestored: - status = IosPurchasingTransaction::PurchaseRestored; - break; - default: - status = IosPurchasingTransaction::Unknown; - break; - } - return status; -} - --( void )processPendingTransactions -{ - NSMutableArray *registeredTransactions = [NSMutableArray array]; - - for ( SKPaymentTransaction * transaction in pendingTransactions ) - { - IosPurchasingTransaction::TransactionStatus status = [InAppPurchaseManager statusFromTransaction:transaction]; - - QSharedPointer plan = backend->findRegisteredPlan( QString::fromNSString( transaction.payment.productIdentifier ) ); - if ( plan ) - { - //It is possible that the product doesn't exist yet (because of previous restores). - QSharedPointer qtTransaction( new IosPurchasingTransaction( transaction, status, plan ) ); - if ( qtTransaction->thread() != backend->thread() ) - { - qtTransaction->moveToThread( backend->thread() ); - } - [registeredTransactions addObject:transaction]; - backend->processTransaction( qtTransaction ); - } - } - - //Remove registeredTransactions from pendingTransactions - [pendingTransactions removeObjectsInArray:registeredTransactions]; -} - -//SKPaymentTransactionObserver -- ( void )paymentQueue:( SKPaymentQueue * )queue updatedTransactions:( NSArray * )transactions -{ - Q_UNUSED( queue ) - for ( SKPaymentTransaction * transaction in transactions ) - { - //Create IosTransaction - IosPurchasingTransaction::TransactionStatus status = [InAppPurchaseManager statusFromTransaction:transaction]; - - if ( status == IosPurchasingTransaction::Unknown ) - continue; - - QSharedPointer plan = backend->findRegisteredPlan( QString::fromNSString( transaction.payment.productIdentifier ) ); - if ( plan ) - { - QSharedPointer qtTransaction( new IosPurchasingTransaction( transaction, status, plan ) ); - if ( qtTransaction->thread() != backend->thread() ) - { - qtTransaction->moveToThread( backend->thread() ); - } - backend->processTransaction( qtTransaction ); - } - else - { - // Add the transaction to the pending transactions list, since product may not be yet registered - [pendingTransactions addObject:transaction]; - } - } -} - -@end - -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ -/* ********************************************************************************************************************************************/ - - -IosPurchasingBackend::IosPurchasingBackend() - : mManager( 0 ) -{ -} - -IosPurchasingBackend::~IosPurchasingBackend() -{ - [mManager release]; -} - -void IosPurchasingBackend::init() -{ - mManager = [[InAppPurchaseManager alloc] initWithBackend:this]; -} - -void IosPurchasingBackend::registerPlan( QSharedPointer plan ) -{ - [mManager requestProductData:( plan->id().toNSString() )]; -} - -void IosPurchasingBackend::createTransaction( QSharedPointer plan ) -{ - QSharedPointer iosPlan = qSharedPointerObjectCast ( plan ); - SKPayment *payment = [SKPayment paymentWithProduct:iosPlan->nativeProduct()]; - [[SKPaymentQueue defaultQueue] addPayment:payment]; -} - -void IosPurchasingBackend::restore() -{ - [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; -} - -void IosPurchasingBackend::processTransaction( QSharedPointer transaction ) -{ - if ( transaction->status() == IosPurchasingTransaction::PurchaseApproved ) - { - emit transactionCreationSucceeded( transaction ); - } - else if ( transaction->status() == IosPurchasingTransaction::PurchaseRestored ) - { - emit transactionCreationSucceeded( transaction ); - } - else if ( transaction->status() == IosPurchasingTransaction::PurchaseFailed ) - { - CoreUtils::log( "transaction creation", QStringLiteral( "Failed: " ) + transaction->errMsg() ); - emit transactionCreationFailed(); - transaction->finalizeTransaction(); - } - - qDebug() << "Transaction for product " << transaction->plan()->id() << " processed with status " << transaction->status(); -} - -bool IosPurchasingBackend::userCanMakePayments() const -{ - return [SKPaymentQueue canMakePayments]; -} - -QString IosPurchasingBackend::getLocalizedPrice( const QString &planId ) const -{ - QSharedPointer plan = findRegisteredPlan( planId ); - if ( plan ) - { - return plan->price(); - } - return QString(); -} - -QSharedPointer IosPurchasingBackend::findRegisteredPlan( const QString &productId ) const -{ - Purchasing *p = purchasing(); - Q_ASSERT( p ); - - QSharedPointer plan = p->registeredPlan( productId ); - if ( !plan ) - return nullptr; - - return qSharedPointerObjectCast( plan ); -} - -QSharedPointer IosPurchasingBackend::findPendingPlan( const QString &productId ) const -{ - Purchasing *p = purchasing(); - Q_ASSERT( p ); - - QSharedPointer plan = p->pendingPlan( productId ); - if ( !plan ) - return QSharedPointer(); - - return qSharedPointerObjectCast( plan ); -} - -#include "moc_iospurchasing.cpp" diff --git a/app/main.cpp b/app/main.cpp index edee257ea..4757ac9de 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -50,7 +50,6 @@ #include "layersproxymodel.h" #include "layersmodel.h" #include "activelayer.h" -#include "purchasing.h" #include "merginuserauth.h" #include "merginuserinfo.h" #include "variablesmanager.h" @@ -257,7 +256,6 @@ void initDeclarative() qmlRegisterUncreatableType( "lc", 1, 0, "MerginUserAuth", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginUserInfo", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "MerginSubscriptionInfo", "" ); - qmlRegisterUncreatableType( "lc", 1, 0, "MerginPlan", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "ActiveProject", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "SynchronizationManager", "" ); qmlRegisterUncreatableType( "lc", 1, 0, "SyncError", "SyncError Enum" ); @@ -491,7 +489,6 @@ int main( int argc, char *argv[] ) ActiveLayer al; ActiveProject activeProject( as, al, recordingLpm, localProjectsManager ); - std::unique_ptr purchasing( new Purchasing( ma.get() ) ); std::unique_ptr vm( new VariablesManager( ma.get() ) ); vm->registerInputExpressionFunctions(); @@ -570,7 +567,7 @@ int main( int argc, char *argv[] ) if ( tests.testingRequested() ) { tests.initTestDeclarative(); - tests.init( ma.get(), purchasing.get(), &iu, vm.get(), &pk, &as ); + tests.init( ma.get(), &iu, vm.get(), &pk, &as ); return tests.runTest(); } #endif @@ -611,7 +608,6 @@ int main( int argc, char *argv[] ) engine.rootContext()->setContextProperty( "__merginProjectStatusModel", &mpsm ); engine.rootContext()->setContextProperty( "__recordingLayersModel", &recordingLpm ); engine.rootContext()->setContextProperty( "__activeLayer", &al ); - engine.rootContext()->setContextProperty( "__purchasing", purchasing.get() ); engine.rootContext()->setContextProperty( "__projectWizard", &pw ); engine.rootContext()->setContextProperty( "__localProjectsManager", &localProjectsManager ); engine.rootContext()->setContextProperty( "__variablesManager", vm.get() ); diff --git a/app/purchasing.cpp b/app/purchasing.cpp deleted file mode 100644 index 38b3c6c8f..000000000 --- a/app/purchasing.cpp +++ /dev/null @@ -1,717 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include -#include -#include - -#include "purchasing.h" -#include "merginapi.h" -#include "inpututils.h" -#include "merginuserinfo.h" -#include "merginsubscriptioninfo.h" -#include "coreutils.h" - -#if defined (APPLE_PURCHASING) -#include "ios/iospurchasing.h" -#endif -#if defined (INPUT_TEST) -#include "test/testingpurchasingbackend.h" -#endif - -void PurchasingPlan::clear() -{ - mAlias = QString(); - mId = QString(); - mPeriod = QString(); - mPrice = QString(); - mStorage = QString(); -} - -QString PurchasingPlan::alias() const -{ - return mAlias; -} - -void PurchasingPlan::setAlias( const QString &alias ) -{ - mAlias = alias; - emit planChanged(); -} - -QString PurchasingPlan::id() const -{ - return mId; -} - -void PurchasingPlan::setId( const QString &billingProductId ) -{ - mId = billingProductId; - emit planChanged(); -} - -QString PurchasingPlan::period() const -{ - return mPeriod; -} - -void PurchasingPlan::setPeriod( const QString &billingPeriod ) -{ - mPeriod = billingPeriod; - emit planChanged(); -} - -QString PurchasingPlan::price() const -{ - return mPrice; -} - -void PurchasingPlan::setPrice( const QString &billingPrice ) -{ - mPrice = billingPrice; - emit planChanged(); -} - -bool PurchasingPlan::isIndividualPlan() const -{ - return !mIsProfessional; -} - -bool PurchasingPlan::isProfessionalPlan() const -{ - return mIsProfessional; -} - -QString PurchasingPlan::storage() const -{ - return mStorage; -} - -void PurchasingPlan::setStorage( const QString &storage ) -{ - mStorage = storage; - emit planChanged(); -} - -Purchasing *PurchasingPlan::purchasing() const -{ - return mPurchasing; -} - -void PurchasingPlan::setPurchasing( Purchasing *purchasing ) -{ - mPurchasing = purchasing; -} - -void PurchasingPlan::setFromJson( QJsonObject docObj ) -{ - mAlias = docObj.value( QStringLiteral( "alias" ) ).toString(); - mId = docObj.value( QStringLiteral( "billing_product_id" ) ).toString(); - const QString billingPeriod = docObj.value( QStringLiteral( "billing_period" ) ).toString(); - bool isMonthly = billingPeriod.contains( "month", Qt::CaseInsensitive ); - if ( isMonthly ) - { - mPeriod = tr( "Monthly subscription" ); - } - else - { - mPeriod = tr( "Annual subscription" ); - } - const QString billingPrice = docObj.value( QStringLiteral( "billing_price" ) ).toString(); - if ( isMonthly ) - { - mPrice = billingPrice + "/" + tr( "month" ); - } - else - { - mPrice = billingPrice + "/" + tr( "year" ); - } - - double bytes = docObj.value( QStringLiteral( "storage" ) ).toDouble(); - mStorage = InputUtils::bytesToHumanSize( bytes ); - - mIsProfessional = ( bytes > 1024.0 * 1024.0 * 1024.0 * 5 ); // storage > 5GB - - emit planChanged(); -} - -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ - - -PurchasingTransaction::PurchasingTransaction( PurchasingTransaction::TransactionType type, QSharedPointer plan ) - : mPlan( plan ) - , mType( type ) -{ -} - -void PurchasingTransaction::verificationFinished() -{ - Q_ASSERT( mPlan ); - Q_ASSERT( mPlan->purchasing() ); - MerginApi *api = mPlan->purchasing()->merginApi(); - - QNetworkReply *r = qobject_cast( sender() ); - Q_ASSERT( r ); - - if ( r->error() == QNetworkReply::NoError ) - { - CoreUtils::log( "purchase successful", QStringLiteral( "Payment success" ) ); - mPlan->purchasing()->onTransactionVerificationSucceeded( this ); - } - else - { - QString serverMsg = api->extractServerErrorMsg( r->readAll() ); - QString message = QStringLiteral( "Network API error: %1(): %2. %3" ).arg( QStringLiteral( "purchase" ), r->errorString(), serverMsg ); - emit api->networkErrorOccurred( serverMsg, QStringLiteral( "Mergin API error: purchase" ) ); - CoreUtils::log( "purchase", QStringLiteral( "FAILED - %1" ).arg( message ) ); - mPlan->purchasing()->onTransactionVerificationFailed( this ); - } - r->deleteLater(); -} - -PurchasingTransaction::TransactionType PurchasingTransaction::type() const -{ - return mType; -} - -PurchasingPlan *PurchasingTransaction::plan() const -{ - return mPlan.get(); -} - -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ -/* ********************************************************************************************************/ - -Purchasing::Purchasing( MerginApi *merginApi, QObject *parent ) - : QObject( parent ) - , mMerginApi( merginApi ) -{ - setDefaultUrls(); - createBackend(); - - connect( mMerginApi, &MerginApi::apiRootChanged, this, &Purchasing::onMerginServerChanged ); - connect( mMerginApi, &MerginApi::apiSupportsSubscriptionsChanged, this, &Purchasing::onMerginServerStatusChanged ); - connect( mMerginApi, &MerginApi::apiVersionStatusChanged, this, &Purchasing::onMerginServerStatusChanged ); - connect( mMerginApi->subscriptionInfo(), &MerginSubscriptionInfo::planProviderChanged, this, &Purchasing::evaluateHasInAppPurchases ); - connect( mMerginApi->subscriptionInfo(), &MerginSubscriptionInfo::planProductIdChanged, this, &Purchasing::onMerginPlanProductIdChanged ); - - connect( this, &Purchasing::hasInAppPurchasesChanged, this, &Purchasing::onHasInAppPurchasesChanged ); -} - -void Purchasing::createBackend() -{ - mBackend.reset(); -#if defined( PURCHASING ) -#if defined (APPLE_PURCHASING) - mBackend.reset( new IosPurchasingBackend ); -#elif defined (INPUT_TEST) - mBackend.reset( new TestingPurchasingBackend( mMerginApi ) ); -#endif -#endif - - if ( mBackend ) - { - mBackend->setPurchasing( this ); - mBackend->init(); - - connect( mBackend.get(), &PurchasingBackend::planRegistrationSucceeded, this, &Purchasing::onPlanRegistrationSucceeded ); - connect( mBackend.get(), &PurchasingBackend::planRegistrationFailed, this, &Purchasing::onPlanRegistrationFailed ); - - connect( mBackend.get(), &PurchasingBackend::transactionCreationSucceeded, this, &Purchasing::onTransactionCreationSucceeded ); - connect( mBackend.get(), &PurchasingBackend::transactionCreationFailed, this, &Purchasing::onTransactionCreationFailed ); - } -} - -void Purchasing::evaluateHasInAppPurchases() -{ - bool hasInApp = - mMerginApi->apiSupportsSubscriptions() && - mMerginApi->apiVersionStatus() == MerginApiStatus::OK && - bool( mBackend ) && - mBackend->userCanMakePayments(); - - bool hasCompatiblePlanType = true; - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - hasCompatiblePlanType = - bool( mBackend ) && - mBackend->provider() == mMerginApi->subscriptionInfo()->planProvider(); - } - setHasInAppPurchases( hasInApp && hasCompatiblePlanType ); -} - -void Purchasing::onHasInAppPurchasesChanged() -{ - bool hasManageCapability = false; - QString subscriptionManageUrl = mMerginApi->apiRoot() + "subscription"; - QString subscriptionBillingUrl = mMerginApi->apiRoot() + "billing"; - - if ( hasInAppPurchases() ) - { - hasManageCapability = mBackend->hasManageSubscriptionCapability(); - subscriptionManageUrl = mBackend->subscriptionManageUrl(); - subscriptionBillingUrl = mBackend->subscriptionBillingUrl(); - } - - setHasManageSubscriptionCapability( hasManageCapability ); - setSubscriptionManageUrl( subscriptionManageUrl ); - setSubscriptionBillingUrl( subscriptionBillingUrl ); -} - -bool Purchasing::hasInAppPurchases() const -{ - return mHasInAppPurchases; -} - -bool Purchasing::hasManageSubscriptionCapability() const -{ - return mHasManageSubscriptionCapability; -} - -QString Purchasing::subscriptionManageUrl() -{ - return mSubscriptionManageUrl; -} - -QString Purchasing::subscriptionBillingUrl() -{ - return mSubscriptionBillingUrl; -} - -void Purchasing::onMerginServerChanged() -{ - qDebug() << "Mergin Server Url changed reseting purchasing"; - clean(); -} - -void Purchasing::purchase( const QString &planId ) -{ - if ( transactionPending() ) - { - qDebug() << "unable to initiate purchase, there is a transaction pending"; - return; - } - - if ( hasInAppPurchases() ) - { - if ( mPlansWithPendingRegistration.contains( planId ) ) - { - qDebug() << "unable to initiate purchase, plan " << planId << " is not yet registered"; - } - - if ( !mRegisteredPlans.contains( planId ) ) - { - qDebug() << "unable to initiate purchase, unable to find plan " << planId; - } - - setTransactionCreationRequested( true ); - return mBackend->createTransaction( mRegisteredPlans.value( planId ) ); - } - else - { - qDebug() << "unable to initiate purchase, purchase api not ready"; - } -} - -void Purchasing::restore() -{ - if ( transactionPending() ) - { - qDebug() << "unable to initiate restore, there is a transaction pending"; - return; - } - - if ( hasInAppPurchases() ) - { - setTransactionCreationRequested( true ); - return mBackend->restore( ); - } - else - { - qDebug() << "unable to initiate restore, purchase api not ready"; - } -} - -void Purchasing::onMerginPlanProductIdChanged() -{ - if ( !mBackend ) - return; - - QString planId = mMerginApi->subscriptionInfo()->planProductId(); - if ( planId.isEmpty() ) - return; - - if ( mBackend->provider() != mMerginApi->subscriptionInfo()->planProvider() ) - return; - - QString price = mBackend->getLocalizedPrice( mMerginApi->subscriptionInfo()->planProductId() ); - mMerginApi->subscriptionInfo()->setLocalizedPrice( price ); -} - -void Purchasing::onMerginServerStatusChanged() -{ - qDebug() << "Mergin Server status changed, fetching purchasing plan"; - if ( mBackend && mPlansWithPendingRegistration.empty() && mRegisteredPlans.empty() ) - { - fetchPurchasingPlans(); - } - evaluateHasInAppPurchases(); -} - -void Purchasing::fetchPurchasingPlans( ) -{ - if ( !mMerginApi->apiSupportsSubscriptions() ) return; - if ( mMerginApi->apiVersionStatus() != MerginApiStatus::OK ) return; - - QUrl url( mMerginApi->apiRoot() + QStringLiteral( "/v1/plan" ) ); - QUrlQuery query; - query.addQueryItem( "billing_service", MerginSubscriptionType::toString( mBackend->provider() ) ); - url.setQuery( query ); - QNetworkRequest request = mMerginApi->getDefaultRequest( false ); - request.setUrl( url ); - QNetworkReply *reply = mMerginApi->mManager.get( request ); - connect( reply, &QNetworkReply::finished, this, &Purchasing::onFetchPurchasingPlansFinished ); - CoreUtils::log( "request plan", QStringLiteral( "Requesting purchasing plans for provider %1" ).arg( MerginSubscriptionType::toString( mBackend->provider() ) ) ); -} - -void Purchasing::onFetchPurchasingPlansFinished() -{ - QNetworkReply *r = qobject_cast( sender() ); - Q_ASSERT( r ); - QString serverMsg; - if ( r->error() == QNetworkReply::NoError ) - { - CoreUtils::log( "fetch plans", QStringLiteral( "Success" ) ); - QByteArray data = r->readAll(); - const QJsonDocument doc = QJsonDocument::fromJson( data ); - if ( doc.isArray() ) - { - const QJsonArray vArray = doc.array(); - for ( auto it = vArray.constBegin(); it != vArray.constEnd(); ++it ) - { - const QJsonObject obj = it->toObject(); - QSharedPointer plan = mBackend->createPlan(); - plan->setFromJson( obj ); - plan->setPurchasing( this ); - if ( mPlansWithPendingRegistration.contains( plan->id() ) ) - { - qDebug() << "Plan " << plan->id() << " registration already pending"; - } - else if ( mRegisteredPlans.contains( plan->id() ) ) - { - qDebug() << "Plan " << plan->id() << " already registered"; - } - else - { - qDebug() << "Plan " << plan->id() << " requested registration"; - mPlansWithPendingRegistration.insert( plan->id(), plan ); - mBackend->registerPlan( plan ); - } - } - } - } - else - { - serverMsg = mMerginApi->extractServerErrorMsg( r->readAll() ); - CoreUtils::log( "fetch plans", QStringLiteral( "FAILED - %1. %2" ).arg( r->errorString(), serverMsg ) ); - } - r->deleteLater(); -} - -void Purchasing::clean() -{ - createBackend(); - mRegisteredPlans.clear(); - mPlansWithPendingRegistration.clear(); - mTransactionsWithPendingVerification.clear(); - mTransactionCreationRequested = false; - mIndividualPlanId.clear(); - mProfessionalPlanId.clear(); - setDefaultUrls(); - setHasInAppPurchases( false ); - - emit individualPlanChanged(); - emit professionalPlanChanged(); - emit transactionPendingChanged(); -} - -void Purchasing::onPlanRegistrationFailed( const QString &id ) -{ - qDebug() << "Failed to register plan " + id; - if ( mPlansWithPendingRegistration.contains( id ) ) - mPlansWithPendingRegistration.remove( id ); - - if ( mPlansWithPendingRegistration.empty() && mRegisteredPlans.empty() ) - { - CoreUtils::log( "Plan Registration", QStringLiteral( "Failed to register any plans" ) ); - } -} - -void Purchasing::onPlanRegistrationSucceeded( const QString &id ) -{ - if ( mPlansWithPendingRegistration.contains( id ) ) - { - QSharedPointer plan = mPlansWithPendingRegistration.take( id ); - if ( mRegisteredPlans.contains( plan->id() ) ) - { - qDebug() << "Plan " << id << " is already in registered plans."; - } - else - { - qDebug() << "Plan " + id + " registered"; - mRegisteredPlans.insert( id, plan ); - if ( plan->isProfessionalPlan() ) - { - qDebug() << "Plan " + id + " is professional plan"; - setProfessionalPlanId( plan->id() ); - } - if ( plan->isIndividualPlan() ) - { - qDebug() << "Plan " + id + " is individual plan"; - setIndividualPlanId( plan->id() ); - } - } - } - else - { - qDebug() << "Plan " + id + " registered OK, but failed to find in pending registrations"; - } - emit hasInAppPurchasesChanged(); -} - -void Purchasing::onTransactionCreationSucceeded( QSharedPointer transaction ) -{ - setTransactionCreationRequested( false ); - - if ( !mMerginApi->validateAuth() || mMerginApi->apiVersionStatus() != MerginApiStatus::OK ) - { - return; - } - - mTransactionsWithPendingVerification.push_back( transaction ); - - QNetworkRequest request = mMerginApi->getDefaultRequest(); - QUrl url( mMerginApi->apiRoot() + QStringLiteral( "v1/subscription/process-transaction" ) ); - request.setUrl( url ); - request.setHeader( QNetworkRequest::ContentTypeHeader, QVariant( "application/json" ) ); - QJsonDocument jsonDoc; - QJsonObject jsonObject; - jsonObject.insert( QStringLiteral( "type" ), MerginSubscriptionType::toString( transaction->provider() ) ); - jsonObject.insert( QStringLiteral( "receipt-data" ), transaction->receipt() ); - jsonObject.insert( QStringLiteral( "api_key" ), mMerginApi->getApiKey( mMerginApi->apiRoot() ) ); - - if ( mMerginApi->serverType() == MerginServerType::SAAS ) - { - jsonObject.insert( QStringLiteral( "workspace" ), mMerginApi->userInfo()->activeWorkspaceId() ); - } - - jsonDoc.setObject( jsonObject ); - QByteArray json = jsonDoc.toJson( QJsonDocument::Compact ); - QNetworkReply *reply = mMerginApi->mManager.post( request, json ); - connect( reply, &QNetworkReply::finished, transaction.get(), &PurchasingTransaction::verificationFinished ); - CoreUtils::log( "process transaction", QStringLiteral( "Requesting processing of in-app transaction: " ) + url.toString() ); -} - -void Purchasing::onTransactionCreationFailed() -{ - notify( tr( "Failed to process payment details.%1Subscription is not purchased." ).arg( "
" ) ); - setTransactionCreationRequested( false ); -} - -void Purchasing::onTransactionVerificationSucceeded( PurchasingTransaction *transaction ) -{ - Q_ASSERT( transaction ); - if ( transaction->type() == PurchasingTransaction::RestoreTransaction ) - notify( tr( "Successfully restored your subscription" ) ); - else - notify( tr( "Successfully purchased subscription" ) ); - - removePendingTransaction( transaction ); - mMerginApi->getServiceInfo(); -} - -void Purchasing::onTransactionVerificationFailed( PurchasingTransaction *transaction ) -{ - Q_ASSERT( transaction ); - if ( transaction->type() == PurchasingTransaction::RestoreTransaction ) - notify( tr( "Unable to restore your subscription" ) ); - else - notify( tr( "Failed to purchase subscription" ) ); - - removePendingTransaction( transaction ); -} - -void Purchasing::notify( const QString &msg ) -{ - mMerginApi->notify( msg ); -} - - -MerginApi *Purchasing::merginApi() const -{ - return mMerginApi; -} - -int Purchasing::registeredPlansCount() const -{ - return mRegisteredPlans.count(); -} - -void Purchasing::removePendingTransaction( PurchasingTransaction *transaction ) -{ - transaction->finalizeTransaction(); - - int index = -1; - for ( int i = 0; i < mTransactionsWithPendingVerification.count(); ++i ) - { - if ( mTransactionsWithPendingVerification.at( i ).get() == transaction ) - { - index = i; - break; - } - } - if ( index >= 0 ) - { - mTransactionsWithPendingVerification.removeAt( index ); - } - - emit transactionPendingChanged(); -} - - -bool Purchasing::transactionPending() const -{ - return !mTransactionsWithPendingVerification.empty() || mTransactionCreationRequested; -} - -PurchasingPlan *Purchasing::individualPlan() const -{ - static PurchasingPlan sEmptyPlan; - - QSharedPointer plan = registeredPlan( mIndividualPlanId ); - if ( plan ) - { - return plan.get(); - } - else - { - return &sEmptyPlan; - } -} - -PurchasingPlan *Purchasing::professionalPlan() const -{ - static PurchasingPlan sEmptyPlan; - - QSharedPointer plan = registeredPlan( mProfessionalPlanId ); - if ( plan ) - { - return plan.get(); - } - else - { - return &sEmptyPlan; - } -} - -QSharedPointer Purchasing::registeredPlan( const QString &id ) const -{ - if ( id.isEmpty() ) - return nullptr; - - if ( mRegisteredPlans.contains( id ) ) - return mRegisteredPlans.value( id ); - - return nullptr; -} - -QSharedPointer Purchasing::pendingPlan( const QString &id ) const -{ - if ( id.isEmpty() ) - return nullptr; - - if ( mPlansWithPendingRegistration.contains( id ) ) - return mPlansWithPendingRegistration.value( id ); - - return nullptr; -} - -void Purchasing::setSubscriptionBillingUrl( const QString &subscriptionBillingUrl ) -{ - if ( mSubscriptionBillingUrl != subscriptionBillingUrl ) - { - mSubscriptionBillingUrl = subscriptionBillingUrl; - emit subscriptionBillingUrlChanged(); - } -} - -void Purchasing::setDefaultUrls() -{ - mSubscriptionManageUrl = mMerginApi->apiRoot() + "subscription"; - emit subscriptionManageUrlChanged(); - mSubscriptionBillingUrl = mMerginApi->apiRoot() + "billing"; - emit subscriptionBillingUrlChanged(); -} - -void Purchasing::setSubscriptionManageUrl( const QString &subscriptionManageUrl ) -{ - if ( mSubscriptionManageUrl != subscriptionManageUrl ) - { - mSubscriptionManageUrl = subscriptionManageUrl; - emit subscriptionManageUrlChanged(); - } -} - -void Purchasing::setHasManageSubscriptionCapability( bool hasManageSubscriptionCapability ) -{ - if ( mHasManageSubscriptionCapability != hasManageSubscriptionCapability ) - { - mHasManageSubscriptionCapability = hasManageSubscriptionCapability; - emit hasManageSubscriptionCapabilityChanged(); - } -} - -void Purchasing::setHasInAppPurchases( bool hasInAppPurchases ) -{ - if ( mHasInAppPurchases != hasInAppPurchases ) - { - mHasInAppPurchases = hasInAppPurchases; - emit hasInAppPurchasesChanged(); - } -} - -void Purchasing::setTransactionCreationRequested( bool transactionCreationRequested ) -{ - if ( mTransactionCreationRequested != transactionCreationRequested ) - { - mTransactionCreationRequested = transactionCreationRequested; - emit transactionPendingChanged(); - } -} - -void Purchasing::setIndividualPlanId( const QString &planId ) -{ - if ( mIndividualPlanId != planId ) - { - mIndividualPlanId = planId; - emit individualPlanChanged(); - } -} - -void Purchasing::setProfessionalPlanId( const QString &planId ) -{ - if ( mProfessionalPlanId != planId ) - { - mProfessionalPlanId = planId; - emit professionalPlanChanged(); - } -} diff --git a/app/purchasing.h b/app/purchasing.h deleted file mode 100644 index 7ce0ae6dc..000000000 --- a/app/purchasing.h +++ /dev/null @@ -1,338 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef PURCHASING_H -#define PURCHASING_H - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "inputconfig.h" -#include "merginsubscriptiontype.h" - -class MerginApi; -class Purchasing; - -/** - * Purchasing Plan is a plan for subscription - * - * Each user can have assigned one subscription plan on Mergin. - * Plan defines the maximum storage, billing price, period and so. - */ -class PurchasingPlan: public QObject -{ - Q_OBJECT - Q_PROPERTY( QString alias READ alias NOTIFY planChanged ) - Q_PROPERTY( QString id READ id NOTIFY planChanged ) - Q_PROPERTY( QString period READ period NOTIFY planChanged ) - Q_PROPERTY( QString price READ price NOTIFY planChanged ) - Q_PROPERTY( QString storage READ storage NOTIFY planChanged ) - - public: - void clear(); - - QString alias() const; - void setAlias( const QString &alias ); - - QString id() const; - void setId( const QString &id ); - - QString period() const; - void setPeriod( const QString &period ); - - QString price() const; - void setPrice( const QString &price ); - - bool isIndividualPlan() const; - bool isProfessionalPlan() const; - - void setFromJson( QJsonObject docObj ); - - QString storage() const; - - void setStorage( const QString &storage ); - - Purchasing *purchasing() const; - void setPurchasing( Purchasing *purchasing ); - - signals: - void planChanged(); - - private: - QString mAlias; - QString mId; - QString mPeriod; - QString mPrice; - QString mStorage; - - Purchasing *mPurchasing = nullptr; - bool mIsProfessional = false; -}; - -/** - * The Purchasing Transaction represent one payment transaction - * - * The puchasing transaction is created when user pays the plan - * and is verified on the Mergin server. - */ -class PurchasingTransaction : public QObject -{ - Q_OBJECT - public: - enum TransactionType - { - RestoreTransaction, - PuchaseTransaction - }; - Q_ENUM( TransactionType ) - - PurchasingTransaction( TransactionType type, QSharedPointer plan ); - - void virtual finalizeTransaction() = 0; - - //! Transaction receipt, e.g. apple base64 receipt - virtual QString receipt() const = 0; - - //! Transaction provider, either apple, stripe or test - virtual MerginSubscriptionType::SubscriptionType provider() const = 0; - - PurchasingPlan *plan() const; - TransactionType type() const; - - public slots: - void verificationFinished(); - - private: - QSharedPointer mPlan; - const TransactionType mType; -}; - - -class PurchasingBackend: public QObject -{ - Q_OBJECT - public: - /** - * Initializes the service - * Called once on very startup of the QApplication - */ - virtual void init() = 0; - - /** - * Creates an instance of the purchasing plan from JSON. - * Registration is done later in registerPlan - */ - virtual QSharedPointer createPlan( ) = 0; - - /** - * Register the products in the native SDK. - */ - virtual void registerPlan( QSharedPointer plan ) = 0; - - /** - * Buys the selected product. - */ - virtual void createTransaction( QSharedPointer plan ) = 0; - - /** - * Restore purchases with recipes stored on this device - */ - virtual void restore() = 0; - - /** - * Returns whether the backend can manage (upgrade/downgrade/...) - * the subscriptions or that has to be done on external - * website (mergin/apple/...) - */ - virtual bool hasManageSubscriptionCapability() const = 0; - - /** - * URL to show user when he wants so upgrade/downgrade - * existing (paid) subscriptions - * - * non-empty URL only when hasManageSubscriptionCapability() == false - */ - virtual QString subscriptionManageUrl() = 0; - - /** - * URL to show user when he wants so - * change billing details (e.g. credit card number) - * - * non-empty URL only when hasManageSubscriptionCapability() == false - */ - virtual QString subscriptionBillingUrl() = 0; - - /** - * Name of the billing service for Mergin API - * (e.g. stripe, apple, google, ...) - */ - virtual MerginSubscriptionType::SubscriptionType provider() const = 0; - - /** - * Whether user can make purchases on the device - */ - virtual bool userCanMakePayments() const = 0; - - void setPurchasing( Purchasing *purchasing ) {mPurchasing = purchasing;} - Purchasing *purchasing() const {return mPurchasing;} - - //! Returns localised prize of plan, empty string if cannot be fetched - virtual QString getLocalizedPrice( const QString &planId ) const = 0; - - signals: - void transactionCreationFailed( ); - void transactionCreationSucceeded( QSharedPointer transaction ); - - void planRegistrationFailed( const QString &id ); - void planRegistrationSucceeded( const QString &id ); - - private: - Purchasing *mPurchasing = nullptr; -}; - -/** - * The Purchasing class, responsible for in-app purchases - * - * The purchasing can be disabled or enabled based on these factors - * 1. Mergin Server can be deployed with the flag that subscriptions are disabled - * 2. Device (e.g. iPhone) can be in mode where purchases are not allowed - * 3. The plans in the 3rd party store (e.g. AppStore) does not match the plans in Mergin Server - * 4. The InputApp does not have backed to manage user plan on Mergin Server (e.g. stripe plans) - * - * When purchasing is enabled the workflow is as follows - * 1. Fetches the plan details from Mergin to check which plans are available for user. One of the plan - * is reccomended and only this one is shown in the GUI - * 2. The plans are then registered in the backend (e.g. with Apple's StoreKit) - * 3. If the plans are correctly registered, the purchasing API is now in state to do payments/transactions - * Therefore user has 2 options: - * 3.A) restore subscriptions (in case for example he paid the subscription but - * network error caused that it was not correctly processed on Mergin) - * 3.B) buy new subscription (the reccomeneded one) - * If there are some pending transactions on the device, these can create transactions even without the user - * action. - * 4. When user pays the plan, the backend creates the transaction. The transaction receipt is extracted - * and send to the Mergin. Mergin verifies the receipt and based on the data changes the subscription on - * the server. - * 5. User is notified about successfull payment and the storage is increased. - * - * For managing existing subscriptions, user needs to use the official URLs (e.g. Apple iTunes subscription manager) - * - * For testing purposes and the development purposes on the Linux, we have also created a fake backend, - * that can be used to simulate various subscription statuses/workflows on the Mergin. The testing backend can - * only be used on development Mergin servers. - * - * For testing the in-app purchases in the 3rd party stores, check the documentation of the PurchasingBackend implementations. - */ -class Purchasing : public QObject -{ - Q_OBJECT - - Q_PROPERTY( PurchasingPlan *individualPlan READ individualPlan NOTIFY individualPlanChanged ) - Q_PROPERTY( PurchasingPlan *professionalPlan READ professionalPlan NOTIFY professionalPlanChanged ) - Q_PROPERTY( bool transactionPending READ transactionPending NOTIFY transactionPendingChanged ) - Q_PROPERTY( bool hasInAppPurchases READ hasInAppPurchases NOTIFY hasInAppPurchasesChanged ) - Q_PROPERTY( bool hasManageSubscriptionCapability READ hasManageSubscriptionCapability NOTIFY hasManageSubscriptionCapabilityChanged ) - Q_PROPERTY( QString subscriptionManageUrl READ subscriptionManageUrl NOTIFY subscriptionManageUrlChanged ) - Q_PROPERTY( QString subscriptionBillingUrl READ subscriptionBillingUrl NOTIFY subscriptionBillingUrlChanged ) - - public: - explicit Purchasing( MerginApi *merginApi, QObject *parent = nullptr ); - - Q_INVOKABLE void purchase( const QString &planId ); - Q_INVOKABLE void restore(); - - bool hasManageSubscriptionCapability() const; - bool transactionPending() const; - bool hasInAppPurchases() const; - QString subscriptionManageUrl(); - QString subscriptionBillingUrl(); - PurchasingPlan *individualPlan() const; - PurchasingPlan *professionalPlan() const; - - // Public function required only internally within the purchasing classes - public: - PurchasingBackend *backend() {return mBackend.get();} - QSharedPointer registeredPlan( const QString &id ) const; - QSharedPointer pendingPlan( const QString &id ) const; - MerginApi *merginApi() const; - int registeredPlansCount() const; - - signals: - void transactionPendingChanged(); - void individualPlanChanged(); - void professionalPlanChanged(); - void hasInAppPurchasesChanged(); - void hasManageSubscriptionCapabilityChanged(); - void subscriptionManageUrlChanged(); - void subscriptionBillingUrlChanged(); - - private slots: - void onFetchPurchasingPlansFinished(); - - void onPlanRegistrationFailed( const QString &id ); - void onPlanRegistrationSucceeded( const QString &id ); - - void onTransactionCreationSucceeded( QSharedPointer transaction ); - void onTransactionCreationFailed( ); - - void onTransactionVerificationSucceeded( PurchasingTransaction *transaction ); - void onTransactionVerificationFailed( PurchasingTransaction *transaction ); - - void onMerginServerStatusChanged(); - void onMerginServerChanged(); - void onMerginPlanProductIdChanged(); - void evaluateHasInAppPurchases(); - void onHasInAppPurchasesChanged(); - - private: - void createBackend(); - - void clean(); - void fetchPurchasingPlans(); - void setTransactionCreationRequested( bool transactionCreationRequested ); - void setIndividualPlanId( const QString &PlanId ); - void setProfessionalPlanId( const QString &PlanId ); - void removePendingTransaction( PurchasingTransaction *transaction ); - - void setHasInAppPurchases( bool hasInAppPurchases ); - void setHasManageSubscriptionCapability( bool hasManageSubscriptionCapability ); - void setSubscriptionManageUrl( const QString &subscriptionManageUrl ); - void setSubscriptionBillingUrl( const QString &subscriptionBillingUrl ); - - void setDefaultUrls(); - - void notify( const QString &msg ); - - QMap > mPlansWithPendingRegistration; - QMap > mRegisteredPlans; - QString mIndividualPlanId; - QString mProfessionalPlanId; - - bool mTransactionCreationRequested = false; - QList> mTransactionsWithPendingVerification; - - std::unique_ptr mBackend = nullptr; - MerginApi *mMerginApi = nullptr; - - bool mHasInAppPurchases = false; - bool mHasManageSubscriptionCapability; - QString mSubscriptionManageUrl; - QString mSubscriptionBillingUrl; - - friend class PurchasingTransaction; -}; - -#endif // PURCHASING_H diff --git a/app/qml/AccountPage.qml b/app/qml/AccountPage.qml index dc6813757..23c8b98e8 100644 --- a/app/qml/AccountPage.qml +++ b/app/qml/AccountPage.qml @@ -20,7 +20,6 @@ Page { signal back signal managePlansClicked signal signOutClicked - signal restorePurchasesClicked signal accountDeleted property color bgColor: "white" property real fieldHeight: InputStyle.rowHeight @@ -149,7 +148,7 @@ Page { Qt.openUrlExternally(link) } text: qsTr("Please update your %1billing details%2 as soon as possible") - .arg("") + .arg("") .arg("") iconColor: InputStyle.highlightColor } @@ -206,13 +205,12 @@ Page { Button { id: subscribeButton - enabled: !__purchasing.transactionPending width: root.width - 2 * InputStyle.rowHeightHeader anchors.horizontalCenter: parent.horizontalCenter visible: __merginApi.apiSupportsSubscriptions height: InputStyle.rowHeightHeader - text: __purchasing.transactionPending ? qsTr("Working...") : root.ownsActiveSubscription ? qsTr("Manage Subscription") : qsTr("Subscription plans") + text: root.ownsActiveSubscription ? qsTr("Manage Subscription") : qsTr("Subscription plans") font.pixelSize: InputStyle.fontPixelSizeBig background: Rectangle { @@ -231,33 +229,6 @@ Page { elide: Text.ElideRight } } - - Item { - id: spacer - visible: textRestore.visible - height: InputStyle.rowHeightHeader - width:parent.width - } - - Text { - id: textRestore - visible: __iosUtils.isIos && __merginApi.apiSupportsSubscriptions && __purchasing.hasInAppPurchases && !__purchasing.transactionPending - textFormat: Text.RichText - onLinkActivated: restorePurchasesClicked() - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - text: "" + qsTr( - "You can also %1restore%2 your purchases") - .arg("") - .arg("") - font.pixelSize: InputStyle.fontPixelSizeNormal - color: InputStyle.fontColor - width: root.width - leftPadding: InputStyle.rowHeightHeader - rightPadding: InputStyle.rowHeightHeader - } } // ///////////////// diff --git a/app/qml/ProjectPanel.qml b/app/qml/ProjectPanel.qml index 0b4caa47e..87146f58f 100644 --- a/app/qml/ProjectPanel.qml +++ b/app/qml/ProjectPanel.qml @@ -915,9 +915,6 @@ Item { stackView.pop( null ) root.resetView() } - onRestorePurchasesClicked: { - __purchasing.restore() - } onAccountDeleted: { stackView.popOnePageOrClose() root.resetView() diff --git a/app/qml/WorkspaceAccountPage.qml b/app/qml/WorkspaceAccountPage.qml index 999b9a010..f6afaae45 100644 --- a/app/qml/WorkspaceAccountPage.qml +++ b/app/qml/WorkspaceAccountPage.qml @@ -177,7 +177,7 @@ Page { titleText: qsTr( "Subscription status" ) text: qsTr("Please update your %1billing details%2 as soon as possible") - .arg("") + .arg("") .arg("") textComponent.onLinkActivated: function ( link ) { @@ -215,33 +215,6 @@ Page { onClicked: root.managePlansClicked() } - Text { - Layout.fillWidth: true - Layout.preferredHeight: InputStyle.rowHeightHeader - - leftPadding: InputStyle.formSpacing - - color: InputStyle.fontColor - linkColor: InputStyle.highlightColor - - wrapMode: Text.WordWrap - textFormat: Text.StyledText - - font.bold: true - font.pixelSize: InputStyle.fontPixelSizeNormal - - verticalAlignment: Qt.AlignVCenter - horizontalAlignment: Qt.AlignLeft - - visible: __iosUtils.isIos && root.apiSupportsSubscriptions && root.canAccessSubscription - - text: qsTr("You can also %1restore%2 your purchase.") - .arg("") - .arg("") - - onLinkActivated: __purchasing.restore() - } - // user profile Rectangle { diff --git a/app/test/inputtests.cpp b/app/test/inputtests.cpp index 16bad90aa..d425802c5 100644 --- a/app/test/inputtests.cpp +++ b/app/test/inputtests.cpp @@ -32,10 +32,6 @@ #include "test/testactiveproject.h" #include "test/testprojectchecksumcache.h" -#if not defined APPLE_PURCHASING -#include "test/testpurchasing.h" -#endif - InputTests::InputTests() = default; InputTests::~InputTests() = default; @@ -63,10 +59,9 @@ bool InputTests::testingRequested() const return !mTestRequested.isEmpty(); } -void InputTests::init( MerginApi *api, Purchasing *purchasing, InputUtils *utils, VariablesManager *varManager, PositionKit *kit, AppSettings *settings ) +void InputTests::init( MerginApi *api, InputUtils *utils, VariablesManager *varManager, PositionKit *kit, AppSettings *settings ) { mApi = api; - mPurchasing = purchasing; mInputUtils = utils; mVariablesManager = varManager; mPositionKit = kit; @@ -94,7 +89,7 @@ int InputTests::runTest() const { int nFailed = 0; - if ( !mApi || !mPurchasing || !mInputUtils ) + if ( !mApi || !mInputUtils ) { nFailed = 1000; qDebug() << "input tests not initialized"; @@ -187,19 +182,11 @@ int InputTests::runTest() const TestProjectChecksumCache projectChecksumTest; nFailed = QTest::qExec( &projectChecksumTest, mTestArgs ); } -#if not defined APPLE_PURCHASING - else if ( mTestRequested == "--testPurchasing" ) - { - TestPurchasing purchasingTest( mApi, mPurchasing ); - nFailed = QTest::qExec( &purchasingTest, mTestArgs ); - } else if ( mTestRequested == "--testMerginApi" ) { - TestMerginApi merginApiTest( mApi, mPurchasing ); + TestMerginApi merginApiTest( mApi ); nFailed = QTest::qExec( &merginApiTest, mTestArgs ); } - -#endif else { qDebug() << "invalid test requested" << mTestRequested; diff --git a/app/test/inputtests.h b/app/test/inputtests.h index 3d994bafe..3fe36fd98 100644 --- a/app/test/inputtests.h +++ b/app/test/inputtests.h @@ -16,7 +16,6 @@ #include class MerginApi; -class Purchasing; class InputUtils; class VariablesManager; class PositionKit; @@ -32,7 +31,7 @@ class InputTests bool testingRequested() const; - void init( MerginApi *api, Purchasing *purchasing, InputUtils *utils, VariablesManager *varManager, PositionKit *positionKit, AppSettings *settings ); + void init( MerginApi *api, InputUtils *utils, VariablesManager *varManager, PositionKit *positionKit, AppSettings *settings ); void initTestDeclarative(); QString initTestingDir(); int runTest() const; @@ -41,7 +40,6 @@ class InputTests QString mTestRequested; QStringList mTestArgs; MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; InputUtils *mInputUtils = nullptr; VariablesManager *mVariablesManager = nullptr; PositionKit *mPositionKit = nullptr; diff --git a/app/test/testingpurchasingbackend.cpp b/app/test/testingpurchasingbackend.cpp deleted file mode 100644 index cd03f723a..000000000 --- a/app/test/testingpurchasingbackend.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#include "testingpurchasingbackend.h" - -#include "merginapi.h" -#include "merginuserinfo.h" -#include "merginsubscriptioninfo.h" -#include "inpututils.h" - -#if defined (HAVE_WIDGETS) -#include -#include -#endif - -TestingPurchasingTransaction::TestingPurchasingTransaction( QString receipt, PurchasingTransaction::TransactionType type, QSharedPointer plan ) - : PurchasingTransaction( type, plan ), mReceipt( receipt ) {} - -QString TestingPurchasingTransaction::receipt() const {return mReceipt;} - -TestingPurchasingBackend::TestingPurchasingBackend( MerginApi *api ) - : PurchasingBackend() - , mMerginApi( api ) -{ -} - -void TestingPurchasingBackend::setNextPurchaseResult( const TestingPurchasingBackend::NextPurchaseResult expected ) -{ - mNextResult = expected; -} - -void TestingPurchasingBackend::registerPlan( QSharedPointer plan ) -{ - if ( plan->isIndividualPlan() ) - mIndividualPlan = plan; - - emit planRegistrationSucceeded( plan->id() ); -} - - -void TestingPurchasingBackend::createTransaction( QSharedPointer plan ) -{ - if ( mNextResult == Interactive ) - { -#if defined (HAVE_WIDGETS) - QStringList items; - if ( plan->isIndividualPlan() ) - { - items << "Buy individual plan | tier01"; - } - else - { - items << "Buy professional plan | tier12"; - } - - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - items << "Immediately refund the subscription (got refund) | cancel" - << "Set grace period | grace" - << "Set unsubscribed | unsubscribe"; - } - items << "Cancel Payment | cancelPayment" - << "Send invalid receipt | invalidreceipt"; - - bool ok; - QString item = QInputDialog::getItem( nullptr, "QInputDialog::getItem()", - "PURCHASING TEST", items, 0, false, &ok ); - if ( ok && !item.isEmpty() ) - { - const QStringList parts = item.split( " | " ); - Q_ASSERT( parts.size() == 2 ); - const QString key = parts[1]; - if ( key.contains( "cancelPayment" ) ) - emit transactionCreationFailed(); - else - emit transactionCreationSucceeded( createTestingTransaction( plan, key ) ); - } -#else - emit transactionCreationFailed(); -#endif - } - else if ( mNextResult == NonInteractiveBuyIndividualPlan ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "tier01" ) ); - } - else if ( mNextResult == NonInteractiveBuyProfessionalPlan ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "tier12" ) ); - } - else if ( mNextResult == NonInteractiveSimulateImmediatelyCancelSubscription ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "cancel" ) ); - } - else if ( mNextResult == NonInteractiveSimulateGracePeriod ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "grace" ) ); - } - else if ( mNextResult == NonInteractiveSimulateUnsubscribed ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "unsubscribe" ) ); - } - else if ( mNextResult == NonInteractiveBadReceipt ) - { - emit transactionCreationSucceeded( createTestingTransaction( plan, "invalidreceipt" ) ); - } - else if ( mNextResult == NonInteractiveUserCancelled ) - { - emit transactionCreationFailed(); - } -} - -void TestingPurchasingBackend::restore() -{ - if ( mMerginApi->subscriptionInfo()->ownsActiveSubscription() ) - { - // we can try to "restore" only recommended plan in test backend - return; - } - - if ( mNextResult == Interactive ) - { -#if defined (HAVE_WIDGETS) - QMessageBox::information( nullptr, "TEST RESTORE", "Test restore individual plan" ); - emit transactionCreationSucceeded( createTestingTransaction( mIndividualPlan, "tier01", true ) ); - return; -#endif - } - emit transactionCreationSucceeded( createTestingTransaction( mIndividualPlan, "tier01", true ) ); -} - -QString TestingPurchasingBackend::subscriptionManageUrl() -{ - return mMerginApi->apiRoot() + "subscription"; -} - -QString TestingPurchasingBackend::subscriptionBillingUrl() -{ - return mMerginApi->apiRoot() + "billing"; -} - -QSharedPointer TestingPurchasingBackend::createTestingTransaction( QSharedPointer plan, const QString &data, bool restore ) -{ - QString planMerginId; - const int id = mMerginApi->subscriptionInfo()->subscriptionId(); - if ( id > 0 ) - { - // this is an existing subscription - planMerginId = QString::number( mMerginApi->subscriptionInfo()->subscriptionId() ); - } - - QString recept = planMerginId + "|" + data; - - PurchasingTransaction::TransactionType type; - restore ? type = PurchasingTransaction::RestoreTransaction : type = PurchasingTransaction::PuchaseTransaction; - - QSharedPointer transaction( new TestingPurchasingTransaction( recept, type, plan ) ); - return transaction; -} diff --git a/app/test/testingpurchasingbackend.h b/app/test/testingpurchasingbackend.h deleted file mode 100644 index 15d92a48e..000000000 --- a/app/test/testingpurchasingbackend.h +++ /dev/null @@ -1,103 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef TESTINGPURCHASINGBACKEND_H -#define TESTINGPURCHASINGBACKEND_H - -#include "purchasing.h" -#include "inputconfig.h" - -#include -#include - -#if defined (HAVE_WIDGETS) -#include -#endif - -class MerginApi; - -/** - * The receipt send to Mergin can trigger either some action to active - * subscription or change the subscription. - * - * Receipt is created in this form: - * "TEST|", - * - where plan_id can be set to -1 (means server please find out my plan_id) - * - and keyword is one of - * tier01 (trial plan active, buy recommended plan) - * tier12 (recommended active, buy upgrade) - * cancel (got refund from provider) - * grace (grace period entered) - * unsubscribed (user chosen not to extend subscription when this one ends) - */ -class TestingPurchasingTransaction: public PurchasingTransaction -{ - public: - TestingPurchasingTransaction( QString receipt, TransactionType type, QSharedPointer plan ); - QString receipt() const override; - MerginSubscriptionType::SubscriptionType provider() const override {return MerginSubscriptionType::TestSubscriptionType; } - - void finalizeTransaction() override {} - - private: - QString mReceipt; -}; - -/** - * Backend usefull for - * GUI testing (select NextPurchaseResult::Interactive) - * Automated tests (select NextPurchaseResult to simulate what next createTransaction() will simulate) - * - * See TestingPurchasingTransaction for description of various transactions. - * - * For the moment, the testing backend does not append plan id to receipt and let the server - * decode - */ -class TestingPurchasingBackend: public PurchasingBackend -{ - Q_OBJECT - public: - TestingPurchasingBackend( MerginApi *api ); - - enum NextPurchaseResult - { - Interactive, - NonInteractiveBuyIndividualPlan, - NonInteractiveBuyProfessionalPlan, - NonInteractiveSimulateImmediatelyCancelSubscription, - NonInteractiveSimulateGracePeriod, - NonInteractiveSimulateUnsubscribed, - NonInteractiveUserCancelled, - NonInteractiveBadReceipt - }; - void setNextPurchaseResult( const NextPurchaseResult expected ); - - void init() override {} - QSharedPointer createPlan( ) override {return QSharedPointer( new PurchasingPlan ); } - void registerPlan( QSharedPointer plan ) override; - void createTransaction( QSharedPointer plan ) override; - void restore() override; - QString subscriptionManageUrl() override; - QString subscriptionBillingUrl() override; - MerginSubscriptionType::SubscriptionType provider() const override { return MerginSubscriptionType::TestSubscriptionType; } - bool userCanMakePayments() const override { return true; } - bool hasManageSubscriptionCapability() const override { return true; } - QString getLocalizedPrice( const QString & ) const override { return ""; } - - void setMerginApi( const QString &url ); - - private: - QSharedPointer createTestingTransaction( QSharedPointer plan, const QString &data, bool restore = false ); - NextPurchaseResult mNextResult = NextPurchaseResult::Interactive; - - MerginApi *mMerginApi = nullptr; - QSharedPointer mIndividualPlan; -}; - -#endif // TESTINGPURCHASINGBACKEND_H diff --git a/app/test/testmerginapi.cpp b/app/test/testmerginapi.cpp index 232ee27c6..93d41ad4d 100644 --- a/app/test/testmerginapi.cpp +++ b/app/test/testmerginapi.cpp @@ -32,14 +32,11 @@ static MerginProject _findProjectByName( const QString &projectNamespace, const } -TestMerginApi::TestMerginApi( MerginApi *api, Purchasing *purchasing ) +TestMerginApi::TestMerginApi( MerginApi *api ) { mApi = api; Q_ASSERT( mApi ); // does not make sense to run without API - mPurchasing = purchasing; - Q_ASSERT( purchasing ); - mSyncManager = std::make_unique( mApi ); mLocalProjectsModel = std::unique_ptr( new ProjectsModel ); @@ -61,7 +58,6 @@ void TestMerginApi::initTestCase() { QString apiRoot, username, password; TestUtils::mergin_setup_auth( mApi, apiRoot, username, password ); - TestUtils::mergin_setup_pro_subscription( mApi, mPurchasing ); mUsername = username; // keep for later diff --git a/app/test/testmerginapi.h b/app/test/testmerginapi.h index 0ccd9c804..33ea94dde 100644 --- a/app/test/testmerginapi.h +++ b/app/test/testmerginapi.h @@ -18,17 +18,14 @@ #include #include #include "project.h" -#include "testingpurchasingbackend.h" #include -class Purchasing; - class TestMerginApi: public QObject { Q_OBJECT public: - explicit TestMerginApi( MerginApi *api, Purchasing *purchasing ); + explicit TestMerginApi( MerginApi *api ); ~TestMerginApi(); static const QString TEST_PROJECT_NAME; @@ -98,7 +95,6 @@ class TestMerginApi: public QObject private: MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; std::unique_ptr mLocalProjectsModel; std::unique_ptr mCreatedProjectsModel; diff --git a/app/test/testpurchasing.cpp b/app/test/testpurchasing.cpp deleted file mode 100644 index b5667c1dd..000000000 --- a/app/test/testpurchasing.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include -#include -#include - -#include "testpurchasing.h" -#include "merginapi.h" -#include "merginapistatus.h" -#include "merginuserauth.h" -#include "merginuserinfo.h" -#include "merginworkspaceinfo.h" -#include "merginsubscriptioninfo.h" -#include "testutils.h" -#include "test/testingpurchasingbackend.h" - -TestPurchasing::TestPurchasing( MerginApi *api, Purchasing *purchasing ) -{ - mApi = api; - Q_ASSERT( mApi ); // does not make sense to run without API - - Q_ASSERT( purchasing ); - mPurchasing = purchasing; -} - -void TestPurchasing::initTestCase() -{ - QString apiRoot, username, password; - TestUtils::mergin_setup_auth( mApi, apiRoot, username, password ); - TestUtils::mergin_setup_pro_subscription( mApi, mPurchasing ); -} - -void TestPurchasing::cleanupTestCase() -{ -} - -void TestPurchasing::testUserBuyTier01() -{ - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserBuyTier12() -{ - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyProfessionalPlan, TestUtils::TIER02_PLAN_ID ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER02_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER02_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserUnsubscribed() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateUnsubscribed, TestUtils::TIER01_PLAN_ID, false ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::SubscriptionUnsubscribed ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserInGracePeriod() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveBuyIndividualPlan, TestUtils::TIER01_PLAN_ID ); - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateGracePeriod, TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), true ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::SubscriptionInGracePeriod ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} - -void TestPurchasing::testUserCancelledSubscription() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), "" ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::FREE_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->ownsActiveSubscription(), false ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::NoneSubscriptionType ); -} - -void TestPurchasing::testUserCancelledTransaction() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - Q_ASSERT( mPurchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( mPurchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - int oldStatus = mApi->subscriptionInfo()->subscriptionStatus(); - purchasingBackend->setNextPurchaseResult( TestingPurchasingBackend::NonInteractiveUserCancelled ); - - QSignalSpy spy0( purchasingBackend, &PurchasingBackend::transactionCreationFailed ); - mPurchasing->purchase( TestUtils::TIER01_PLAN_ID ); - // immediate action without server for testbackend - QCOMPARE( spy0.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), oldStatus ); -} - -void TestPurchasing::testUserSendsBadReceipt() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - Q_ASSERT( mPurchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( mPurchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - int oldStatus = mApi->subscriptionInfo()->subscriptionStatus(); - purchasingBackend->setNextPurchaseResult( TestingPurchasingBackend::NonInteractiveBadReceipt ); - - QSignalSpy spy0( mApi, &MerginApi::networkErrorOccurred ); - mPurchasing->purchase( TestUtils::TIER01_PLAN_ID ); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), oldStatus ); -} - -void TestPurchasing::testUserRestore() -{ - QSKIP( "Must be revisited when working with workspaces!" ); - - TestUtils::runPurchasingCommand( mApi, mPurchasing, TestingPurchasingBackend::NonInteractiveSimulateImmediatelyCancelSubscription, mApi->subscriptionInfo()->planProductId() ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::CanceledSubscription ); - - QSignalSpy spy0( mApi->subscriptionInfo(), &MerginSubscriptionInfo::subscriptionInfoChanged ); - QSignalSpy spy1( mApi->userInfo(), &MerginUserInfo::userInfoChanged ); - mPurchasing->restore(); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); - QVERIFY( spy1.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy1.count(), 1 ); - - QCOMPARE( mApi->subscriptionInfo()->planProductId(), TestUtils::TIER01_PLAN_ID ); - QCOMPARE( mApi->workspaceInfo()->storageLimit(), TestUtils::TIER01_STORAGE ); - QCOMPARE( mApi->subscriptionInfo()->subscriptionStatus(), MerginSubscriptionStatus::ValidSubscription ); - QCOMPARE( mApi->subscriptionInfo()->planProvider(), MerginSubscriptionType::TestSubscriptionType ); -} diff --git a/app/test/testpurchasing.h b/app/test/testpurchasing.h deleted file mode 100644 index efa5d9198..000000000 --- a/app/test/testpurchasing.h +++ /dev/null @@ -1,45 +0,0 @@ -/*************************************************************************** - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License as published by * - * the Free Software Foundation; either version 2 of the License, or * - * (at your option) any later version. * - * * - ***************************************************************************/ - -#ifndef TESTPURCHASING_H -#define TESTPURCHASING_H - -#include - -#include "inputconfig.h" - -class MerginApi; -class Purchasing; - -class TestPurchasing: public QObject -{ - Q_OBJECT - public: - explicit TestPurchasing( MerginApi *api, Purchasing *purchasing ); - ~TestPurchasing() = default; - - private slots: - void initTestCase(); - void cleanupTestCase(); - - void testUserBuyTier01(); - void testUserBuyTier12(); - void testUserCancelledTransaction(); - void testUserUnsubscribed(); - void testUserInGracePeriod(); - void testUserCancelledSubscription(); - void testUserSendsBadReceipt(); - void testUserRestore(); - - private: - MerginApi *mApi = nullptr; - Purchasing *mPurchasing = nullptr; -}; - -# endif // TESTPURCHASING_H diff --git a/app/test/testutils.cpp b/app/test/testutils.cpp index f58cd7850..fd7c4b6b2 100644 --- a/app/test/testutils.cpp +++ b/app/test/testutils.cpp @@ -16,8 +16,6 @@ #include "coreutils.h" #include "inpututils.h" #include "merginapi.h" -#include "purchasing.h" -#include "testingpurchasingbackend.h" void TestUtils::mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &username, QString &password ) { @@ -84,48 +82,6 @@ void TestUtils::mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &us qDebug() << "MERGIN WORKSPACE:" << api->userInfo()->activeWorkspaceName() << api->userInfo()->activeWorkspaceId(); } -void TestUtils::mergin_setup_pro_subscription( MerginApi *api, Purchasing *purchasing ) -{ - QSignalSpy spy2( api, &MerginApi::subscriptionInfoChanged ); - api->getServiceInfo(); - QVERIFY( spy2.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy2.count(), 1 ); - - Q_ASSERT( ! purchasing->transactionPending() ); - - if ( api->subscriptionInfo()->planProductId() != TIER02_PLAN_ID ) - { - // always start from PRO subscription - qDebug() << "PURCHASE PRO subscription:" << api->userInfo()->activeWorkspaceName(); - runPurchasingCommand( api, purchasing, TestingPurchasingBackend::NonInteractiveBuyProfessionalPlan, TIER02_PLAN_ID ); - } - - Q_ASSERT( api->subscriptionInfo()->planProductId() == TIER02_PLAN_ID ); - qDebug() << "MERGIN SUBSCRIPTION:" << api->subscriptionInfo()->planProductId(); -} - -void TestUtils::runPurchasingCommand( MerginApi *api, Purchasing *purchasing, TestingPurchasingBackend::NextPurchaseResult result, const QString &planId, bool waitForWorkspaceInfoChanged ) -{ - Q_ASSERT( purchasing ); - TestingPurchasingBackend *purchasingBackend = qobject_cast( purchasing->backend() ); - Q_ASSERT( purchasingBackend ); - - purchasingBackend->setNextPurchaseResult( result ); - - QSignalSpy spy0( api, &MerginApi::subscriptionInfoChanged ); - QVERIFY( !planId.isEmpty() ); - QSignalSpy spy1( api->workspaceInfo(), &MerginWorkspaceInfo::workspaceInfoChanged ); - - purchasing->purchase( planId ); - QVERIFY( spy0.wait( TestUtils::LONG_REPLY ) ); - QCOMPARE( spy0.count(), 1 ); - - if ( waitForWorkspaceInfoChanged ) - { - QVERIFY( spy1.wait( TestUtils::LONG_REPLY ) ); - } -} - QString TestUtils::generateUsername() { QDateTime time = QDateTime::currentDateTime(); diff --git a/app/test/testutils.h b/app/test/testutils.h index d25cad6df..4f79f26cb 100644 --- a/app/test/testutils.h +++ b/app/test/testutils.h @@ -15,11 +15,8 @@ #include "inputconfig.h" #include "qgsproject.h" -#include "testingpurchasingbackend.h" class MerginApi; -class Purchasing; -class TestingPurchasingBackend; namespace TestUtils { @@ -37,9 +34,6 @@ namespace TestUtils //! Use credentials from env variables if they are set, otherwise register new user and set its credentials to env var void mergin_setup_auth( MerginApi *api, QString &apiRoot, QString &username, QString &password ); - //! Setup professional plan for active workspace - void mergin_setup_pro_subscription( MerginApi *api, Purchasing *purchasing ); - QString generateUsername(); QString generateEmail(); QString generatePassword(); @@ -56,9 +50,6 @@ namespace TestUtils * Returns true if files were successfully created */ bool generateProjectFolder( const QString &rootPath, const QJsonDocument &structure ); - - //! Test util function to invoke purchasing function and wait for the replies. - void runPurchasingCommand( MerginApi *api, Purchasing *purchasing, TestingPurchasingBackend::NextPurchaseResult result, const QString &planId, bool waitForWorkspaceInfoChanged = true ); } #define COMPARENEAR(actual, expected, epsilon) \ diff --git a/cmake/FindAppleFrameworks.cmake b/cmake/FindAppleFrameworks.cmake index b083f2aa4..d81d5001e 100644 --- a/cmake/FindAppleFrameworks.cmake +++ b/cmake/FindAppleFrameworks.cmake @@ -20,10 +20,6 @@ set(APPLE_FRAMEWORKS CoreLocation ) -if (HAVE_APPLE_PURCHASING) - set(APPLE_FRAMEWORKS ${APPLE_FRAMEWORKS} Foundation StoreKit) -endif () - foreach (framework ${APPLE_FRAMEWORKS}) find_library(APPLE_${framework}_LIBRARY NAMES ${framework}) set(APPLE_REQUIRED_VARS ${APPLE_REQUIRED_VARS} APPLE_${framework}_LIBRARY) diff --git a/cmake_templates/inputconfig.h.in b/cmake_templates/inputconfig.h.in index 0de9c322c..20e6c5c62 100644 --- a/cmake_templates/inputconfig.h.in +++ b/cmake_templates/inputconfig.h.in @@ -14,9 +14,5 @@ #cmakedefine HAVE_BLUETOOTH -#cmakedefine PURCHASING - -#cmakedefine APPLE_PURCHASING - #endif diff --git a/core/merginapi.h b/core/merginapi.h index eedcbebef..f3410e609 100644 --- a/core/merginapi.h +++ b/core/merginapi.h @@ -35,8 +35,6 @@ #include "merginworkspaceinfo.h" #include "merginuserauth.h" -class Purchasing; - class RegistrationError { Q_GADGET @@ -806,8 +804,6 @@ class MerginApi: public QObject MerginServerType::ServerType mServerType = MerginServerType::ServerType::OLD; friend class TestMerginApi; - friend class Purchasing; - friend class PurchasingTransaction; }; #endif // MERGINAPI_H diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4e1c7cf5d..50085091f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -11,7 +11,6 @@ set(MM_TESTS testUtils testAttributePreviewController testMerginApi - testPurchasing testAttributeController testIdentifyKit testPosition