diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ec6b5a9f6..1e163a2117 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ if(APPLE) set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER}) if(IOS) - set(CMAKE_OSX_DEPLOYMENT_TARGET 14.0) + set(CMAKE_OSX_DEPLOYMENT_TARGET 15.0) else() set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0) endif() diff --git a/src/platforms/ios/iosiaphandler.h b/src/platforms/ios/iosiaphandler.h index 5cd1427fc6..a210e83506 100644 --- a/src/platforms/ios/iosiaphandler.h +++ b/src/platforms/ios/iosiaphandler.h @@ -18,11 +18,8 @@ class IOSIAPHandler final : public PurchaseIAPHandler { void nativeRegisterProducts() override; public slots: - void productRegistered(void* product); - void processCompletedTransactions(const QStringList& ids); void processCompletedTransactions(const QStringList& ids, const QString transactionIdentifier); - void noSubscriptionFoundError(); protected: void nativeStartSubscription(ProductsHandler::Product* product) override; @@ -30,13 +27,6 @@ class IOSIAPHandler final : public PurchaseIAPHandler { private: void* m_delegate = nullptr; - int discountToDays(void* discount); - // This is a void (and cast to InAppPurchaseHandler as needed) as the - // InAppPurchaseHandler needs to store Product, which is only available in iOS - // 15+. So the entire class must be marked as only available in iOS 15. But we - // can't mark this variable as only available in iOS 15+ here. Hence, we have - // this variable as a `void`, and cast it to InAppPurchaseHandler as needed. - void* swiftIAPHandler = nullptr; }; #endif // IOSIAPHANDLER_H diff --git a/src/platforms/ios/iosiaphandler.mm b/src/platforms/ios/iosiaphandler.mm index 44c90bf247..8cddae9ff1 100644 --- a/src/platforms/ios/iosiaphandler.mm +++ b/src/platforms/ios/iosiaphandler.mm @@ -19,228 +19,45 @@ #include #import -#import constexpr const uint32_t GUARDIAN_ERROR_RECEIPT_NOT_VALID = 142; constexpr const uint32_t GUARDIAN_ERROR_RECEIPT_IN_USE = 145; namespace { Logger logger("IOSIAPHandler"); -bool s_transactionsProcessed = false; +InAppPurchaseHandler* swiftIAPHandler = nullptr; } // namespace -@interface IOSIAPHandlerDelegate - : NSObject { - IOSIAPHandler* m_handler; -} -@end - -@implementation IOSIAPHandlerDelegate - -- (id)initWithObject:(IOSIAPHandler*)handler { - self = [super init]; - if (self) { - m_handler = handler; - } - return self; -} - -- (void)productsRequest:(nonnull SKProductsRequest*)request - didReceiveResponse:(nonnull SKProductsResponse*)response { - logger.debug() << "Registration completed"; - - ProductsHandler* productsHandler = ProductsHandler::instance(); - PurchaseIAPHandler* purchaseHandler = IOSIAPHandler::instance(); - - if (response.invalidProductIdentifiers) { - NSArray* products = response.invalidProductIdentifiers; - logger.error() << "Registration failure" << [products count]; - - for (unsigned long i = 0, count = [products count]; i < count; ++i) { - NSString* identifier = [products objectAtIndex:i]; - QMetaObject::invokeMethod(productsHandler, "unknownProductRegistered", Qt::QueuedConnection, - Q_ARG(QString, QString::fromNSString(identifier))); - } - } - - NSArray* products = response.products; - if (products) { - logger.debug() << "Products registered" << [products count]; - - for (unsigned long i = 0, count = [products count]; i < count; ++i) { - SKProduct* product = [[products objectAtIndex:i] retain]; - QMetaObject::invokeMethod(purchaseHandler, "productRegistered", Qt::QueuedConnection, - Q_ARG(void*, product)); - } - } - - QMetaObject::invokeMethod(productsHandler, "productsRegistrationCompleted", Qt::QueuedConnection); - - [request release]; -} - -- (void)paymentQueue:(nonnull SKPaymentQueue*)queue - updatedTransactions:(nonnull NSArray*)transactions { - logger.debug() << "payment queue:" << [transactions count]; - - s_transactionsProcessed = true; - - QStringList completedTransactionIds; - bool failedTransactions = false; - bool canceledTransactions = false; - bool completedTransactions = false; - - for (SKPaymentTransaction* transaction in transactions) { - switch (transaction.transactionState) { - case SKPaymentTransactionStateFailed: - logger.error() << "transaction failed: " << transaction.error.code; - - if (transaction.error.code == SKErrorPaymentCancelled) { - canceledTransactions = true; - } else { - failedTransactions = true; - } - break; - - case SKPaymentTransactionStateRestored: - [[fallthrough]]; - case SKPaymentTransactionStatePurchased: { - QString identifier = QString::fromNSString(transaction.transactionIdentifier); - QDateTime date = QDateTime::fromNSDate(transaction.transactionDate); - logger.debug() << "transaction purchased - identifier: " << identifier - << "- date:" << date.toString(); - - if (transaction.transactionState == SKPaymentTransactionStateRestored) { - SKPaymentTransaction* originalTransaction = transaction.originalTransaction; - if (originalTransaction) { - QString originalIdentifier = - QString::fromNSString(originalTransaction.transactionIdentifier); - QDateTime originalDate = QDateTime::fromNSDate(originalTransaction.transactionDate); - logger.debug() << "original transaction identifier: " << originalIdentifier - << "- date:" << originalDate.toString(); - } - } +IOSIAPHandler::IOSIAPHandler(QObject* parent) : PurchaseIAPHandler(parent) { + MZ_COUNT_CTOR(IOSIAPHandler); - completedTransactions = true; + swiftIAPHandler = [[InAppPurchaseHandler alloc] + initWithErrorCallback:^(bool showNoSubscriptionError) { + logger.debug() << "Subscription error with StoreKit2."; + QMetaObject::invokeMethod(this, "stopSubscription", Qt::QueuedConnection); + QMetaObject::invokeMethod(this, "subscriptionCanceled", Qt::QueuedConnection); - if (SettingsHolder::instance()->subscriptionTransactions().contains(identifier)) { - logger.warning() << "This transaction has already been processed. Let's ignore it."; - } else { - completedTransactionIds.append(identifier); + if (showNoSubscriptionError) { + emit ErrorHandler::instance()->noSubscriptionFound(); } - - break; } - case SKPaymentTransactionStatePurchasing: - logger.debug() << "transaction purchasing"; - break; - case SKPaymentTransactionStateDeferred: - logger.debug() << "transaction deferred"; - break; - default: - logger.warning() << "transaction unknown state"; - break; - } - } - - if (!completedTransactions && !canceledTransactions && !failedTransactions) { - // Nothing completed, nothing restored, nothing failed. Just purchasing transactions. - return; - } - - if (canceledTransactions) { - logger.debug() << "Subscription canceled"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (failedTransactions) { - logger.error() << "Subscription failed"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (completedTransactionIds.isEmpty()) { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed - but all the transactions are known"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } else if (App::instance()->userAuthenticated()) { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed. Let's start the validation"; - QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection, - Q_ARG(QStringList, completedTransactionIds)); - } else { - Q_ASSERT(completedTransactions); - logger.debug() << "Subscription completed - but the user is not authenticated yet"; - QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); - } - - for (SKPaymentTransaction* transaction in transactions) { - switch (transaction.transactionState) { - case SKPaymentTransactionStateFailed: - [[fallthrough]]; - case SKPaymentTransactionStateRestored: - [[fallthrough]]; - case SKPaymentTransactionStatePurchased: - [queue finishTransaction:transaction]; - break; - default: - break; - } - } -} - -- (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue*)queue { - if (!s_transactionsProcessed) { - logger.error() << "No transaction to restore"; - QMetaObject::invokeMethod(m_handler, "noSubscriptionFoundError", Qt::QueuedConnection); - } - s_transactionsProcessed = false; - logger.debug() << "restore request completed"; -} - -@end - -IOSIAPHandler::IOSIAPHandler(QObject* parent) : PurchaseIAPHandler(parent) { - MZ_COUNT_CTOR(IOSIAPHandler); - - if (@available(iOS 15, *)) { - swiftIAPHandler = [[InAppPurchaseHandler alloc] - initWithErrorCallback:^(void) { - logger.debug() << "Subscription error with StoreKit2."; + successCallback:^(NSString* productIdentifier, NSString* transactionIdentifier) { + if (App::instance()->userAuthenticated()) { + logger.debug() << "Subscription completed with StoreKit2. Starting validation."; + QMetaObject::invokeMethod(this, "processCompletedTransactions", Qt::QueuedConnection, + Q_ARG(QStringList, {QString::fromNSString(productIdentifier)}), + Q_ARG(QString, QString::fromNSString(transactionIdentifier))); + } else { + logger.debug() << "Subscription completed with StoreKit2. User not signed in."; QMetaObject::invokeMethod(this, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(this, "subscriptionCanceled", Qt::QueuedConnection); } - successCallback:^(NSString* productIdentifier, NSString* transactionIdentifier) { - if (App::instance()->userAuthenticated()) { - logger.debug() << "Subscription completed with StoreKit2. Starting validation."; - QMetaObject::invokeMethod( - this, "processCompletedTransactions", Qt::QueuedConnection, - Q_ARG(QStringList, {QString::fromNSString(productIdentifier)}), - Q_ARG(QString, QString::fromNSString(transactionIdentifier))); - } else { - logger.debug() << "Subscription completed with StoreKit2. User not signed in."; - QMetaObject::invokeMethod(this, "stopSubscription", Qt::QueuedConnection); - QMetaObject::invokeMethod(this, "subscriptionCanceled", Qt::QueuedConnection); - } - }]; - } else { - m_delegate = [[IOSIAPHandlerDelegate alloc] initWithObject:this]; - [[SKPaymentQueue defaultQueue] - addTransactionObserver:static_cast(m_delegate)]; - } + }]; } IOSIAPHandler::~IOSIAPHandler() { MZ_COUNT_DTOR(IOSIAPHandler); - - if (@available(iOS 15, *)) { - swiftIAPHandler = nullptr; - } else { - IOSIAPHandlerDelegate* delegate = static_cast(m_delegate); - [[SKPaymentQueue defaultQueue] removeTransactionObserver:delegate]; - - [delegate dealloc]; - m_delegate = nullptr; - } + swiftIAPHandler = nullptr; } void IOSIAPHandler::nativeRegisterProducts() { @@ -249,167 +66,58 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue productIdentifiers = [productIdentifiers setByAddingObject:product.m_name.toNSString()]; } - if (@available(iOS 15, *)) { - logger.debug() << "Registering" << [productIdentifiers count] - << "products using StoreKit2 API."; - [(InAppPurchaseHandler*)swiftIAPHandler getProductsWith:productIdentifiers - productRegistrationCallback:^(NSString* productIdentifier, NSString* currencyCode, - NSString* totalPrice, NSString* monthlyPrice, - double monthlyPriceNumber, NSInteger freeTrialDays) { - ProductsHandler* productsHandler = ProductsHandler::instance(); - Q_ASSERT(productsHandler->isRegistering()); - logger.debug() << "Product registered"; - ProductsHandler::Product* productData = - productsHandler->findProduct(QString::fromNSString(productIdentifier)); - Q_ASSERT(productData); - productData->m_price = QString::fromNSString(totalPrice); - productData->m_trialDays = (int)freeTrialDays; - productData->m_monthlyPrice = QString::fromNSString(monthlyPrice); - productData->m_nonLocalizedMonthlyPrice = monthlyPriceNumber; - productData->m_currencyCode = QString::fromNSString(currencyCode); - - logger.debug() << "Id:" << QString::fromNSString(productIdentifier); - logger.debug() << "Price:" << productData->m_price; - logger.debug() << "Monthly price:" << productData->m_monthlyPrice; - } - registrationCompleteCallback:^(void) { - ProductsHandler* productsHandler = ProductsHandler::instance(); - QMetaObject::invokeMethod(productsHandler, "productsRegistrationCompleted", - Qt::QueuedConnection); - } - registrationFailureCallback:^{ - logger.error() << "Registration failure"; - ProductsHandler* productsHandler = ProductsHandler::instance(); - NSArray* productIdentifiersArray = [productIdentifiers allObjects]; - for (unsigned long i = 0, count = [productIdentifiersArray count]; i < count; ++i) { - NSString* identifier = [productIdentifiersArray objectAtIndex:i]; - productsHandler->unknownProductRegistered(QString::fromNSString(identifier)); - } + logger.debug() << "Registering" << [productIdentifiers count] << "products using StoreKit2 API."; + [swiftIAPHandler getProductsWith:productIdentifiers + productRegistrationCallback:^(NSString* productIdentifier, NSString* currencyCode, + NSString* totalPrice, NSString* monthlyPrice, + double monthlyPriceNumber, NSInteger freeTrialDays) { + ProductsHandler* productsHandler = ProductsHandler::instance(); + Q_ASSERT(productsHandler->isRegistering()); + logger.debug() << "Product registered"; + ProductsHandler::Product* productData = + productsHandler->findProduct(QString::fromNSString(productIdentifier)); + Q_ASSERT(productData); + productData->m_price = QString::fromNSString(totalPrice); + productData->m_trialDays = (int)freeTrialDays; + productData->m_monthlyPrice = QString::fromNSString(monthlyPrice); + productData->m_nonLocalizedMonthlyPrice = monthlyPriceNumber; + productData->m_currencyCode = QString::fromNSString(currencyCode); + + logger.debug() << "Id:" << QString::fromNSString(productIdentifier); + logger.debug() << "Price:" << productData->m_price; + logger.debug() << "Monthly price:" << productData->m_monthlyPrice; + } + registrationCompleteCallback:^(void) { + ProductsHandler* productsHandler = ProductsHandler::instance(); + QMetaObject::invokeMethod(productsHandler, "productsRegistrationCompleted", + Qt::QueuedConnection); + } + registrationFailureCallback:^{ + logger.error() << "Registration failure"; + ProductsHandler* productsHandler = ProductsHandler::instance(); + NSArray* productIdentifiersArray = [productIdentifiers allObjects]; + for (unsigned long i = 0, count = [productIdentifiersArray count]; i < count; ++i) { + NSString* identifier = [productIdentifiersArray objectAtIndex:i]; + productsHandler->unknownProductRegistered(QString::fromNSString(identifier)); } - completionHandler:^{ - }]; - } else { - logger.debug() << "Registering" << [productIdentifiers count] << "using legacy StoreKit API."; - SKProductsRequest* productsRequest = - [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers]; - - IOSIAPHandlerDelegate* delegate = static_cast(m_delegate); - productsRequest.delegate = delegate; - [productsRequest start]; - } + } + completionHandler:^{ + }]; } void IOSIAPHandler::nativeStartSubscription(ProductsHandler::Product* product) { - if (@available(iOS 15, *)) { - logger.debug() << "Using StoreKit2 APIs"; - NSString* productId = product->m_name.toNSString(); - [(InAppPurchaseHandler*)swiftIAPHandler startSubscriptionFor:productId - completionHandler:^{ - }]; - } else { - logger.debug() << "Using legacy StoreKit API."; - Q_ASSERT(product->m_extra); - SKProduct* skProduct = static_cast(product->m_extra); - SKPayment* payment = [SKPayment paymentWithProduct:skProduct]; - [[SKPaymentQueue defaultQueue] addPayment:payment]; - } + logger.debug() << "Using StoreKit2 APIs"; + NSString* productId = product->m_name.toNSString(); + [swiftIAPHandler startSubscriptionFor:productId + completionHandler:^{ + }]; } void IOSIAPHandler::nativeRestoreSubscription() { - if (@available(iOS 15, *)) { - [(InAppPurchaseHandler*)swiftIAPHandler restoreSubscriptionsWithCompletionHandler:^{ - }]; - } else { - s_transactionsProcessed = false; - [[SKPaymentQueue defaultQueue] restoreCompletedTransactions]; - } -} - -void IOSIAPHandler::productRegistered(void* a_product) { - SKProduct* product = static_cast(a_product); - - ProductsHandler* productsHandler = ProductsHandler::instance(); - - Q_ASSERT(productsHandler->isRegistering()); - - logger.debug() << "Product registered"; - - NSString* nsProductIdentifier = [product productIdentifier]; - QString productIdentifier = QString::fromNSString(nsProductIdentifier); - - ProductsHandler::Product* productData = productsHandler->findProduct(productIdentifier); - Q_ASSERT(productData); - - logger.debug() << "Id:" << productIdentifier; - logger.debug() << "Title:" << QString::fromNSString([product localizedTitle]); - logger.debug() << "Description:" << QString::fromNSString([product localizedDescription]); - - QString priceValue; - { - NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - - NSString* price = [numberFormatter stringFromNumber:product.price]; - priceValue = QString::fromNSString(price); - [numberFormatter release]; - } - - logger.debug() << "Price:" << priceValue; - - QString monthlyPriceValue; - NSDecimalNumber* monthlyPriceNS = nullptr; - { - NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; - [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; - [numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle]; - [numberFormatter setLocale:product.priceLocale]; - - int32_t mounthCount = productsHandler->productTypeToMonthCount(productData->m_type); - Q_ASSERT(mounthCount >= 1); - - if (mounthCount == 1) { - monthlyPriceNS = product.price; - } else { - NSDecimalNumber* divider = [[NSDecimalNumber alloc] initWithDouble:(double)mounthCount]; - monthlyPriceNS = [product.price decimalNumberByDividingBy:divider]; - [divider release]; - } - - NSString* price = [numberFormatter stringFromNumber:monthlyPriceNS]; - monthlyPriceValue = QString::fromNSString(price); - - [numberFormatter release]; - } - int discountDays = 0; - if (@available(iOS 12.2, *)) { - auto discount = product.introductoryPrice; - discountDays = discountToDays(discount); - } - - logger.debug() << "Monthly Price:" << monthlyPriceValue; - - productData->m_price = priceValue; - productData->m_trialDays = discountDays; - productData->m_monthlyPrice = monthlyPriceValue; - productData->m_nonLocalizedMonthlyPrice = [monthlyPriceNS doubleValue]; - productData->m_currencyCode = QString::fromNSString(product.priceLocale.currencyCode); - productData->m_extra = product; + [swiftIAPHandler restoreSubscriptionsWithCompletionHandler:^{ + }]; } -// Called directly when using StoreKit1 -void IOSIAPHandler::processCompletedTransactions(const QStringList& ids) { - if (@available(iOS 15, *)) { - logger.error() << "This block should never be hit."; - assert(NO); - } else { - // For StoreKit1, we don't need a transaction identifier, so giving it some garbage. - processCompletedTransactions(ids, "this should never be seen"); - } -} - -// Called directly when using StoreKit2 void IOSIAPHandler::processCompletedTransactions(const QStringList& ids, const QString transactionIdentifier) { logger.debug() << "process completed transactions"; @@ -418,18 +126,7 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue logger.warning() << "Completing transaction out of subscription process!"; } - TaskPurchase* purchase; - if (@available(iOS 15, *)) { - purchase = TaskPurchase::createForIOS(transactionIdentifier, false); - } else { - QString receipt = IOSUtils::IAPReceipt(); - if (receipt.isEmpty()) { - logger.warning() << "Empty receipt found"; - emit subscriptionFailed(); - return; - } - purchase = TaskPurchase::createForIOS(receipt, true); - } + TaskPurchase* purchase = TaskPurchase::createForIOS(transactionIdentifier); Q_ASSERT(purchase); @@ -490,37 +187,3 @@ - (void)paymentQueueRestoreCompletedTransactionsFinished:(nonnull SKPaymentQueue TaskScheduler::scheduleTask(purchase); } - -void IOSIAPHandler::noSubscriptionFoundError() { - emit subscriptionCanceled(); - emit ErrorHandler::instance()->noSubscriptionFound(); -} - -int IOSIAPHandler::discountToDays(void* aDiscount) { - SKProductDiscount* discount = static_cast(aDiscount); - if (discount == nullptr) { - return 0; - } - if (discount.paymentMode != SKProductDiscountPaymentMode::SKProductDiscountPaymentModeFreeTrial) { - return 0; - } - // Is it a week / day / month - auto discountPeriodUnit = discount.subscriptionPeriod.unit; - // How many units (i.e 3 days) per period - auto periodUnits = (int)discount.subscriptionPeriod.numberOfUnits; - // How many period's are we getting - auto discountAmount = (int)discount.numberOfPeriods; - switch (discountPeriodUnit) { - case SKProductPeriodUnitDay: - return discountAmount * periodUnits; - case SKProductPeriodUnitWeek: - return 7 * discountAmount * periodUnits; - case SKProductPeriodUnitMonth: - return 30 * discountAmount * periodUnits; - default: - Q_UNREACHABLE(); - return 0; - } - Q_UNREACHABLE(); - return 0; -} diff --git a/src/platforms/ios/iosiaphandler.swift b/src/platforms/ios/iosiaphandler.swift index 79edc096df..40866b44f9 100644 --- a/src/platforms/ios/iosiaphandler.swift +++ b/src/platforms/ios/iosiaphandler.swift @@ -1,7 +1,6 @@ import Foundation import StoreKit -@available(iOS 15, *) @objc class InAppPurchaseHandler: NSObject { private static let logger = IOSLoggerImpl(tag: "InAppPurchaseHandler") @@ -15,10 +14,10 @@ import StoreKit private var productList: [Product] = [] var transactionUpdates: Task? = nil - var errorCallback: (() -> Void) + var errorCallback: ((Bool) -> Void) var successCallback: ((NSString, NSString) -> Void) - @objc init(errorCallback: @escaping (() -> Void), successCallback: @escaping ((NSString, NSString) -> Void)) { + @objc init(errorCallback: @escaping ((Bool) -> Void), successCallback: @escaping ((NSString, NSString) -> Void)) { self.errorCallback = errorCallback self.successCallback = successCallback super.init() @@ -85,7 +84,7 @@ import StoreKit @objc func startSubscription(for productIdentifier: String) async { guard let product = productList.first(where: {$0.id == productIdentifier}) else { InAppPurchaseHandler.logger.error(message: "Could not find a product with ID \(productIdentifier)") - errorCallback() + errorCallback(false) assertionFailure() return } @@ -95,7 +94,7 @@ import StoreKit await handlePurchaseResult(purchaseResult) } catch { InAppPurchaseHandler.logger.error(message: "Error purchasing product: \(error.localizedDescription)") - errorCallback() + errorCallback(false) assertionFailure() } } @@ -111,17 +110,17 @@ import StoreKit await transaction.finish() case .unverified(_, let verificationError): InAppPurchaseHandler.logger.info(message: "StoreKit subscription returned success, but unverified: \(verificationError.localizedDescription)") - errorCallback() + errorCallback(false) } case .pending: InAppPurchaseHandler.logger.info(message: "StoreKit subscription returned pending") // do not call error nor success - wait for further updates via `Transaction.updates` case .userCancelled: InAppPurchaseHandler.logger.info(message: "StoreKit subscription returned userCancelled") - errorCallback() + errorCallback(false) @unknown default: InAppPurchaseHandler.logger.error(message: "purchaseResult using a new enum") - errorCallback() + errorCallback(false) } } @@ -142,7 +141,7 @@ import StoreKit } if !didFindVerifiedTransaction { InAppPurchaseHandler.logger.info(message: "Found no verified transactions") - errorCallback() + errorCallback(true) } } @@ -188,18 +187,18 @@ import StoreKit if let _ = transaction.revocationDate { InAppPurchaseHandler.logger.info(message: "Transaction was revoked") - errorCallback() + errorCallback(false) } else if let expirationDate = transaction.expirationDate, expirationDate < Date() { // Expirations handled by server InAppPurchaseHandler.logger.info(message: "Transaction has expired") - errorCallback() + errorCallback(false) return } else if transaction.isUpgraded { // Do nothing, there is an active transaction // for a higher level of service. InAppPurchaseHandler.logger.info(message: "Transaction was upgraded") - errorCallback() + errorCallback(false) return } else { InAppPurchaseHandler.logger.info(message: "New successful transaction returned") diff --git a/src/tasks/purchase/taskpurchase.cpp b/src/tasks/purchase/taskpurchase.cpp index 63690c5d5b..442dd6c96c 100644 --- a/src/tasks/purchase/taskpurchase.cpp +++ b/src/tasks/purchase/taskpurchase.cpp @@ -22,11 +22,9 @@ Logger logger("TaskPurchase"); #ifdef MZ_IOS // static -TaskPurchase* TaskPurchase::createForIOS(const QString& receipt, - bool isOlderOS) { +TaskPurchase* TaskPurchase::createForIOS(const QString& originalTransactionId) { TaskPurchase* task = new TaskPurchase(IOS); - task->m_iOSData = receipt; - task->m_isOlderOS = isOlderOS; + task->m_iOSOriginalTransactionId = originalTransactionId; return task; } #endif @@ -61,11 +59,7 @@ void TaskPurchase::run() { #ifdef MZ_IOS // APIv2 returns 200, APIv1 returns 201 int expectedStatusCode; - if (m_isOlderOS) { - expectedStatusCode = 201; - } else { - expectedStatusCode = 200; - } + expectedStatusCode = 200; #endif NetworkRequest* request = new NetworkRequest(this, #ifdef MZ_IOS @@ -81,14 +75,8 @@ void TaskPurchase::run() { case IOS: Constants::ApiEndpoint endpoint; QJsonObject body; - if (m_isOlderOS) { - endpoint = Constants::PurchasesIOS; - body = QJsonObject{{"receipt", m_iOSData}, - {"appId", QString::fromNSString(IOSUtils::appId())}}; - } else { - endpoint = Constants::PurchasesIOSv2; - body = QJsonObject{{"originalTransactionId", m_iOSData}}; - } + endpoint = Constants::PurchasesIOSv2; + body = QJsonObject{{"originalTransactionId", m_iOSOriginalTransactionId}}; request->post(Constants::apiUrl(endpoint), body); break; #endif diff --git a/src/tasks/purchase/taskpurchase.h b/src/tasks/purchase/taskpurchase.h index 1a32e067b8..c2b243ef29 100644 --- a/src/tasks/purchase/taskpurchase.h +++ b/src/tasks/purchase/taskpurchase.h @@ -16,7 +16,7 @@ class TaskPurchase final : public Task { public: #ifdef MZ_IOS - static TaskPurchase* createForIOS(const QString& receipt, bool isOlderOS); + static TaskPurchase* createForIOS(const QString& originalTransactionId); #endif #ifdef MZ_ANDROID static TaskPurchase* createForAndroid(const QString& sku, @@ -52,12 +52,7 @@ class TaskPurchase final : public Task { private: Op m_op; #ifdef MZ_IOS - // m_iOSData is the originalTransactionId for iOS 15+, and the receipt for - // earlier versions of iOS - QString m_iOSData; - // `@available(iOS 15, *)` is not available in C++, so this value must be - // passed in and saved - bool m_isOlderOS; + QString m_iOSOriginalTransactionId; #endif #ifdef MZ_ANDROID QString m_androidSku;