From 870114d7aeeacafc7e4a7e98637bf79ee06a0962 Mon Sep 17 00:00:00 2001 From: stuartmorgan Date: Thu, 17 Oct 2024 10:32:23 -0700 Subject: [PATCH] Revert "[in_app_purchase_storekit] Add support for purchase and transactions #7574" (#7886) Reverts flutter/packages#7812 See https://github.com/flutter/packages/pull/7812#issuecomment-2419280873 --- .../in_app_purchase_storekit/CHANGELOG.md | 4 - .../darwin/Classes/InAppPurchasePlugin.swift | 16 - .../StoreKit2/InAppPurchaseStoreKit2.swift | 151 +------- .../StoreKit2/StoreKit2Translators.swift | 35 -- .../Classes/StoreKit2/sk2_pigeon.g.swift | 296 +-------------- .../ios/Runner.xcodeproj/project.pbxproj | 16 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 - .../example/ios/Runner/Configuration.storekit | 86 +---- .../InAppPurchaseStoreKit2PluginTests.swift | 100 +++++- .../StoreKit2TranslatorTests.swift | 83 ++++- .../macos/Runner.xcodeproj/project.pbxproj | 16 +- .../contents.xcworkspacedata | 3 + .../InAppPurchaseStoreKit2PluginTests.swift | 229 ------------ .../StoreKit2TranslatorTests.swift | 82 ----- .../in_app_purchase_storekit_platform.dart | 69 +--- .../lib/src/sk2_pigeon.g.dart | 337 +----------------- .../sk2_product_wrapper.dart | 59 --- .../sk2_transaction_wrapper.dart | 136 ------- .../src/types/app_store_purchase_details.dart | 15 - .../lib/store_kit_2_wrappers.dart | 1 - .../pigeons/sk2_pigeon.dart | 62 +--- .../in_app_purchase_storekit/pubspec.yaml | 2 +- .../test/fakes/fake_storekit_platform.dart | 53 --- ...app_purchase_storekit_2_platform_test.dart | 81 ----- .../test/sk2_test_api.g.dart | 198 +--------- 25 files changed, 251 insertions(+), 1880 deletions(-) mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart diff --git a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md index 1fb5e1bb97d8..f072c0927709 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,7 +1,3 @@ -## 0.3.18+2 - -* Adds support for StoreKit2's `purchase` and `transactions` - ## 0.3.18+1 * Adds support for StoreKit2's `canMakePayments` and `products` diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index 43d500a8e3df..a591739eb693 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -27,21 +27,6 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This property is optional, as it requires self to exist to be initialized. public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? - // This should be an Task, but Task is on available >= iOS 13 - private var _updateListenerTask: Any? - - @available(iOS 13.0, *) - var updateListenerTask: Task<(), Never> { - return self._updateListenerTask as! Task<(), Never> - } - - @available(iOS 13.0, *) - func setListenerTaskAsTask(task: Task<(), Never>) { - self._updateListenerTask = task - } - - var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil - public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) let messenger = registrar.messenger() @@ -108,7 +93,6 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { let messenger = registrar.messenger #endif setupTransactionObserverChannelIfNeeded(withMessenger: messenger) - self.transactionCallbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: messenger) } // MARK: - Pigeon Functions diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift index cd8e96b5d135..7ab0405ae674 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift @@ -4,17 +4,16 @@ @available(iOS 15.0, macOS 12.0, *) extension InAppPurchasePlugin: InAppPurchase2API { - // MARK: - Pigeon Functions - /// Wrapper method around StoreKit2's canMakePayments() method - /// https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments + // Wrapper method around StoreKit2's canMakePayments() method + // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments func canMakePayments() throws -> Bool { return AppStore.canMakePayments } - /// Wrapper method around StoreKit2's products() method - /// https://developer.apple.com/documentation/storekit/product/3851116-products + // Wrapper method around StoreKit2's products() method + // https://developer.apple.com/documentation/storekit/product/3851116-products func products( identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void ) { @@ -35,146 +34,4 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } } - - /// Gets the appropriate product, then calls purchase on it. - /// https://developer.apple.com/documentation/storekit/product/3791971-purchase - func purchase( - id: String, options: SK2ProductPurchaseOptionsMessage?, - completion: @escaping (Result) -> Void - ) { - Task { @MainActor in - do { - guard let product = try await Product.products(for: [id]).first else { - let error = PigeonError( - code: "storekit2_failed_to_fetch_product", - message: "Storekit has failed to fetch this product.", - details: "Product ID : \(id)") - return completion(.failure(error)) - } - - let result = try await product.purchase(options: []) - - switch result { - case .success(let verification): - switch verification { - case .verified(let transaction): - self.sendTransactionUpdate(transaction: transaction) - completion(.success(result.convertToPigeon())) - case .unverified(_, let error): - completion(.failure(error)) - } - case .pending: - completion( - .failure( - PigeonError( - code: "storekit2_purchase_pending", - message: - "This transaction is still pending and but may complete in the future. If it completes, it will be delivered via `purchaseStream`", - details: "Product ID : \(id)"))) - case .userCancelled: - completion( - .failure( - PigeonError( - code: "storekit2_purchase_cancelled", - message: "This transaction has been cancelled by the user.", - details: "Product ID : \(id)"))) - @unknown default: - fatalError("An unknown StoreKit PurchaseResult has been encountered.") - } - } catch { - completion(.failure(error)) - } - } - } - - /// Wrapper method around StoreKit2's transactions() method - /// https://developer.apple.com/documentation/storekit/product/3851116-products - func transactions( - completion: @escaping (Result<[SK2TransactionMessage], Error>) -> Void - ) { - Task { - @MainActor in - do { - let transactionsMsgs = await rawTransactions().map { - $0.convertToPigeon() - } - completion(.success(transactionsMsgs)) - } - } - } - - /// Wrapper method around StoreKit2's finish() method https://developer.apple.com/documentation/storekit/transaction/3749694-finish - func finish(id: Int64, completion: @escaping (Result) -> Void) { - Task { - let transaction = try await fetchTransaction(by: UInt64(id)) - if let transaction = transaction { - await transaction.finish() - } - } - } - - /// This Task listens to Transation.updates as shown here - /// https://developer.apple.com/documentation/storekit/transaction/3851206-updates - /// This function should be called as soon as the app starts to avoid missing any Transactions done outside of the app. - func startListeningToTransactions() throws { - self.setListenerTaskAsTask( - task: Task { [weak self] in - for await verificationResult in Transaction.updates { - switch verificationResult { - case .verified(let transaction): - self?.sendTransactionUpdate(transaction: transaction) - case .unverified: - break - } - } - }) - } - - /// Stop subscribing to Transaction.updates - func stopListeningToTransactions() throws { - updateListenerTask.cancel() - } - - /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` - func sendTransactionUpdate(transaction: Transaction) { - let transactionMessage = transaction.convertToPigeon() - transactionCallbackAPI?.onTransactionsUpdated(newTransaction: transactionMessage) { result in - switch result { - case .success: break - case .failure(let error): - print("Failed to send transaction updates: \(error)") - } - } - } - - // MARK: - Convenience Functions - - /// Helper function that fetches and unwraps all verified transactions - private func rawTransactions() async -> [Transaction] { - var transactions: [Transaction] = [] - for await verificationResult in Transaction.all { - switch verificationResult { - case .verified(let transaction): - transactions.append(transaction) - case .unverified: - break - } - } - return transactions - } - - /// Helper function to fetch specific transaction - private func fetchTransaction(by id: UInt64) async throws -> Transaction? { - for await result in Transaction.all { - switch result { - case .verified(let transaction): - if transaction.id == id { - return transaction - } - case .unverified: - continue - } - } - return nil - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift index b04caa25ae64..aed0059733e0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift @@ -167,38 +167,3 @@ extension SK2PriceLocaleMessage: Equatable { return lhs.currencyCode == rhs.currencyCode && lhs.currencySymbol == rhs.currencySymbol } } - -@available(iOS 15.0, macOS 12.0, *) -extension Product.PurchaseResult { - func convertToPigeon() -> SK2ProductPurchaseResultMessage { - switch self { - case .success(_): - return SK2ProductPurchaseResultMessage.success - case .userCancelled: - return SK2ProductPurchaseResultMessage.userCancelled - case .pending: - return SK2ProductPurchaseResultMessage.pending - @unknown default: - fatalError() - } - } -} - -@available(iOS 15.0, macOS 12.0, *) -extension Transaction { - func convertToPigeon(restoring: Bool = false) -> SK2TransactionMessage { - - let dateFromatter: DateFormatter = DateFormatter() - dateFromatter.dateFormat = "yyyy-MM-dd HH:mm:ss" - - return SK2TransactionMessage( - id: Int64(id), - originalId: Int64(originalID), - productId: productID, - purchaseDate: dateFromatter.string(from: purchaseDate), - purchasedQuantity: Int64(purchasedQuantity), - appAccountToken: appAccountToken?.uuidString, - restoring: restoring - ) - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift index 6a327812444d..119fb288f364 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift @@ -58,12 +58,6 @@ private func wrapError(_ error: Any) -> [Any?] { ] } -private func createConnectionError(withChannelName channelName: String) -> PigeonError { - return PigeonError( - code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", - details: "") -} - private func isNullish(_ value: Any?) -> Bool { return value is NSNull || value == nil } @@ -102,12 +96,6 @@ enum SK2SubscriptionPeriodUnitMessage: Int { case year = 3 } -enum SK2ProductPurchaseResultMessage: Int { - case success = 0 - case userCancelled = 1 - case pending = 2 -} - /// Generated class from Pigeon that represents data sent in messages. struct SK2SubscriptionOfferMessage { var id: String? = nil @@ -287,111 +275,6 @@ struct SK2PriceLocaleMessage { } } -/// Generated class from Pigeon that represents data sent in messages. -struct SK2ProductPurchaseOptionsMessage { - var appAccountToken: String? = nil - var quantity: Int64? = nil - - // swift-format-ignore: AlwaysUseLowerCamelCase - static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductPurchaseOptionsMessage? { - let appAccountToken: String? = nilOrValue(pigeonVar_list[0]) - let quantity: Int64? = - isNullish(pigeonVar_list[1]) - ? nil - : (pigeonVar_list[1] is Int64? - ? pigeonVar_list[1] as! Int64? : Int64(pigeonVar_list[1] as! Int32)) - - return SK2ProductPurchaseOptionsMessage( - appAccountToken: appAccountToken, - quantity: quantity - ) - } - func toList() -> [Any?] { - return [ - appAccountToken, - quantity, - ] - } -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2TransactionMessage { - var id: Int64 - var originalId: Int64 - var productId: String - var purchaseDate: String - var purchasedQuantity: Int64 - var appAccountToken: String? = nil - var restoring: Bool - var error: SK2ErrorMessage? = nil - - // swift-format-ignore: AlwaysUseLowerCamelCase - static func fromList(_ pigeonVar_list: [Any?]) -> SK2TransactionMessage? { - let id = - pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) - let originalId = - pigeonVar_list[1] is Int64 ? pigeonVar_list[1] as! Int64 : Int64(pigeonVar_list[1] as! Int32) - let productId = pigeonVar_list[2] as! String - let purchaseDate = pigeonVar_list[3] as! String - let purchasedQuantity = - pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) - let appAccountToken: String? = nilOrValue(pigeonVar_list[5]) - let restoring = pigeonVar_list[6] as! Bool - let error: SK2ErrorMessage? = nilOrValue(pigeonVar_list[7]) - - return SK2TransactionMessage( - id: id, - originalId: originalId, - productId: productId, - purchaseDate: purchaseDate, - purchasedQuantity: purchasedQuantity, - appAccountToken: appAccountToken, - restoring: restoring, - error: error - ) - } - func toList() -> [Any?] { - return [ - id, - originalId, - productId, - purchaseDate, - purchasedQuantity, - appAccountToken, - restoring, - error, - ] - } -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2ErrorMessage { - var code: Int64 - var domain: String - var userInfo: [String?: Any?]? = nil - - // swift-format-ignore: AlwaysUseLowerCamelCase - static func fromList(_ pigeonVar_list: [Any?]) -> SK2ErrorMessage? { - let code = - pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) - let domain = pigeonVar_list[1] as! String - let userInfo: [String?: Any?]? = nilOrValue(pigeonVar_list[2]) - - return SK2ErrorMessage( - code: code, - domain: domain, - userInfo: userInfo - ) - } - func toList() -> [Any?] { - return [ - code, - domain, - userInfo, - ] - } -} - private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -420,27 +303,15 @@ private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { } return nil case 133: - let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) - if let enumResultAsInt = enumResultAsInt { - return SK2ProductPurchaseResultMessage(rawValue: enumResultAsInt) - } - return nil - case 134: return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) - case 135: + case 134: return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) - case 136: + case 135: return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) - case 137: + case 136: return SK2ProductMessage.fromList(self.readValue() as! [Any?]) - case 138: + case 137: return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) - case 139: - return SK2ProductPurchaseOptionsMessage.fromList(self.readValue() as! [Any?]) - case 140: - return SK2TransactionMessage.fromList(self.readValue() as! [Any?]) - case 141: - return SK2ErrorMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -461,32 +332,20 @@ private class sk2_pigeonPigeonCodecWriter: FlutterStandardWriter { } else if let value = value as? SK2SubscriptionPeriodUnitMessage { super.writeByte(132) super.writeValue(value.rawValue) - } else if let value = value as? SK2ProductPurchaseResultMessage { - super.writeByte(133) - super.writeValue(value.rawValue) } else if let value = value as? SK2SubscriptionOfferMessage { - super.writeByte(134) + super.writeByte(133) super.writeValue(value.toList()) } else if let value = value as? SK2SubscriptionPeriodMessage { - super.writeByte(135) + super.writeByte(134) super.writeValue(value.toList()) } else if let value = value as? SK2SubscriptionInfoMessage { - super.writeByte(136) + super.writeByte(135) super.writeValue(value.toList()) } else if let value = value as? SK2ProductMessage { - super.writeByte(137) + super.writeByte(136) super.writeValue(value.toList()) } else if let value = value as? SK2PriceLocaleMessage { - super.writeByte(138) - super.writeValue(value.toList()) - } else if let value = value as? SK2ProductPurchaseOptionsMessage { - super.writeByte(139) - super.writeValue(value.toList()) - } else if let value = value as? SK2TransactionMessage { - super.writeByte(140) - super.writeValue(value.toList()) - } else if let value = value as? SK2ErrorMessage { - super.writeByte(141) + super.writeByte(137) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -513,13 +372,6 @@ protocol InAppPurchase2API { func canMakePayments() throws -> Bool func products( identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void) - func purchase( - id: String, options: SK2ProductPurchaseOptionsMessage?, - completion: @escaping (Result) -> Void) - func transactions(completion: @escaping (Result<[SK2TransactionMessage], Error>) -> Void) - func finish(id: Int64, completion: @escaping (Result) -> Void) - func startListeningToTransactions() throws - func stopListeningToTransactions() throws } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -567,135 +419,5 @@ class InAppPurchase2APISetup { } else { productsChannel.setMessageHandler(nil) } - let purchaseChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - purchaseChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let idArg = args[0] as! String - let optionsArg: SK2ProductPurchaseOptionsMessage? = nilOrValue(args[1]) - api.purchase(id: idArg, options: optionsArg) { result in - switch result { - case .success(let res): - reply(wrapResult(res)) - case .failure(let error): - reply(wrapError(error)) - } - } - } - } else { - purchaseChannel.setMessageHandler(nil) - } - let transactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - transactionsChannel.setMessageHandler { _, reply in - api.transactions { result in - switch result { - case .success(let res): - reply(wrapResult(res)) - case .failure(let error): - reply(wrapError(error)) - } - } - } - } else { - transactionsChannel.setMessageHandler(nil) - } - let finishChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - finishChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let idArg = args[0] is Int64 ? args[0] as! Int64 : Int64(args[0] as! Int32) - api.finish(id: idArg) { result in - switch result { - case .success: - reply(wrapResult(nil)) - case .failure(let error): - reply(wrapError(error)) - } - } - } - } else { - finishChannel.setMessageHandler(nil) - } - let startListeningToTransactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - startListeningToTransactionsChannel.setMessageHandler { _, reply in - do { - try api.startListeningToTransactions() - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - startListeningToTransactionsChannel.setMessageHandler(nil) - } - let stopListeningToTransactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - stopListeningToTransactionsChannel.setMessageHandler { _, reply in - do { - try api.stopListeningToTransactions() - reply(wrapResult(nil)) - } catch { - reply(wrapError(error)) - } - } - } else { - stopListeningToTransactionsChannel.setMessageHandler(nil) - } - } -} -/// Generated protocol from Pigeon that represents Flutter messages that can be called from Swift. -protocol InAppPurchase2CallbackAPIProtocol { - func onTransactionsUpdated( - newTransaction newTransactionArg: SK2TransactionMessage, - completion: @escaping (Result) -> Void) -} -class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { - private let binaryMessenger: FlutterBinaryMessenger - private let messageChannelSuffix: String - init(binaryMessenger: FlutterBinaryMessenger, messageChannelSuffix: String = "") { - self.binaryMessenger = binaryMessenger - self.messageChannelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - } - var codec: sk2_pigeonPigeonCodec { - return sk2_pigeonPigeonCodec.shared - } - func onTransactionsUpdated( - newTransaction newTransactionArg: SK2TransactionMessage, - completion: @escaping (Result) -> Void - ) { - let channelName: String = - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated\(messageChannelSuffix)" - let channel = FlutterBasicMessageChannel( - name: channelName, binaryMessenger: binaryMessenger, codec: codec) - channel.sendMessage([newTransactionArg] as [Any?]) { response in - guard let listResponse = response as? [Any?] else { - completion(.failure(createConnectionError(withChannelName: channelName))) - return - } - if listResponse.count > 1 { - let code: String = listResponse[0] as! String - let message: String? = nilOrValue(listResponse[1]) - let details: String? = nilOrValue(listResponse[2]) - completion(.failure(PigeonError(code: code, message: message, details: details))) - } else { - completion(.success(Void())) - } - } } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj index 9d01ee6865fe..caf8c62f6e80 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -18,14 +18,14 @@ C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */; }; E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */; }; F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */; }; - F22FD7A22CB080AE0006F28F /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22FD7A12CB080AE0006F28F /* StoreKit2TranslatorTests.swift */; }; F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E12C409D41000C6C72 /* InAppPurchasePluginTests.swift */; }; F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F276940A2C47268700277144 /* ProductRequestHandlerTests.swift */; }; F27694112C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694102C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694172C49DBCA00277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */; }; + F2858EE72C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2858EE62C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F2858EE82C76A4230063A092 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F6E5D5F926131C4800C68BED /* Configuration.storekit */; }; + F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */; }; F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD392C1256DD0067C78A /* Stubs.m */; }; - F2D127492CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D127482CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F2D5271A2C50627500C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527192C50627500C137C7 /* PaymentQueueTests.swift */; }; F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527292C583C4A00C137C7 /* TranslatorTests.swift */; }; /* End PBXBuildFile section */ @@ -78,14 +78,14 @@ CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStubs.swift; sourceTree = ""; }; - F22FD7A12CB080AE0006F28F /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = SOURCE_ROOT; }; F24C45E12C409D41000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F276940A2C47268700277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694102C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; + F2858EE62C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TranslatorTests.swift; sourceTree = ""; }; F295AD362C1251300067C78A /* Stubs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Stubs.h; path = ../../shared/RunnerTests/Stubs.h; sourceTree = ""; }; F295AD392C1256DD0067C78A /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; - F2D127482CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = SOURCE_ROOT; }; F2D527192C50627500C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; F2D527292C583C4A00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; @@ -191,8 +191,6 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( - F2D127482CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift */, - F22FD7A12CB080AE0006F28F /* StoreKit2TranslatorTests.swift */, F2D527292C583C4A00C137C7 /* TranslatorTests.swift */, F2D527192C50627500C137C7 /* PaymentQueueTests.swift */, F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */, @@ -203,6 +201,8 @@ F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, + F2858EE62C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift */, + F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -444,12 +444,12 @@ F2D5271A2C50627500C137C7 /* PaymentQueueTests.swift in Sources */, F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, - F22FD7A22CB080AE0006F28F /* StoreKit2TranslatorTests.swift in Sources */, + F2858EE72C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, + F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */, F27694172C49DBCA00277144 /* FIATransactionCacheTests.swift in Sources */, - F2D127492CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F27694112C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 9c3ad5d54dda..477131feb699 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -39,7 +39,6 @@ skipped = "NO"> + + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift deleted file mode 100644 index c2fcfbdfedf0..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import StoreKitTest -import XCTest - -@testable import in_app_purchase_storekit - -@available(iOS 15.0, macOS 12.0, *) - -final class InAppPurchase2PluginTests: XCTestCase { - private var session: SKTestSession! - private var plugin: InAppPurchasePlugin! - - override func setUp() async throws { - try await super.setUp() - - session = try! SKTestSession(configurationFileNamed: "Configuration") - session.resetToDefaultState() - session.clearTransactions() - session.disableDialogs = true - - plugin = InAppPurchasePluginStub(receiptManager: FIAPReceiptManagerStub()) { request in - DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request)) - } - try plugin.startListeningToTransactions() - } - - override func tearDown() async throws { - self.session.clearTransactions() - session.disableDialogs = false - } - - func testCanMakePayments() throws { - let result = try plugin.canMakePayments() - XCTAssertTrue(result) - } - - func testGetProducts() async throws { - let expectation = self.expectation(description: "products successfully fetched") - - var fetchedProductMsg: SK2ProductMessage? - plugin.products(identifiers: ["subscription_silver"]) { result in - switch result { - case .success(let productMessages): - fetchedProductMsg = productMessages.first - expectation.fulfill() - case .failure(let error): - print("Failed to fetch products: \(error.localizedDescription)") - } - } - await fulfillment(of: [expectation], timeout: 5) - - let testProduct = try await Product.products(for: ["subscription_silver"]).first - - let testProductMsg = testProduct?.convertToPigeon - - XCTAssertNotNil(fetchedProductMsg) - XCTAssertEqual(testProductMsg, fetchedProductMsg) - } - - func testGetDiscountedProducts() async throws { - let expectation = self.expectation(description: "products successfully fetched") - - var fetchedProductMsg: SK2ProductMessage? - plugin.products(identifiers: ["subscription_silver"]) { result in - switch result { - case .success(let productMessages): - fetchedProductMsg = productMessages.first - expectation.fulfill() - case .failure(let error): print("Failed to fetch products: \(error.localizedDescription)") - } - } - await fulfillment(of: [expectation], timeout: 5) - - let testProduct = try await Product.products(for: ["subscription_silver"]).first - - let testProductMsg = testProduct?.convertToPigeon - - XCTAssertNotNil(fetchedProductMsg) - XCTAssertEqual(testProductMsg, fetchedProductMsg) - } - - func testGetInvalidProducts() async throws { - let expectation = self.expectation(description: "products successfully fetched") - - var fetchedProductMsg: [SK2ProductMessage]? - plugin.products(identifiers: ["invalid_product"]) { result in - switch result { - case .success(let productMessages): - fetchedProductMsg = productMessages - expectation.fulfill() - case .failure(_): - XCTFail("Products should be successfully fetched") - } - } - await fulfillment(of: [expectation], timeout: 5) - - XCTAssert(fetchedProductMsg?.count == 0) - } - - //TODO(louisehsu): Add testing for lower versions. - @available(iOS 17.0, macOS 14.0, *) - func testGetProductsWithStoreKitError() async throws { - try await session.setSimulatedError( - .generic(.networkError(URLError(.badURL))), forAPI: .loadProducts) - - let expectation = self.expectation(description: "products request should fail") - - plugin.products(identifiers: ["subscription_silver"]) { result in - switch result { - case .success(_): - XCTFail("This `products` call should not succeed") - case .failure(let error): - expectation.fulfill() - XCTAssert( - error.localizedDescription - == "The operation couldn’t be completed. (in_app_purchase_storekit.PigeonError error 1.)" - ) - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - func testSuccessfulPurchase() async throws { - let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase(id: "consumable", options: nil) { result in - switch result { - case .success(let purchaseResult): - expectation.fulfill() - case .failure(let error): - XCTFail("Purchase should NOT fail. Failed with \(error)") - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - @available(iOS 17.0, macOS 14.0, *) - func testFailedNetworkErrorPurchase() async throws { - try await session.setSimulatedError( - .generic(.networkError(URLError(.badURL))), forAPI: .loadProducts) - let expectation = self.expectation(description: "products request should fail") - plugin.purchase(id: "consumable", options: nil) { result in - switch result { - case .success(_): - XCTFail("Purchase should NOT suceed.") - case .failure(let error): - XCTAssertEqual( - error.localizedDescription, - "The operation couldn’t be completed. (NSURLErrorDomain error -1009.)") - expectation.fulfill() - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - @available(iOS 17.0, macOS 14.0, *) - func testFailedProductUnavilablePurchase() async throws { - try await session.setSimulatedError( - .purchase(.productUnavailable), forAPI: .purchase) - let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase(id: "consumable", options: nil) { result in - switch result { - case .success(_): - XCTFail("Purchase should NOT suceed.") - case .failure(let error): - XCTAssertEqual(error.localizedDescription, "Item Unavailable") - expectation.fulfill() - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - func testInvalidProductPurchase() async throws { - let expectation = self.expectation(description: "products request should fail") - plugin.purchase(id: "invalid_product", options: nil) { result in - switch result { - case .success(_): - XCTFail("Purchase should NOT suceed.") - case .failure(let error): - let pigeonError = error as! PigeonError - - XCTAssertEqual(pigeonError.code, "storekit2_failed_to_fetch_product") - expectation.fulfill() - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - func testPurchaseUpgradeConsumableSuccess() async throws { - let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase(id: "subscription_discounted", options: nil) { result in - switch result { - case .success(let purchaseResult): - expectation.fulfill() - case .failure(let error): - XCTFail("Purchase should NOT fail. Failed with \(error)") - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - func testDiscountedSubscriptionSuccess() async throws { - let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase(id: "subscription_discounted", options: nil) { result in - switch result { - case .success(let purchaseResult): - expectation.fulfill() - case .failure(let error): - XCTFail("Purchase should NOT fail. Failed with \(error)") - } - } - await fulfillment(of: [expectation], timeout: 5) - } - - func testDiscountedProductSuccess() async throws { - let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase(id: "consumable_discounted", options: nil) { result in - switch result { - case .success(let purchaseResult): - expectation.fulfill() - case .failure(let error): - XCTFail("Purchase should NOT fail. Failed with \(error)") - } - } - await fulfillment(of: [expectation], timeout: 5) - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift deleted file mode 100644 index 6fbd1f8444f0..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import Foundation -import StoreKitTest -import XCTest - -@testable import in_app_purchase_storekit - -@available(iOS 15.0, macOS 12.0, *) -final class StoreKit2TranslatorTests: XCTestCase { - private var session: SKTestSession! - private var plugin: InAppPurchasePlugin! - private var product: Product! - - // This is transcribed from the Configuration.storekit file. - var productMessage: SK2ProductMessage = - SK2ProductMessage( - id: "subscription_silver", - displayName: "Subscription Silver", - description: "A lower level subscription.", - price: 4.99, - displayPrice: "$4.99", - type: SK2ProductTypeMessage.autoRenewable, - subscription: SK2SubscriptionInfoMessage( - promotionalOffers: [], - subscriptionGroupID: "D0FEE8D8", - subscriptionPeriod: SK2SubscriptionPeriodMessage( - value: 1, - unit: SK2SubscriptionPeriodUnitMessage.week)), - priceLocale: SK2PriceLocaleMessage(currencyCode: "USD", currencySymbol: "$")) - - override func setUp() async throws { - try await super.setUp() - - self.session = try! SKTestSession(configurationFileNamed: "Configuration") - self.session.clearTransactions() - let receiptManagerStub = FIAPReceiptManagerStub() - plugin = InAppPurchasePluginStub(receiptManager: receiptManagerStub) { request in - DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request)) - } - product = try await Product.products(for: ["subscription_silver"]).first! - - } - - func testPigeonConversionForProduct() async throws { - XCTAssertNotNil(product) - let pigeonMessage = product.convertToPigeon - XCTAssertEqual(pigeonMessage, productMessage) - } - - func testPigeonConversionForSubscriptionInfo() async throws { - guard let subscription = product.subscription else { - XCTFail("SubscriptionInfo should not be nil") - return - } - let pigeonMessage = subscription.convertToPigeon - XCTAssertEqual(pigeonMessage, productMessage.subscription) - } - - func testPigeonConversionForProductType() async throws { - let type = product.type - let pigeonMessage = type.convertToPigeon - XCTAssertEqual(pigeonMessage, productMessage.type) - } - - func testPigeonConversionForSubscriptionPeriod() async throws { - guard let period = product.subscription?.subscriptionPeriod else { - XCTFail("SubscriptionPeriod should not be nil") - return - } - let pigeonMessage = period.convertToPigeon - XCTAssertEqual(pigeonMessage, productMessage.subscription?.subscriptionPeriod) - } - - func testPigeonConversionForPriceLocale() async throws { - let locale = product.priceFormatStyle.locale - let pigeonMessage = locale.convertToPigeon - XCTAssertEqual(pigeonMessage, productMessage.priceLocale) - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart index f930e57a8e7a..4bbd863b8447 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -33,26 +33,16 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Experimental flag for StoreKit2. static bool _useStoreKit2 = false; - /// StoreKit1 static late SKPaymentQueueWrapper _skPaymentQueueWrapper; - static late _TransactionObserver _sk1transactionObserver; - - /// StoreKit2 - static late SK2TransactionObserverWrapper _sk2transactionObserver; + static late _TransactionObserver _observer; @override - Stream> get purchaseStream => _useStoreKit2 - ? _sk2transactionObserver.transactionsCreatedController.stream - : _sk1transactionObserver.purchaseUpdatedController.stream; + Stream> get purchaseStream => + _observer.purchaseUpdatedController.stream; /// Callback handler for transaction status changes. @visibleForTesting - static SKTransactionObserverWrapper get observer => _sk1transactionObserver; - - /// Callback handler for transaction status changes for StoreKit2 transactions - @visibleForTesting - static SK2TransactionObserverWrapper get sk2transactionObserver => - _sk2transactionObserver; + static SKTransactionObserverWrapper get observer => _observer; /// Registers this class as the default instance of [InAppPurchasePlatform]. static void registerPlatform() { @@ -67,26 +57,15 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { _skPaymentQueueWrapper = SKPaymentQueueWrapper(); - if (_useStoreKit2) { - final StreamController> updateController2 = - StreamController>.broadcast( - onListen: () => SK2Transaction.startListeningToTransactions(), - onCancel: () => SK2Transaction.stopListeningToTransactions(), - ); - _sk2transactionObserver = SK2TransactionObserverWrapper( - transactionsCreatedController: updateController2); - InAppPurchase2CallbackAPI.setUp(_sk2transactionObserver); - } else { - // Create a purchaseUpdatedController and notify the native side when to - // start of stop sending updates. - final StreamController> updateController = - StreamController>.broadcast( - onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(), - onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(), - ); - _sk1transactionObserver = _TransactionObserver(updateController); - _skPaymentQueueWrapper.setTransactionObserver(observer); - } + // Create a purchaseUpdatedController and notify the native side when to + // start of stop sending updates. + final StreamController> updateController = + StreamController>.broadcast( + onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(), + onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(), + ); + _observer = _TransactionObserver(updateController); + _skPaymentQueueWrapper.setTransactionObserver(observer); } @override @@ -99,17 +78,6 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { - if (_useStoreKit2) { - final SK2ProductPurchaseOptions options = SK2ProductPurchaseOptions( - quantity: purchaseParam is AppStorePurchaseParam - ? purchaseParam.quantity - : 1, - appAccountToken: purchaseParam.applicationUserName); - await SK2Product.purchase(purchaseParam.productDetails.id, - options: options); - - return true; - } await _skPaymentQueueWrapper.addPayment(SKPaymentWrapper( productIdentifier: purchaseParam.productDetails.id, quantity: @@ -134,14 +102,10 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future completePurchase(PurchaseDetails purchase) { assert( - purchase is AppStorePurchaseDetails || purchase is SK2PurchaseDetails, + purchase is AppStorePurchaseDetails, 'On iOS, the `purchase` should always be of type `AppStorePurchaseDetails`.', ); - if (_useStoreKit2) { - return SK2Transaction.finish(int.parse(purchase.purchaseID!)); - } - return _skPaymentQueueWrapper.finishTransaction( (purchase as AppStorePurchaseDetails).skPaymentTransaction, ); @@ -149,12 +113,11 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future restorePurchases({String? applicationUserName}) async { - return _sk1transactionObserver + return _observer .restoreTransactions( queue: _skPaymentQueueWrapper, applicationUserName: applicationUserName) - .whenComplete( - () => _sk1transactionObserver.cleanUpRestoredTransactions()); + .whenComplete(() => _observer.cleanUpRestoredTransactions()); } /// Query the product detail list. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart index 1dcc2cad7717..d45d16074ef3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart @@ -61,12 +61,6 @@ enum SK2SubscriptionPeriodUnitMessage { year, } -enum SK2ProductPurchaseResultMessage { - success, - userCancelled, - pending, -} - class SK2SubscriptionOfferMessage { SK2SubscriptionOfferMessage({ this.id, @@ -270,119 +264,6 @@ class SK2PriceLocaleMessage { } } -class SK2ProductPurchaseOptionsMessage { - SK2ProductPurchaseOptionsMessage({ - this.appAccountToken, - this.quantity = 1, - }); - - String? appAccountToken; - - int? quantity; - - Object encode() { - return [ - appAccountToken, - quantity, - ]; - } - - static SK2ProductPurchaseOptionsMessage decode(Object result) { - result as List; - return SK2ProductPurchaseOptionsMessage( - appAccountToken: result[0] as String?, - quantity: result[1] as int?, - ); - } -} - -class SK2TransactionMessage { - SK2TransactionMessage({ - required this.id, - required this.originalId, - required this.productId, - required this.purchaseDate, - this.purchasedQuantity = 1, - this.appAccountToken, - this.restoring = false, - this.error, - }); - - int id; - - int originalId; - - String productId; - - String purchaseDate; - - int purchasedQuantity; - - String? appAccountToken; - - bool restoring; - - SK2ErrorMessage? error; - - Object encode() { - return [ - id, - originalId, - productId, - purchaseDate, - purchasedQuantity, - appAccountToken, - restoring, - error, - ]; - } - - static SK2TransactionMessage decode(Object result) { - result as List; - return SK2TransactionMessage( - id: result[0]! as int, - originalId: result[1]! as int, - productId: result[2]! as String, - purchaseDate: result[3]! as String, - purchasedQuantity: result[4]! as int, - appAccountToken: result[5] as String?, - restoring: result[6]! as bool, - error: result[7] as SK2ErrorMessage?, - ); - } -} - -class SK2ErrorMessage { - SK2ErrorMessage({ - required this.code, - required this.domain, - this.userInfo, - }); - - int code; - - String domain; - - Map? userInfo; - - Object encode() { - return [ - code, - domain, - userInfo, - ]; - } - - static SK2ErrorMessage decode(Object result) { - result as List; - return SK2ErrorMessage( - code: result[0]! as int, - domain: result[1]! as String, - userInfo: (result[2] as Map?)?.cast(), - ); - } -} - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -402,32 +283,20 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { - buffer.putUint8(133); - writeValue(buffer, value.index); } else if (value is SK2SubscriptionOfferMessage) { - buffer.putUint8(134); + buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionPeriodMessage) { - buffer.putUint8(135); + buffer.putUint8(134); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionInfoMessage) { - buffer.putUint8(136); + buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is SK2ProductMessage) { - buffer.putUint8(137); + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is SK2PriceLocaleMessage) { - buffer.putUint8(138); - writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { - buffer.putUint8(139); - writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { - buffer.putUint8(140); - writeValue(buffer, value.encode()); - } else if (value is SK2ErrorMessage) { - buffer.putUint8(141); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -456,26 +325,15 @@ class _PigeonCodec extends StandardMessageCodec { ? null : SK2SubscriptionPeriodUnitMessage.values[value]; case 133: - final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2ProductPurchaseResultMessage.values[value]; - case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 134: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 135: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 136: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 137: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: - return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: - return SK2TransactionMessage.decode(readValue(buffer)!); - case 141: - return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -555,181 +413,4 @@ class InAppPurchase2API { .cast(); } } - - Future purchase(String id, - {SK2ProductPurchaseOptionsMessage? options}) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([id, options]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as SK2ProductPurchaseResultMessage?)!; - } - } - - Future> transactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else if (pigeonVar_replyList[0] == null) { - throw PlatformException( - code: 'null-error', - message: 'Host platform returned null value for non-null return value.', - ); - } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); - } - } - - Future finish(int id) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send([id]) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future startListeningToTransactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } - - Future stopListeningToTransactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( - pigeonVar_channelName, - pigeonChannelCodec, - binaryMessenger: pigeonVar_binaryMessenger, - ); - final List? pigeonVar_replyList = - await pigeonVar_channel.send(null) as List?; - if (pigeonVar_replyList == null) { - throw _createConnectionError(pigeonVar_channelName); - } else if (pigeonVar_replyList.length > 1) { - throw PlatformException( - code: pigeonVar_replyList[0]! as String, - message: pigeonVar_replyList[1] as String?, - details: pigeonVar_replyList[2], - ); - } else { - return; - } - } -} - -abstract class InAppPurchase2CallbackAPI { - static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - - void onTransactionsUpdated(SK2TransactionMessage newTransaction); - - static void setUp( - InAppPurchase2CallbackAPI? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - pigeonVar_channel.setMessageHandler(null); - } else { - pigeonVar_channel.setMessageHandler((Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated was null.'); - final List args = (message as List?)!; - final SK2TransactionMessage? arg_newTransaction = - (args[0] as SK2TransactionMessage?); - assert(arg_newTransaction != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated was null, expected non-null SK2TransactionMessage.'); - try { - api.onTransactionsUpdated(arg_newTransaction!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart index 820ea211d7b2..9decfa433c9b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart @@ -264,51 +264,6 @@ extension on SK2PriceLocaleMessage { } } -/// Wrapper around [PurchaseResult] -/// https://developer.apple.com/documentation/storekit/product/purchaseresult -enum SK2ProductPurchaseResult { - /// The purchase succeeded and results in a transaction. - success, - - /// The user canceled the purchase. - userCancelled, - - /// The purchase is pending, and requires action from the customer. - pending -} - -/// Wrapper around [PurchaseOption] -/// https://developer.apple.com/documentation/storekit/product/purchaseoption -class SK2ProductPurchaseOptions { - /// Creates a new instance of [SK2ProductPurchaseOptions] - SK2ProductPurchaseOptions({this.appAccountToken, this.quantity}); - - /// Sets a UUID to associate the purchase with an account in your system. - final String? appAccountToken; - - /// Indicates the quantity of items the customer is purchasing. - final int? quantity; - - /// Convert to pigeon representation [SK2ProductPurchaseOptionsMessage] - SK2ProductPurchaseOptionsMessage convertToPigeon() { - return SK2ProductPurchaseOptionsMessage( - appAccountToken: appAccountToken, quantity: quantity); - } -} - -extension on SK2ProductPurchaseResultMessage { - SK2ProductPurchaseResult convertFromPigeon() { - switch (this) { - case SK2ProductPurchaseResultMessage.success: - return SK2ProductPurchaseResult.success; - case SK2ProductPurchaseResultMessage.userCancelled: - return SK2ProductPurchaseResult.userCancelled; - case SK2ProductPurchaseResultMessage.pending: - return SK2ProductPurchaseResult.pending; - } - } -} - /// A wrapper around StoreKit2's [Product](https://developer.apple.com/documentation/storekit/product). /// The Product type represents the in-app purchases that you configure in /// App Store Connect and make available for purchase within your app. @@ -369,20 +324,6 @@ class SK2Product { .toList(); } - /// Wrapper for StoreKit's [Product.purchase] - /// https://developer.apple.com/documentation/storekit/product/3791971-purchase - /// Initiates a purchase for the product with the App Store and displays the confirmation sheet. - static Future purchase(String id, - {SK2ProductPurchaseOptions? options}) async { - SK2ProductPurchaseResultMessage result; - if (options != null) { - result = await _hostApi.purchase(id, options: options.convertToPigeon()); - } else { - result = await _hostApi.purchase(id); - } - return result.convertFromPigeon(); - } - /// Converts this instance of [SK2Product] to it's pigeon representation [SK2ProductMessage] SK2ProductMessage convertToPigeon() { return SK2ProductMessage( diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart deleted file mode 100644 index c6ec3bf0d5ac..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; - -import '../../in_app_purchase_storekit.dart'; -import '../../store_kit_wrappers.dart'; -import '../sk2_pigeon.g.dart'; - -InAppPurchase2API _hostApi = InAppPurchase2API(); - -/// Dart wrapper around StoreKit2's [Transaction](https://developer.apple.com/documentation/storekit/transaction) -/// Note that in StoreKit2, a Transaction encompasses the data contained by -/// SKPayment and SKTransaction in StoreKit1 -class SK2Transaction { - /// Creates a new instance of [SK2Transaction] - SK2Transaction( - {required this.id, - required this.originalId, - required this.productId, - required this.purchaseDate, - this.quantity = 1, - required this.appAccountToken, - this.subscriptionGroupID, - this.price, - this.error}); - - /// The unique identifier for the transaction. - final String id; - - /// The original transaction identifier of a purchase. - /// The original transaction identifier, originalID, is identical to id except - /// when the user restores a purchase or renews a transaction. - final String originalId; - - /// The product identifier of the in-app purchase. - final String productId; - - /// The date that the App Store charged the user’s account for a purchased or - /// restored product, or for a subscription purchase or renewal after a lapse. - final String purchaseDate; - - /// The number of consumable products purchased. - final int quantity; - - /// A UUID that associates the transaction with a user on your own service. - final String? appAccountToken; - - /// The identifier of the subscription group that the subscription belongs to. - final String? subscriptionGroupID; - - /// The price of the in-app purchase that the system records in the transaction. - final double? price; - - /// Any error returned from StoreKit - final SKError? error; - - /// Wrapper around [Transaction.finish] - /// https://developer.apple.com/documentation/storekit/transaction/3749694-finish - /// Indicates to the App Store that the app delivered the purchased content - /// or enabled the service to finish the transaction. - static Future finish(int id) async { - await _hostApi.finish(id); - } - - /// A wrapper around [Transaction.all] - /// https://developer.apple.com/documentation/storekit/transaction/3851203-all - /// A sequence that emits all the customer’s transactions for your app. - static Future> transactions() async { - final List msgs = await _hostApi.transactions(); - final List transactions = msgs - .map((SK2TransactionMessage? e) => e?.convertFromPigeon()) - .cast() - .toList(); - return transactions; - } - - /// Start listening to transactions. - /// Call this as soon as you can your app to avoid missing transactions. - static void startListeningToTransactions() { - _hostApi.startListeningToTransactions(); - } - - /// Stop listening to transactions. - static void stopListeningToTransactions() { - _hostApi.stopListeningToTransactions(); - } -} - -extension on SK2TransactionMessage { - SK2Transaction convertFromPigeon() { - return SK2Transaction( - id: id.toString(), - originalId: originalId.toString(), - productId: productId, - purchaseDate: purchaseDate, - appAccountToken: appAccountToken); - } - - PurchaseDetails convertToDetails() { - return SK2PurchaseDetails( - productID: productId, - // in SK2, as per Apple - // https://developer.apple.com/documentation/foundation/nsbundle/1407276-appstorereceipturl - // receipt isn’t necessary with SK2 as a Transaction can only be returned - // from validated purchases. - verificationData: PurchaseVerificationData( - localVerificationData: '', serverVerificationData: '', source: ''), - transactionDate: purchaseDate, - // Note that with SK2, any transactions that *can* be returned will - // require to be finished, and are already purchased. - // So set this as purchased for all transactions initially. - // Any failed transaction will simply not be returned. - status: restoring ? PurchaseStatus.restored : PurchaseStatus.purchased, - purchaseID: id.toString(), - ); - } -} - -/// An observer that listens to all transactions created -class SK2TransactionObserverWrapper implements InAppPurchase2CallbackAPI { - /// Creates a new instance of [SK2TransactionObserverWrapper] - SK2TransactionObserverWrapper({required this.transactionsCreatedController}); - - /// The transactions stream to listen to - final StreamController> transactionsCreatedController; - - @override - void onTransactionsUpdated(SK2TransactionMessage newTransaction) { - transactionsCreatedController - .add([newTransaction.convertToDetails()]); - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart index 335ff6ad2641..21a1e11116b7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_purchase_details.dart @@ -77,18 +77,3 @@ class AppStorePurchaseDetails extends PurchaseDetails { @override bool get pendingCompletePurchase => _pendingCompletePurchase; } - -/// The class represents the information of a purchase made with the Apple -/// AppStore, when using Storekit2 -class SK2PurchaseDetails extends PurchaseDetails { - /// Creates new instance of [SK2PurchaseDetails] - SK2PurchaseDetails( - {required super.productID, - required super.purchaseID, - required super.verificationData, - required super.transactionDate, - required super.status}); - - @override - bool get pendingCompletePurchase => status == PurchaseStatus.purchased; -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart index 05482180b1a1..746cc3f378a1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart @@ -5,4 +5,3 @@ export 'src/sk2_pigeon.g.dart'; export 'src/store_kit_2_wrappers/sk2_appstore_wrapper.dart'; export 'src/store_kit_2_wrappers/sk2_product_wrapper.dart'; -export 'src/store_kit_2_wrappers/sk2_transaction_wrapper.dart'; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart index b276524a5877..941dd6dbb272 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart @@ -128,72 +128,14 @@ class SK2PriceLocaleMessage { final String currencySymbol; } -class SK2ProductPurchaseOptionsMessage { - SK2ProductPurchaseOptionsMessage({ - this.appAccountToken, - this.quantity = 1, - }); - final String? appAccountToken; - final int? quantity; -} - -class SK2TransactionMessage { - SK2TransactionMessage( - {required this.id, - required this.originalId, - required this.productId, - required this.purchaseDate, - this.purchasedQuantity = 1, - this.appAccountToken, - this.error, - this.restoring = false}); - final int id; - final int originalId; - final String productId; - final String purchaseDate; - final int purchasedQuantity; - final String? appAccountToken; - final bool restoring; - final SK2ErrorMessage? error; -} - -class SK2ErrorMessage { - const SK2ErrorMessage( - {required this.code, required this.domain, required this.userInfo}); - - final int code; - final String domain; - final Map? userInfo; -} - -enum SK2ProductPurchaseResultMessage { success, userCancelled, pending } - @HostApi(dartHostTestHandler: 'TestInAppPurchase2Api') abstract class InAppPurchase2API { // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments + // SK1 canMakePayments bool canMakePayments(); // https://developer.apple.com/documentation/storekit/product/3851116-products + // SK1 startProductRequest @async List products(List identifiers); - - // https://developer.apple.com/documentation/storekit/product/3791971-purchase - @async - SK2ProductPurchaseResultMessage purchase(String id, - {SK2ProductPurchaseOptionsMessage? options}); - - @async - List transactions(); - - @async - void finish(int id); - - void startListeningToTransactions(); - - void stopListeningToTransactions(); -} - -@FlutterApi() -abstract class InAppPurchase2CallbackAPI { - void onTransactionsUpdated(SK2TransactionMessage newTransaction); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index fe60dcda583e..ad1fac7ad970 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_storekit description: An implementation for the iOS and macOS platforms of the Flutter `in_app_purchase` plugin. This uses the StoreKit Framework. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_storekit issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.18+2 +version: 0.3.18+1 environment: sdk: ^3.3.0 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 98e96e26d673..6a5f6a53b644 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -8,7 +8,6 @@ import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; import 'package:in_app_purchase_storekit/src/messages.g.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; import 'package:in_app_purchase_storekit/src/store_kit_2_wrappers/sk2_product_wrapper.dart'; -import 'package:in_app_purchase_storekit/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart'; import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; import '../sk2_test_api.g.dart'; @@ -285,13 +284,7 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { class FakeStoreKit2Platform implements TestInAppPurchase2Api { late Set validProductIDs; late Map validProducts; - late List transactionList; - late bool testTransactionFail; - late int testTransactionCancel; - late List finishedTransactions; - PlatformException? queryProductException; - bool isListenerRegistered = false; void reset() { validProductIDs = {'123', '456'}; @@ -334,50 +327,4 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { return Future>.value(result); } - - @override - Future purchase(String id, - {SK2ProductPurchaseOptionsMessage? options}) { - final SK2TransactionMessage transaction = createPendingTransaction(id); - - InAppPurchaseStoreKitPlatform.sk2transactionObserver - .onTransactionsUpdated(transaction); - return Future.value( - SK2ProductPurchaseResultMessage.success); - } - - @override - Future finish(int id) { - return Future.value(); - } - - @override - Future> transactions() { - return Future>.value([ - SK2TransactionMessage( - id: 123, - originalId: 123, - productId: 'product_id', - purchaseDate: '12-12') - ]); - } - - @override - void startListeningToTransactions() { - isListenerRegistered = true; - } - - @override - void stopListeningToTransactions() { - isListenerRegistered = false; - } -} - -SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) { - return SK2TransactionMessage( - id: 1, - originalId: 2, - productId: id, - purchaseDate: 'purchaseDate', - appAccountToken: 'appAccountToken'); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart index 02c80b9d9bee..afc08cf71b9e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart @@ -2,27 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; -import 'package:in_app_purchase_storekit/store_kit_2_wrappers.dart'; import 'fakes/fake_storekit_platform.dart'; import 'sk2_test_api.g.dart'; void main() { - final SK2Product dummyProductWrapper = SK2Product( - id: '2', - displayName: 'name', - displayPrice: '0.99', - description: 'desc', - price: 0.99, - type: SK2ProductType.consumable, - priceLocale: SK2PriceLocale(currencyCode: 'USD', currencySymbol: r'$')); - TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKit2Platform fakeStoreKit2Platform = FakeStoreKit2Platform(); @@ -82,73 +70,4 @@ void main() { expect(response.error!.details, {'info': 'error_info'}); }); }); - - group('make payment', () { - test( - 'buying non consumable, should get purchase objects in the purchase update callback', - () async { - final List details = []; - final Completer> completer = - Completer>(); - final Stream> stream = - iapStoreKitPlatform.purchaseStream; - - late StreamSubscription> subscription; - subscription = stream.listen((List purchaseDetailsList) { - details.addAll(purchaseDetailsList); - if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { - completer.complete(details); - subscription.cancel(); - } - }); - final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( - productDetails: - AppStoreProduct2Details.fromSK2Product(dummyProductWrapper), - applicationUserName: 'appName'); - await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); - - final List result = await completer.future; - expect(result.length, 1); - expect(result.first.productID, dummyProductWrapper.id); - }); - - test( - 'buying consumable, should get purchase objects in the purchase update callback', - () async { - final List details = []; - final Completer> completer = - Completer>(); - final Stream> stream = - iapStoreKitPlatform.purchaseStream; - - late StreamSubscription> subscription; - subscription = stream.listen((List purchaseDetailsList) { - details.addAll(purchaseDetailsList); - if (purchaseDetailsList.first.status == PurchaseStatus.purchased) { - completer.complete(details); - subscription.cancel(); - } - }); - final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( - productDetails: - AppStoreProduct2Details.fromSK2Product(dummyProductWrapper), - applicationUserName: 'appName'); - await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam); - - final List result = await completer.future; - expect(result.length, 1); - expect(result.first.productID, dummyProductWrapper.id); - }); - - test('buying consumable, should throw when autoConsume is false', () async { - final AppStorePurchaseParam purchaseParam = AppStorePurchaseParam( - productDetails: - AppStoreProduct2Details.fromSK2Product(dummyProductWrapper), - applicationUserName: 'appName'); - expect( - () => iapStoreKitPlatform.buyConsumable( - purchaseParam: purchaseParam, autoConsume: false), - throwsA(isInstanceOf())); - }); - }); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart index 2bd0201d9f4d..21ad07890668 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart @@ -32,32 +32,20 @@ class _PigeonCodec extends StandardMessageCodec { } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { - buffer.putUint8(133); - writeValue(buffer, value.index); } else if (value is SK2SubscriptionOfferMessage) { - buffer.putUint8(134); + buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionPeriodMessage) { - buffer.putUint8(135); + buffer.putUint8(134); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionInfoMessage) { - buffer.putUint8(136); + buffer.putUint8(135); writeValue(buffer, value.encode()); } else if (value is SK2ProductMessage) { - buffer.putUint8(137); + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is SK2PriceLocaleMessage) { - buffer.putUint8(138); - writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { - buffer.putUint8(139); - writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { - buffer.putUint8(140); - writeValue(buffer, value.encode()); - } else if (value is SK2ErrorMessage) { - buffer.putUint8(141); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -86,26 +74,15 @@ class _PigeonCodec extends StandardMessageCodec { ? null : SK2SubscriptionPeriodUnitMessage.values[value]; case 133: - final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2ProductPurchaseResultMessage.values[value]; - case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 134: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 135: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 136: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 137: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: - return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: - return SK2TransactionMessage.decode(readValue(buffer)!); - case 141: - return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -121,17 +98,6 @@ abstract class TestInAppPurchase2Api { Future> products(List identifiers); - Future purchase(String id, - {SK2ProductPurchaseOptionsMessage? options}); - - Future> transactions(); - - Future finish(int id); - - void startListeningToTransactions(); - - void stopListeningToTransactions(); - static void setUp( TestInAppPurchase2Api? api, { BinaryMessenger? binaryMessenger, @@ -199,151 +165,5 @@ abstract class TestInAppPurchase2Api { }); } } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase was null.'); - final List args = (message as List?)!; - final String? arg_id = (args[0] as String?); - assert(arg_id != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase was null, expected non-null String.'); - final SK2ProductPurchaseOptionsMessage? arg_options = - (args[1] as SK2ProductPurchaseOptionsMessage?); - try { - final SK2ProductPurchaseResultMessage output = - await api.purchase(arg_id!, options: arg_options); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - try { - final List output = - await api.transactions(); - return [output]; - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish was null.'); - final List args = (message as List?)!; - final int? arg_id = (args[0] as int?); - assert(arg_id != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish was null, expected non-null int.'); - try { - await api.finish(arg_id!); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - try { - api.startListeningToTransactions(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } - { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$messageChannelSuffix', - pigeonChannelCodec, - binaryMessenger: binaryMessenger); - if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); - } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { - try { - api.stopListeningToTransactions(); - return wrapResponse(empty: true); - } on PlatformException catch (e) { - return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); - } - }); - } - } } }