From b126a15cd659914c435629ad36a21e0ae939dd11 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 3 Sep 2024 15:12:02 -0700 Subject: [PATCH 01/53] pigeon stuff --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 50 ++++++ .../StoreKit2/StoreKit2Translators.swift | 2 + .../Classes/StoreKit2/sk2_pigeon.g.swift | 119 ++++++++++---- .../lib/src/sk2_pigeon.g.dart | 151 ++++++++++++------ .../pigeons/sk2_pigeon.dart | 36 +++++ .../test/fakes/fake_storekit_platform.dart | 6 + .../test/sk2_test_api.g.dart | 148 +++++++++-------- 7 files changed, 367 insertions(+), 145 deletions(-) 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 7ab0405ae67..c5ba8e3b4f7 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 @@ -34,4 +34,54 @@ 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 { + do { + let product = try await rawProducts(identifiers: [id]).first + guard let product = product else { + throw PigeonError( + code: "storekit2_failed_to_fetch_product", message: "failed to make purchase", + details: "") + } + print("native purchase") + let result = try await product.purchase() + + switch result { + case .success(let verification): + switch verification { + case .verified(let transaction): + TransactionCache.shared.add(transaction: transaction) + print("purchase \(transaction)") + self.transactionListenerAPI?.transactionUpdated(updatedTransactions: 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", + details: ""))) + case .userCancelled: + completion( + .failure( + PigeonError( + code: "storekit2_purchase_cancelled", + message: "this transaction has been cancelled", details: ""))) + @unknown default: + fatalError() + } + } catch { + completion(.failure(error)) + } + + } + } } 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 aed0059733e..83e9f339c47 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,3 +167,5 @@ extension SK2PriceLocaleMessage: Equatable { return lhs.currencyCode == rhs.currencyCode && lhs.currencySymbol == rhs.currencySymbol } } + + 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 119fb288f36..355c0fc05d6 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 @@ -29,7 +29,7 @@ final class PigeonError: Error { var localizedDescription: String { return "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -96,6 +96,12 @@ 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 @@ -105,14 +111,15 @@ struct SK2SubscriptionOfferMessage { var periodCount: Int64 var paymentMode: SK2SubscriptionOfferPaymentModeMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionOfferMessage? { let id: String? = nilOrValue(pigeonVar_list[0]) let price = pigeonVar_list[1] as! Double let type = pigeonVar_list[2] as! SK2SubscriptionOfferTypeMessage let period = pigeonVar_list[3] as! SK2SubscriptionPeriodMessage - let periodCount = - pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) + let periodCount = pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) let paymentMode = pigeonVar_list[5] as! SK2SubscriptionOfferPaymentModeMessage return SK2SubscriptionOfferMessage( @@ -143,10 +150,11 @@ struct SK2SubscriptionPeriodMessage { /// The unit of time that this period represents. var unit: SK2SubscriptionPeriodUnitMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = - pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) + let value = pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) let unit = pigeonVar_list[1] as! SK2SubscriptionPeriodUnitMessage return SK2SubscriptionPeriodMessage( @@ -173,6 +181,8 @@ struct SK2SubscriptionInfoMessage { /// The duration that this subscription lasts before auto-renewing. var subscriptionPeriod: SK2SubscriptionPeriodMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionInfoMessage? { let promotionalOffers = pigeonVar_list[0] as! [SK2SubscriptionOfferMessage?] @@ -216,6 +226,8 @@ struct SK2ProductMessage { /// The currency and locale information for this product var priceLocale: SK2PriceLocaleMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductMessage? { let id = pigeonVar_list[0] as! String @@ -257,6 +269,8 @@ struct SK2PriceLocaleMessage { var currencyCode: String var currencySymbol: String + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2PriceLocaleMessage? { let currencyCode = pigeonVar_list[0] as! String @@ -275,6 +289,31 @@ 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, + ] + } +} + private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -303,15 +342,23 @@ private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { } return nil case 133: - return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + return SK2ProductPurchaseResultMessage(rawValue: enumResultAsInt) + } + return nil case 134: - return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) + return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) case 135: - return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) + return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) case 136: - return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) case 137: + return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + case 138: return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) + case 139: + return SK2ProductPurchaseOptionsMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } @@ -332,20 +379,26 @@ 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? SK2SubscriptionOfferMessage { + } 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.writeValue(value.toList()) } else if let value = value as? SK2SubscriptionPeriodMessage { - super.writeByte(134) + super.writeByte(135) super.writeValue(value.toList()) } else if let value = value as? SK2SubscriptionInfoMessage { - super.writeByte(135) + super.writeByte(136) super.writeValue(value.toList()) } else if let value = value as? SK2ProductMessage { - super.writeByte(136) + super.writeByte(137) super.writeValue(value.toList()) } else if let value = value as? SK2PriceLocaleMessage { - super.writeByte(137) + super.writeByte(138) + super.writeValue(value.toList()) + } else if let value = value as? SK2ProductPurchaseOptionsMessage { + super.writeByte(139) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -367,26 +420,21 @@ class sk2_pigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = sk2_pigeonPigeonCodec(readerWriter: sk2_pigeonPigeonCodecReaderWriter()) } + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol InAppPurchase2API { func canMakePayments() throws -> Bool - func products( - identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void) + func products(identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void) + func purchase(id: String, options: SK2ProductPurchaseOptionsMessage?, completion: @escaping (Result) -> Void) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { sk2_pigeonPigeonCodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. - static func setUp( - binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, - messageChannelSuffix: String = "" - ) { + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let canMakePaymentsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in do { @@ -399,10 +447,7 @@ class InAppPurchase2APISetup { } else { canMakePaymentsChannel.setMessageHandler(nil) } - let productsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -419,5 +464,23 @@ 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) + } } } 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 d45d16074ef..8fbcd79bc03 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 @@ -18,8 +18,7 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -32,13 +31,10 @@ List wrapResponse( enum SK2ProductTypeMessage { /// A consumable in-app purchase. consumable, - /// A non-consumable in-app purchase. nonConsumable, - /// A non-renewing subscription. nonRenewable, - /// An auto-renewable subscription. autoRenewable, } @@ -61,6 +57,12 @@ enum SK2SubscriptionPeriodUnitMessage { year, } +enum SK2ProductPurchaseResultMessage { + success, + userCancelled, + pending, +} + class SK2SubscriptionOfferMessage { SK2SubscriptionOfferMessage({ this.id, @@ -164,8 +166,7 @@ class SK2SubscriptionInfoMessage { static SK2SubscriptionInfoMessage decode(Object result) { result as List; return SK2SubscriptionInfoMessage( - promotionalOffers: - (result[0] as List?)!.cast(), + promotionalOffers: (result[0] as List?)!.cast(), subscriptionGroupID: result[1]! as String, subscriptionPeriod: result[2]! as SK2SubscriptionPeriodMessage, ); @@ -264,6 +265,33 @@ 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 _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -271,33 +299,39 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); 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 { super.writeValue(buffer, value); } @@ -306,34 +340,33 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null ? 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 134: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 135: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 136: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 137: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); + case 139: + return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -344,11 +377,9 @@ class InAppPurchase2API { /// Constructor for [InAppPurchase2API]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - InAppPurchase2API( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + InAppPurchase2API({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -356,10 +387,8 @@ class InAppPurchase2API { final String pigeonVar_messageChannelSuffix; Future canMakePayments() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -385,10 +414,8 @@ class InAppPurchase2API { } Future> products(List identifiers) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -409,8 +436,34 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); + return (pigeonVar_replyList[0] as List?)!.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?)!; } } } 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 941dd6dbb27..17b932a76aa 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,6 +128,35 @@ 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.restoring = false}); + final int id; + final int originalId; + final String productId; + final String purchaseDate; + final int purchasedQuantity; + final String? appAccountToken; + final bool restoring; +} + +enum SK2ProductPurchaseResultMessage { success, userCancelled, pending } + @HostApi(dartHostTestHandler: 'TestInAppPurchase2Api') abstract class InAppPurchase2API { // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments @@ -138,4 +167,11 @@ abstract class InAppPurchase2API { // SK1 startProductRequest @async List products(List identifiers); + + // https://developer.apple.com/documentation/storekit/product/3791971-purchase + // SK1 addPayment + // needs id to reference product, and also purchaseOptions + @async + SK2ProductPurchaseResultMessage purchase(String id, + {SK2ProductPurchaseOptionsMessage? options}); } 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 6a5f6a53b64..3a32f9c025e 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 @@ -327,4 +327,10 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { return Future>.value(result); } + + @override + Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { + // TODO: implement purchase + throw UnimplementedError(); + } } 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 21ad0789066..3e831012d0f 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 @@ -13,6 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -20,33 +21,39 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); 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 { super.writeValue(buffer, value); } @@ -55,34 +62,33 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: + final int? value = readValue(buffer) as int?; + return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 134: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 135: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 136: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 137: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); + case 139: + return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -90,77 +96,83 @@ class _PigeonCodec extends StandardMessageCodec { } abstract class TestInAppPurchase2Api { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); bool canMakePayments(); Future> products(List identifiers); - static void setUp( - TestInAppPurchase2Api? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); + + static void setUp(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { try { final bool output = api.canMakePayments(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } 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.products$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); final List args = (message as List?)!; - final List? arg_identifiers = - (args[0] as List?)?.cast(); + final List? arg_identifiers = (args[0] as List?)?.cast(); assert(arg_identifiers != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null, expected non-null List.'); try { - final List output = - await api.products(arg_identifiers!); + final List output = await api.products(arg_identifiers!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + '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())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } From ae9095aa4cf72b4a0900609f1877e02a5d33895c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 4 Sep 2024 13:45:25 -0700 Subject: [PATCH 02/53] refactored AND working heh --- .../darwin/Classes/InAppPurchasePlugin.swift | 3 + .../StoreKit2/InAppPurchaseStoreKit2.swift | 67 ++++++- .../StoreKit2/StoreKit2Translators.swift | 34 ++++ .../Classes/StoreKit2/TransactionCache.swift | 32 +++ .../StoreKit2/TransactionCallbacks.swift | 23 +++ .../Classes/StoreKit2/sk2_pigeon.g.swift | 149 ++++++++++++++ .../lib/src/sk2_pigeon.g.dart | 184 ++++++++++++++++++ .../sk2_transaction_wrapper.dart | 105 ++++++++++ .../src/types/app_store_purchase_details.dart | 13 ++ .../pigeons/sk2_pigeon.dart | 16 ++ .../test/fakes/fake_storekit_platform.dart | 22 +++ .../test/sk2_test_api.g.dart | 95 +++++++++ 12 files changed, 742 insertions(+), 1 deletion(-) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift create 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/darwin/Classes/InAppPurchasePlugin.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift index a591739eb69..fd2c2763912 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 @@ -26,6 +26,8 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { public var registrar: FlutterPluginRegistrar? // This property is optional, as it requires self to exist to be initialized. public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? + var updateListenerTask: Any? + var transactionListenerAPI: TransactionCallbacks? = nil public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) @@ -93,6 +95,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { let messenger = registrar.messenger #endif setupTransactionObserverChannelIfNeeded(withMessenger: messenger) + self.transactionListenerAPI = TransactionCallbacks.init(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 c5ba8e3b4f7..7594b1847e1 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 @@ -43,7 +43,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { ) { Task { do { - let product = try await rawProducts(identifiers: [id]).first + let product = try await Product.products(for: [id]).first guard let product = product else { throw PigeonError( code: "storekit2_failed_to_fetch_product", message: "failed to make purchase", @@ -84,4 +84,69 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } + + func transactions( + completion: @escaping (Result<[SK2TransactionMessage], any Error>) -> Void + ) { + Task { + do { + let transactionsMsgs = await rawTransactions().map { + $0.convertToPigeon() + } + completion(.success(transactionsMsgs)) + } + } + } + + func finish(id: Int64, completion: @escaping (Result) -> Void) { + Task { + print("native finish") + let transaction = TransactionCache.shared.get(id: Int(id)) + if let transaction = transaction { + await transaction.finish() + TransactionCache.shared.remove(id: Int(id)) + } + + } + } + + func startListeningToTransactions() throws { + self.updateListenerTask = self.listenForTransactions() + } + + func stopListeningToTransactions() throws { + self.updateListenerTask = nil + } + + func listenForTransactions() -> Task { + return Task.detached { + for await verificationResult in Transaction.updates { + switch verificationResult { + case .verified(let transaction): + print("listened \(transaction.productID)") + TransactionCache.shared.add(transaction: transaction) + self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) + case .unverified(let transaction, _): + print("unverified", transaction.id) + break + } + } + } + + } + + func rawTransactions() async -> [Transaction] { + var transactions: [Transaction] = [] + + for await verificationResult in Transaction.all { + switch verificationResult { + case .verified(let transaction): + transactions.append(transaction) + case .unverified(_, _): + // Handle unverified transactions if necessary + break + } + } + return transactions + } } 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 83e9f339c47..ba4593360b5 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 @@ -168,4 +168,38 @@ extension SK2PriceLocaleMessage: Equatable { } } +@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/TransactionCache.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift new file mode 100644 index 00000000000..e699ec67b43 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift @@ -0,0 +1,32 @@ +import StoreKit + +@available(iOS 15.0, *) +class TransactionCache { + private var cache: [Transaction] + + static var shared = TransactionCache() + private init() { + cache = [] + } + + func add(transaction: Transaction) { + cache.append(transaction) + } + + func remove(id: Int) -> Bool { + if cache.contains(where: {transaction in transaction.id == id}) { + cache.removeAll { transaction in + transaction.id == id + } + return true; + } + return false; + } + + func get(id: Int) -> Transaction? { + let res = cache.first(where: { transaction in + transaction.id == id + }) + return res + } +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift new file mode 100644 index 00000000000..38be3375fce --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -0,0 +1,23 @@ +class TransactionCallbacks: InAppPurchase2CallbackAPI { + var callbackAPI: InAppPurchase2CallbackAPI + + init(binaryMessenger: FlutterBinaryMessenger) { + callbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: binaryMessenger) + super.init(binaryMessenger: binaryMessenger) + } + + @available(iOS 15.0, *) + func transactionUpdated(updatedTransactions: Transaction, restoring: Bool = false) { + let transactionMsg = updatedTransactions.convertToPigeon( + restoring: restoring) + callbackAPI.onTransactionsUpdated(newTransaction: transactionMsg) { result in + switch result { + case .success: + print("Transaction updates successfully sent") + case .failure(let error): + print("Failed to send transaction updates: \(error)") + } + } + } + +} 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 355c0fc05d6..80014a60764 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,6 +58,10 @@ 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 } @@ -314,6 +318,51 @@ struct SK2ProductPurchaseOptionsMessage { } } +/// 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 + + + + // 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 + + return SK2TransactionMessage( + id: id, + originalId: originalId, + productId: productId, + purchaseDate: purchaseDate, + purchasedQuantity: purchasedQuantity, + appAccountToken: appAccountToken, + restoring: restoring + ) + } + func toList() -> [Any?] { + return [ + id, + originalId, + productId, + purchaseDate, + purchasedQuantity, + appAccountToken, + restoring, + ] + } +} + private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { @@ -359,6 +408,8 @@ private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { 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?]) default: return super.readValue(ofType: type) } @@ -400,6 +451,9 @@ private class sk2_pigeonPigeonCodecWriter: FlutterStandardWriter { } 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 { super.writeValue(value) } @@ -426,6 +480,10 @@ 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`. @@ -482,5 +540,96 @@ class InAppPurchase2APISetup { } 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/lib/src/sk2_pigeon.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart index 8fbcd79bc03..62ae5e23666 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 @@ -291,6 +291,57 @@ class SK2ProductPurchaseOptionsMessage { } } +class SK2TransactionMessage { + SK2TransactionMessage({ + required this.id, + required this.originalId, + required this.productId, + required this.purchaseDate, + this.purchasedQuantity = 1, + this.appAccountToken, + this.restoring = false, + }); + + int id; + + int originalId; + + String productId; + + String purchaseDate; + + int purchasedQuantity; + + String? appAccountToken; + + bool restoring; + + Object encode() { + return [ + id, + originalId, + productId, + purchaseDate, + purchasedQuantity, + appAccountToken, + restoring, + ]; + } + + 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, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @@ -332,6 +383,9 @@ class _PigeonCodec extends StandardMessageCodec { } 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 { super.writeValue(buffer, value); } @@ -367,6 +421,8 @@ class _PigeonCodec extends StandardMessageCodec { return SK2PriceLocaleMessage.decode(readValue(buffer)!); case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); + case 140: + return SK2TransactionMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -466,4 +522,132 @@ class InAppPurchase2API { 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 pigeonVar_channel = BasicMessageChannel( + '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_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart new file mode 100644 index 00000000000..660dbcc2d2f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart @@ -0,0 +1,105 @@ +// 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(); + +/// Note that in StoreKit2, a Transaction encompasses the data contained by +/// SKPayment and SKTransaction in StoreKit1 +/// Dart wrapper around StoreKit2's [Transaction](https://developer.apple.com/documentation/storekit/transaction) +/// +class 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}); + + // SKTransaction + final String id; + +// The original transaction identifier, originalID, is identical to id except +// when the user restores a purchase or renews a transaction. + final String originalId; + +// SKPayment + final String productId; + final String purchaseDate; + final int quantity; + final String? appAccountToken; + final String? subscriptionGroupID; + final double? price; + + final SKError? error; + + static Future finish(int id) async { + await _hostapi.finish(id); + } + + static Future> transactions() async { + List msgs = await _hostapi.transactions(); + List transactions = msgs + .map((SK2TransactionMessage? e) => e?.convertFromPigeon()) + .cast() + .toList(); + return transactions; + } + + static void startListeningToTransactions() { + _hostapi.startListeningToTransactions(); + } + + 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() { + print("converting to details"); + return SK2PurchaseDetails( + productID: productId, + verificationData: PurchaseVerificationData( + localVerificationData: "", serverVerificationData: "", source: ""), + transactionDate: "", + // 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. + status: restoring ? PurchaseStatus.restored : PurchaseStatus.purchased, + purchaseID: id.toString(), + ); + } +} + +class SK2TransactionObserver implements InAppPurchase2CallbackAPI { + SK2TransactionObserver({required this.purchaseUpdatedController}); + + final StreamController> purchaseUpdatedController; + + @override + void onTransactionsUpdated(SK2TransactionMessage newTransaction) { + purchaseUpdatedController.add([newTransaction.convertToDetails()]); + } +} \ No newline at end of file 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 21a1e11116b..97e585a0b14 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,3 +77,16 @@ class AppStorePurchaseDetails extends PurchaseDetails { @override bool get pendingCompletePurchase => _pendingCompletePurchase; } + +class SK2PurchaseDetails extends PurchaseDetails { + SK2PurchaseDetails( + {required super.productID, + required super.purchaseID, + required super.verificationData, + required super.transactionDate, + required super.status}); + + @override + // but not restored + bool get pendingCompletePurchase => status == PurchaseStatus.purchased; +} 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 17b932a76aa..b9de9f72c8a 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 @@ -174,4 +174,20 @@ abstract class InAppPurchase2API { @async SK2ProductPurchaseResultMessage purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); + + // Note that the sk1 version of this only returns unfinished transactions. + @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/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index 3a32f9c025e..1002ac93739 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 @@ -333,4 +333,26 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { // TODO: implement purchase throw UnimplementedError(); } + + @override + Future finish(int id) { + // TODO: implement finish + throw UnimplementedError(); + } + + @override + Future> transactions() { + // TODO: implement transactions + throw UnimplementedError(); + } + + @override + void startListeningToTransactions() { + // TODO: implement startListeningToTransactions + } + + @override + void stopListeningToTransactions() { + // TODO: implement stopListeningToTransactions + } } 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 3e831012d0f..5aff83bf820 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 @@ -54,6 +54,9 @@ class _PigeonCodec extends StandardMessageCodec { } 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 { super.writeValue(buffer, value); } @@ -89,6 +92,8 @@ class _PigeonCodec extends StandardMessageCodec { return SK2PriceLocaleMessage.decode(readValue(buffer)!); case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); + case 140: + return SK2TransactionMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -105,6 +110,14 @@ abstract class TestInAppPurchase2Api { Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); + Future> transactions(); + + Future finish(int id); + + void startListeningToTransactions(); + + void stopListeningToTransactions(); + static void setUp(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { @@ -177,5 +190,87 @@ abstract class TestInAppPurchase2Api { }); } } + { + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + '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 pigeonVar_channel = BasicMessageChannel( + '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 pigeonVar_channel = BasicMessageChannel( + '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 pigeonVar_channel = BasicMessageChannel( + '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())); + } + }); + } + } } } From c38b66fa3b87a9969f8ef2976e266c1c658bb605 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 4 Sep 2024 13:46:05 -0700 Subject: [PATCH 03/53] format --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 132 +++++----- .../StoreKit2/StoreKit2Translators.swift | 1 - .../Classes/StoreKit2/TransactionCache.swift | 6 +- .../Classes/StoreKit2/sk2_pigeon.g.swift | 103 +++++--- .../lib/src/sk2_pigeon.g.dart | 157 +++++++----- .../sk2_transaction_wrapper.dart | 18 +- .../src/types/app_store_purchase_details.dart | 8 +- .../pigeons/sk2_pigeon.dart | 12 +- .../test/fakes/fake_storekit_platform.dart | 3 +- .../test/sk2_test_api.g.dart | 234 +++++++++++------- 10 files changed, 406 insertions(+), 268 deletions(-) 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 7594b1847e1..b6f1930dca9 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 @@ -36,87 +36,87 @@ 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 { - do { - let product = try await Product.products(for: [id]).first - guard let product = product else { - throw PigeonError( - code: "storekit2_failed_to_fetch_product", message: "failed to make purchase", - details: "") - } - print("native purchase") - let result = try await product.purchase() + // https://developer.apple.com/documentation/storekit/product/3791971-purchase + func purchase( + id: String, options: SK2ProductPurchaseOptionsMessage?, + completion: @escaping (Result) -> Void + ) { + Task { + do { + let product = try await Product.products(for: [id]).first + guard let product = product else { + throw PigeonError( + code: "storekit2_failed_to_fetch_product", message: "failed to make purchase", + details: "") + } + print("native purchase") + let result = try await product.purchase() - switch result { - case .success(let verification): - switch verification { - case .verified(let transaction): - TransactionCache.shared.add(transaction: transaction) - print("purchase \(transaction)") - self.transactionListenerAPI?.transactionUpdated(updatedTransactions: 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", - details: ""))) - case .userCancelled: - completion( - .failure( - PigeonError( - code: "storekit2_purchase_cancelled", - message: "this transaction has been cancelled", details: ""))) - @unknown default: - fatalError() + switch result { + case .success(let verification): + switch verification { + case .verified(let transaction): + TransactionCache.shared.add(transaction: transaction) + print("purchase \(transaction)") + self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) + completion(.success(result.convertToPigeon())) + case .unverified(_, let error): + completion(.failure(error)) } - } catch { - completion(.failure(error)) + case .pending: + completion( + .failure( + PigeonError( + code: "storekit2_purchase_pending", message: "this transaction is still pending", + details: ""))) + case .userCancelled: + completion( + .failure( + PigeonError( + code: "storekit2_purchase_cancelled", + message: "this transaction has been cancelled", details: ""))) + @unknown default: + fatalError() } - + } catch { + completion(.failure(error)) } + } + } func transactions( - completion: @escaping (Result<[SK2TransactionMessage], any Error>) -> Void - ) { - Task { - do { - let transactionsMsgs = await rawTransactions().map { - $0.convertToPigeon() - } - completion(.success(transactionsMsgs)) + completion: @escaping (Result<[SK2TransactionMessage], any Error>) -> Void + ) { + Task { + do { + let transactionsMsgs = await rawTransactions().map { + $0.convertToPigeon() } + completion(.success(transactionsMsgs)) } } + } - func finish(id: Int64, completion: @escaping (Result) -> Void) { - Task { - print("native finish") - let transaction = TransactionCache.shared.get(id: Int(id)) - if let transaction = transaction { - await transaction.finish() - TransactionCache.shared.remove(id: Int(id)) - } - + func finish(id: Int64, completion: @escaping (Result) -> Void) { + Task { + print("native finish") + let transaction = TransactionCache.shared.get(id: Int(id)) + if let transaction = transaction { + await transaction.finish() + TransactionCache.shared.remove(id: Int(id)) } - } - func startListeningToTransactions() throws { - self.updateListenerTask = self.listenForTransactions() } + } - func stopListeningToTransactions() throws { - self.updateListenerTask = nil - } + func startListeningToTransactions() throws { + self.updateListenerTask = self.listenForTransactions() + } + + func stopListeningToTransactions() throws { + self.updateListenerTask = nil + } func listenForTransactions() -> Task { return Task.detached { 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 ba4593360b5..b04caa25ae6 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 @@ -202,4 +202,3 @@ extension Transaction { ) } } - diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift index e699ec67b43..7e14abccd49 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift @@ -14,13 +14,13 @@ class TransactionCache { } func remove(id: Int) -> Bool { - if cache.contains(where: {transaction in transaction.id == id}) { + if cache.contains(where: { transaction in transaction.id == id }) { cache.removeAll { transaction in transaction.id == id } - return true; + return true } - return false; + return false } func get(id: Int) -> Transaction? { 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 80014a60764..18055ae7dfd 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 @@ -29,7 +29,7 @@ final class PigeonError: Error { var localizedDescription: String { return "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -59,7 +59,9 @@ 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: "") + return PigeonError( + code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", + details: "") } private func isNullish(_ value: Any?) -> Bool { @@ -115,15 +117,14 @@ struct SK2SubscriptionOfferMessage { var periodCount: Int64 var paymentMode: SK2SubscriptionOfferPaymentModeMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionOfferMessage? { let id: String? = nilOrValue(pigeonVar_list[0]) let price = pigeonVar_list[1] as! Double let type = pigeonVar_list[2] as! SK2SubscriptionOfferTypeMessage let period = pigeonVar_list[3] as! SK2SubscriptionPeriodMessage - let periodCount = pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) + let periodCount = + pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) let paymentMode = pigeonVar_list[5] as! SK2SubscriptionOfferPaymentModeMessage return SK2SubscriptionOfferMessage( @@ -154,11 +155,10 @@ struct SK2SubscriptionPeriodMessage { /// The unit of time that this period represents. var unit: SK2SubscriptionPeriodUnitMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) + let value = + pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) let unit = pigeonVar_list[1] as! SK2SubscriptionPeriodUnitMessage return SK2SubscriptionPeriodMessage( @@ -185,8 +185,6 @@ struct SK2SubscriptionInfoMessage { /// The duration that this subscription lasts before auto-renewing. var subscriptionPeriod: SK2SubscriptionPeriodMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionInfoMessage? { let promotionalOffers = pigeonVar_list[0] as! [SK2SubscriptionOfferMessage?] @@ -230,8 +228,6 @@ struct SK2ProductMessage { /// The currency and locale information for this product var priceLocale: SK2PriceLocaleMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductMessage? { let id = pigeonVar_list[0] as! String @@ -273,8 +269,6 @@ struct SK2PriceLocaleMessage { var currencyCode: String var currencySymbol: String - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2PriceLocaleMessage? { let currencyCode = pigeonVar_list[0] as! String @@ -298,12 +292,14 @@ 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)) + 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, @@ -328,15 +324,16 @@ struct SK2TransactionMessage { var appAccountToken: String? = nil var restoring: Bool - - // 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 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 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 @@ -474,12 +471,14 @@ class sk2_pigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = sk2_pigeonPigeonCodec(readerWriter: sk2_pigeonPigeonCodecReaderWriter()) } - /// Generated protocol from Pigeon that represents a handler of messages from Flutter. 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 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 @@ -490,9 +489,15 @@ protocol InAppPurchase2API { class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { sk2_pigeonPigeonCodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, messageChannelSuffix: String = "") { + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, + messageChannelSuffix: String = "" + ) { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + let canMakePaymentsChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in do { @@ -505,7 +510,10 @@ class InAppPurchase2APISetup { } else { canMakePaymentsChannel.setMessageHandler(nil) } - let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + let productsChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -522,7 +530,10 @@ class InAppPurchase2APISetup { } else { productsChannel.setMessageHandler(nil) } - let purchaseChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -540,7 +551,10 @@ class InAppPurchase2APISetup { } else { purchaseChannel.setMessageHandler(nil) } - let transactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 @@ -555,7 +569,9 @@ class InAppPurchase2APISetup { } else { transactionsChannel.setMessageHandler(nil) } - let finishChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -572,7 +588,10 @@ class InAppPurchase2APISetup { } else { finishChannel.setMessageHandler(nil) } - let startListeningToTransactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -585,7 +604,10 @@ class InAppPurchase2APISetup { } else { startListeningToTransactionsChannel.setMessageHandler(nil) } - let stopListeningToTransactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -602,7 +624,9 @@ class InAppPurchase2APISetup { } /// 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) + func onTransactionsUpdated( + newTransaction newTransactionArg: SK2TransactionMessage, + completion: @escaping (Result) -> Void) } class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -614,9 +638,14 @@ class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { 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) + 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))) 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 62ae5e23666..67c98e700a3 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 @@ -18,7 +18,8 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -31,10 +32,13 @@ List wrapResponse({Object? result, PlatformException? error, bool empty enum SK2ProductTypeMessage { /// A consumable in-app purchase. consumable, + /// A non-consumable in-app purchase. nonConsumable, + /// A non-renewing subscription. nonRenewable, + /// An auto-renewable subscription. autoRenewable, } @@ -166,7 +170,8 @@ class SK2SubscriptionInfoMessage { static SK2SubscriptionInfoMessage decode(Object result) { result as List; return SK2SubscriptionInfoMessage( - promotionalOffers: (result[0] as List?)!.cast(), + promotionalOffers: + (result[0] as List?)!.cast(), subscriptionGroupID: result[1]! as String, subscriptionPeriod: result[2]! as SK2SubscriptionPeriodMessage, ); @@ -342,7 +347,6 @@ class SK2TransactionMessage { } } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -350,40 +354,40 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); } else { @@ -394,34 +398,42 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null + ? null + : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null + ? null + : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null + ? null + : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -433,9 +445,11 @@ class InAppPurchase2API { /// Constructor for [InAppPurchase2API]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - InAppPurchase2API({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + InAppPurchase2API( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -443,8 +457,10 @@ class InAppPurchase2API { final String pigeonVar_messageChannelSuffix; Future canMakePayments() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -470,8 +486,10 @@ class InAppPurchase2API { } Future> products(List identifiers) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -492,13 +510,17 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)!.cast(); + return (pigeonVar_replyList[0] as List?)! + .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( + 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, @@ -524,8 +546,10 @@ class InAppPurchase2API { } Future> transactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -546,13 +570,16 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)!.cast(); + 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( + 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, @@ -573,8 +600,10 @@ class InAppPurchase2API { } Future startListeningToTransactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -595,8 +624,10 @@ class InAppPurchase2API { } Future stopListeningToTransactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -622,20 +653,29 @@ abstract class InAppPurchase2CallbackAPI { void onTransactionsUpdated(SK2TransactionMessage newTransaction); - static void setUp(InAppPurchase2CallbackAPI? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + InAppPurchase2CallbackAPI? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated$messageChannelSuffix', pigeonChannelCodec, + 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.'); + '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?); + 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 { @@ -643,8 +683,9 @@ abstract class InAppPurchase2CallbackAPI { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } 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_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart index 660dbcc2d2f..643d3525f4f 100644 --- 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 @@ -19,14 +19,14 @@ InAppPurchase2API _hostapi = InAppPurchase2API(); class 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}); + required this.originalId, + required this.productId, + required this.purchaseDate, + this.quantity = 1, + required this.appAccountToken, + this.subscriptionGroupID, + this.price, + this.error}); // SKTransaction final String id; @@ -102,4 +102,4 @@ class SK2TransactionObserver implements InAppPurchase2CallbackAPI { void onTransactionsUpdated(SK2TransactionMessage newTransaction) { purchaseUpdatedController.add([newTransaction.convertToDetails()]); } -} \ No newline at end of file +} 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 97e585a0b14..9fe8775cf09 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 @@ -81,10 +81,10 @@ class AppStorePurchaseDetails extends PurchaseDetails { class SK2PurchaseDetails extends PurchaseDetails { SK2PurchaseDetails( {required super.productID, - required super.purchaseID, - required super.verificationData, - required super.transactionDate, - required super.status}); + required super.purchaseID, + required super.verificationData, + required super.transactionDate, + required super.status}); @override // but not restored 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 b9de9f72c8a..da367da8f3d 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 @@ -140,12 +140,12 @@ class SK2ProductPurchaseOptionsMessage { class SK2TransactionMessage { SK2TransactionMessage( {required this.id, - required this.originalId, - required this.productId, - required this.purchaseDate, - this.purchasedQuantity = 1, - this.appAccountToken, - this.restoring = false}); + required this.originalId, + required this.productId, + required this.purchaseDate, + this.purchasedQuantity = 1, + this.appAccountToken, + this.restoring = false}); final int id; final int originalId; final String productId; 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 1002ac93739..2a4793c9596 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 @@ -329,7 +329,8 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { } @override - Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { + Future purchase(String id, + {SK2ProductPurchaseOptionsMessage? options}) { // TODO: implement purchase throw UnimplementedError(); } 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 5aff83bf820..d2d95b40e82 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 @@ -13,7 +13,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -21,40 +20,40 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); } else { @@ -65,34 +64,42 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null + ? null + : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null + ? null + : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null + ? null + : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -101,14 +108,16 @@ class _PigeonCodec extends StandardMessageCodec { } abstract class TestInAppPurchase2Api { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); bool canMakePayments(); Future> products(List identifiers); - Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); + Future purchase(String id, + {SK2ProductPurchaseOptionsMessage? options}); Future> transactions(); @@ -118,107 +127,151 @@ abstract class TestInAppPurchase2Api { void stopListeningToTransactions(); - static void setUp(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + TestInAppPurchase2Api? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', pigeonChannelCodec, + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { try { final bool output = api.canMakePayments(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', pigeonChannelCodec, + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); final List args = (message as List?)!; - final List? arg_identifiers = (args[0] as List?)?.cast(); + final List? arg_identifiers = + (args[0] as List?)?.cast(); assert(arg_identifiers != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null, expected non-null List.'); try { - final List output = await api.products(arg_identifiers!); + final List output = + await api.products(arg_identifiers!); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase was 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?); + final SK2ProductPurchaseOptionsMessage? arg_options = + (args[1] as SK2ProductPurchaseOptionsMessage?); try { - final SK2ProductPurchaseResultMessage output = await api.purchase(arg_id!, options: arg_options); + 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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { try { - final List output = await api.transactions(); + 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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish was 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, @@ -228,46 +281,61 @@ abstract class TestInAppPurchase2Api { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } From 268a50e4811495b0d155a95756600827636f2f84 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 4 Sep 2024 15:32:26 -0700 Subject: [PATCH 04/53] pre refactor for cache --- .../darwin/Classes/InAppPurchasePlugin.swift | 4 +- .../InAppPurchaseStoreKit2PluginTests.swift | 4 ++ .../in_app_purchase_storekit_platform.dart | 48 +++++++++++++----- .../sk2_product_wrapper.dart | 50 +++++++++++++++++++ .../lib/store_kit_2_wrappers.dart | 1 + 5 files changed, 92 insertions(+), 15 deletions(-) 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 fd2c2763912..826ef72990a 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 @@ -26,8 +26,8 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { public var registrar: FlutterPluginRegistrar? // This property is optional, as it requires self to exist to be initialized. public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? - var updateListenerTask: Any? - var transactionListenerAPI: TransactionCallbacks? = nil + internal var updateListenerTask: Any? + internal var transactionListenerAPI: TransactionCallbacks? = nil public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index 20a8bf8621b..f07fccd3cec 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -96,4 +96,8 @@ final class InAppPurchase2PluginTests: XCTestCase { // Reset test session try await session.setSimulatedError(nil, forAPI: .loadProducts) } + + func testSuccessfulPurchase() async throws { + // plugin.purchase(id: "subscription_silver") + } } 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 4bbd863b844..00efe02f7aa 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 @@ -31,14 +31,17 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { InAppPurchaseStoreKitPlatform(); /// Experimental flag for StoreKit2. - static bool _useStoreKit2 = false; + static bool _useStoreKit2 = true; static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; + static late SK2TransactionObserver _sk2transactionObserver; + @override - Stream> get purchaseStream => - _observer.purchaseUpdatedController.stream; + Stream> get purchaseStream => _useStoreKit2 + ? _sk2transactionObserver.purchaseUpdatedController.stream + : _observer.purchaseUpdatedController.stream; /// Callback handler for transaction status changes. @visibleForTesting @@ -57,15 +60,26 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { _skPaymentQueueWrapper = SKPaymentQueueWrapper(); - // 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); + if (_useStoreKit2) { + final StreamController> updateController2 = + StreamController>.broadcast( + onListen: () => SK2Transaction.startListeningToTransactions(), + onCancel: () => SK2Transaction.stopListeningToTransactions(), + ); + _sk2transactionObserver = + SK2TransactionObserver(purchaseUpdatedController: 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(), + ); + _observer = _TransactionObserver(updateController); + _skPaymentQueueWrapper.setTransactionObserver(observer); + } } @override @@ -78,6 +92,10 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { + if (_useStoreKit2) { + await SK2Product.purchase(purchaseParam.productDetails.id); + return true; + } await _skPaymentQueueWrapper.addPayment(SKPaymentWrapper( productIdentifier: purchaseParam.productDetails.id, quantity: @@ -102,10 +120,14 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future completePurchase(PurchaseDetails purchase) { assert( - purchase is AppStorePurchaseDetails, + purchase is AppStorePurchaseDetails || purchase is SK2PurchaseDetails, '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, ); 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 9decfa433c9..5b055192c40 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 @@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/services.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../store_kit_2_wrappers.dart'; InAppPurchase2API _hostApi = InAppPurchase2API(); @@ -264,6 +265,44 @@ extension on SK2PriceLocaleMessage { } } +enum SK2ProductPurchaseResult { success, userCancelled, pending } + +class SK2ProductPurchaseOptions { + SK2ProductPurchaseOptions({this.appAccountToken, this.quantity}); + // this.appAccountToken + + final String? appAccountToken; + final int? quantity; + + SK2ProductPurchaseOptionsMessage convertToPigeon() { + return SK2ProductPurchaseOptionsMessage(); + } +} + +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; + } + } + + PurchaseStatus convertToPurchaseStatus() { + switch (this) { + case SK2ProductPurchaseResultMessage.success: + return PurchaseStatus.purchased; + case SK2ProductPurchaseResultMessage.userCancelled: + return PurchaseStatus.canceled; + case SK2ProductPurchaseResultMessage.pending: + return PurchaseStatus.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. @@ -324,6 +363,17 @@ class SK2Product { .toList(); } + 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/store_kit_2_wrappers.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart index 746cc3f378a..05482180b1a 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,3 +5,4 @@ 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'; From f6ec07a9b84ae48d6abe693b98bb459a6362b780 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 4 Sep 2024 16:32:24 -0700 Subject: [PATCH 05/53] . --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) 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 b6f1930dca9..a6551bcccaf 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 @@ -50,6 +50,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { details: "") } print("native purchase") + print(id) let result = try await product.purchase() switch result { @@ -101,10 +102,9 @@ extension InAppPurchasePlugin: InAppPurchase2API { func finish(id: Int64, completion: @escaping (Result) -> Void) { Task { print("native finish") - let transaction = TransactionCache.shared.get(id: Int(id)) + let transaction = try await fetchTransaction(by: UInt64(id)); if let transaction = transaction { await transaction.finish() - TransactionCache.shared.remove(id: Int(id)) } } @@ -149,4 +149,18 @@ extension InAppPurchasePlugin: InAppPurchase2API { } return transactions } + + 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 + } } From aa36dfd914545be08920508f9bc310f5a0bd73d0 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 5 Sep 2024 13:01:36 -0700 Subject: [PATCH 06/53] fix configuration file --- .../example/ios/Runner/Configuration.storekit | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit index d4484faac8d..c1eb0ed0961 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit @@ -106,7 +106,7 @@ "localizations" : [ ], - "name" : "Example Subscriptions", + "name" : "Silver Subscriptions", "subscriptions" : [ { "adHocOffers" : [ @@ -135,7 +135,16 @@ "winbackOffers" : [ ] - }, + } + ] + }, + { + "id" : "5D664105", + "localizations" : [ + + ], + "name" : "Gold Subscription", + "subscriptions" : [ { "adHocOffers" : [ @@ -145,8 +154,8 @@ ], "displayPrice" : "5.99", "familyShareable" : false, - "groupNumber" : 2, - "internalID" : "0BC7FF5E", + "groupNumber" : 1, + "internalID" : "12B805AE", "introductoryOffer" : null, "localizations" : [ { @@ -158,7 +167,7 @@ "productID" : "subscription_gold", "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_gold", - "subscriptionGroupID" : "D0FEE8D8", + "subscriptionGroupID" : "5D664105", "type" : "RecurringSubscription", "winbackOffers" : [ From 8724d4039a364351ea7157cef7a825d71e07dccd Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 5 Sep 2024 14:20:44 -0700 Subject: [PATCH 07/53] more docs --- .../InAppPurchaseStoreKit2PluginTests.swift | 2 +- .../sk2_product_wrapper.dart | 13 +++-- .../sk2_transaction_wrapper.dart | 53 +++++++++++++------ .../pigeons/sk2_pigeon.dart | 5 -- 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index f07fccd3cec..6aba85c2c30 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -98,6 +98,6 @@ final class InAppPurchase2PluginTests: XCTestCase { } func testSuccessfulPurchase() async throws { - // plugin.purchase(id: "subscription_silver") + plugin.purchase(id: <#T##String#>, options: nil, completion: <#T##(Result) -> Void#>) } } 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 5b055192c40..aef3ee69f1d 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 @@ -265,12 +265,19 @@ extension on SK2PriceLocaleMessage { } } -enum SK2ProductPurchaseResult { success, userCancelled, pending } +/// 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 +} class SK2ProductPurchaseOptions { SK2ProductPurchaseOptions({this.appAccountToken, this.quantity}); - // this.appAccountToken - final String? appAccountToken; final int? quantity; 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 index 643d3525f4f..03c8ab718d8 100644 --- 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 @@ -10,13 +10,13 @@ import '../../in_app_purchase_storekit.dart'; import '../../store_kit_wrappers.dart'; import '../sk2_pigeon.g.dart'; -InAppPurchase2API _hostapi = InAppPurchase2API(); +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 -/// Dart wrapper around StoreKit2's [Transaction](https://developer.apple.com/documentation/storekit/transaction) -/// class SK2Transaction { + /// Creates a new instance of [SK2Transaction] SK2Transaction( {required this.id, required this.originalId, @@ -28,42 +28,65 @@ class SK2Transaction { this.price, this.error}); - // SKTransaction + /// The unique identifier for the transaction. final String id; -// The original transaction identifier, originalID, is identical to id except -// when the user restores a purchase or renews a transaction. + /// 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; -// SKPayment + /// 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); + 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 { - List msgs = await _hostapi.transactions(); - List transactions = msgs + 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(); + _hostApi.startListeningToTransactions(); } + /// Stop listening to transactions. static void stopListeningToTransactions() { - _hostapi.stopListeningToTransactions(); + _hostApi.stopListeningToTransactions(); } } @@ -78,12 +101,11 @@ extension on SK2TransactionMessage { } PurchaseDetails convertToDetails() { - print("converting to details"); return SK2PurchaseDetails( productID: productId, verificationData: PurchaseVerificationData( - localVerificationData: "", serverVerificationData: "", source: ""), - transactionDate: "", + localVerificationData: '', serverVerificationData: '', source: ''), + transactionDate: '', // 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. @@ -93,6 +115,7 @@ extension on SK2TransactionMessage { } } +/// An observer that listens to all transactions used class SK2TransactionObserver implements InAppPurchase2CallbackAPI { SK2TransactionObserver({required this.purchaseUpdatedController}); 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 da367da8f3d..a3603380fd1 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 @@ -160,22 +160,17 @@ 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 - // SK1 addPayment - // needs id to reference product, and also purchaseOptions @async SK2ProductPurchaseResultMessage purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); - // Note that the sk1 version of this only returns unfinished transactions. @async List transactions(); From a6cbd985baf76f807e426b92984486bb3f496323 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 5 Sep 2024 14:56:01 -0700 Subject: [PATCH 08/53] more docs and clean up --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 22 +++++++-------- .../InAppPurchaseStoreKit2PluginTests.swift | 4 ++- .../in_app_purchase_storekit_platform.dart | 6 ++-- .../sk2_product_wrapper.dart | 28 ++++++++++--------- .../sk2_transaction_wrapper.dart | 17 ++++++----- .../src/types/app_store_purchase_details.dart | 3 +- 6 files changed, 44 insertions(+), 36 deletions(-) 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 a6551bcccaf..8046b16c69c 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 @@ -102,7 +102,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { func finish(id: Int64, completion: @escaping (Result) -> Void) { Task { print("native finish") - let transaction = try await fetchTransaction(by: UInt64(id)); + let transaction = try await fetchTransaction(by: UInt64(id)) if let transaction = transaction { await transaction.finish() } @@ -151,16 +151,16 @@ extension InAppPurchasePlugin: InAppPurchase2API { } 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 - } + for await result in Transaction.all { + switch result { + case .verified(let transaction): + if transaction.id == id { + return transaction + } + case .unverified(_, _): + continue } - return nil + } + return nil } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index 6aba85c2c30..c643bc4e1c3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -98,6 +98,8 @@ final class InAppPurchase2PluginTests: XCTestCase { } func testSuccessfulPurchase() async throws { - plugin.purchase(id: <#T##String#>, options: nil, completion: <#T##(Result) -> Void#>) + plugin.purchase( + id: <#T##String#>, options: nil, + completion: <#T##(Result) -> Void#>) } } 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 00efe02f7aa..6838e5c16f4 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 @@ -40,7 +40,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Stream> get purchaseStream => _useStoreKit2 - ? _sk2transactionObserver.purchaseUpdatedController.stream + ? _sk2transactionObserver.transactionsCreatedController.stream : _observer.purchaseUpdatedController.stream; /// Callback handler for transaction status changes. @@ -66,8 +66,8 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { onListen: () => SK2Transaction.startListeningToTransactions(), onCancel: () => SK2Transaction.stopListeningToTransactions(), ); - _sk2transactionObserver = - SK2TransactionObserver(purchaseUpdatedController: updateController2); + _sk2transactionObserver = SK2TransactionObserver( + transactionsCreatedController: updateController2); InAppPurchase2CallbackAPI.setUp(_sk2transactionObserver); } else { // Create a purchaseUpdatedController and notify the native side when to 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 aef3ee69f1d..820ea211d7b 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 @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'package:flutter/services.dart'; -import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../store_kit_2_wrappers.dart'; InAppPurchase2API _hostApi = InAppPurchase2API(); @@ -270,19 +269,30 @@ extension on SK2PriceLocaleMessage { 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(); + return SK2ProductPurchaseOptionsMessage( + appAccountToken: appAccountToken, quantity: quantity); } } @@ -297,17 +307,6 @@ extension on SK2ProductPurchaseResultMessage { return SK2ProductPurchaseResult.pending; } } - - PurchaseStatus convertToPurchaseStatus() { - switch (this) { - case SK2ProductPurchaseResultMessage.success: - return PurchaseStatus.purchased; - case SK2ProductPurchaseResultMessage.userCancelled: - return PurchaseStatus.canceled; - case SK2ProductPurchaseResultMessage.pending: - return PurchaseStatus.pending; - } - } } /// A wrapper around StoreKit2's [Product](https://developer.apple.com/documentation/storekit/product). @@ -370,6 +369,9 @@ 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; 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 index 03c8ab718d8..1a75c65f42b 100644 --- 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 @@ -106,23 +106,26 @@ extension on SK2TransactionMessage { verificationData: PurchaseVerificationData( localVerificationData: '', serverVerificationData: '', source: ''), transactionDate: '', - // 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. + // 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. status: restoring ? PurchaseStatus.restored : PurchaseStatus.purchased, purchaseID: id.toString(), ); } } -/// An observer that listens to all transactions used +/// An observer that listens to all transactions created class SK2TransactionObserver implements InAppPurchase2CallbackAPI { - SK2TransactionObserver({required this.purchaseUpdatedController}); + /// Creates a new instance of [SK2TransactionObserver] + SK2TransactionObserver({required this.transactionsCreatedController}); - final StreamController> purchaseUpdatedController; + /// The transactions stream to listen to + final StreamController> transactionsCreatedController; @override void onTransactionsUpdated(SK2TransactionMessage newTransaction) { - purchaseUpdatedController.add([newTransaction.convertToDetails()]); + 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 9fe8775cf09..95c974ef8a7 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 @@ -78,6 +78,8 @@ class AppStorePurchaseDetails extends PurchaseDetails { bool get pendingCompletePurchase => _pendingCompletePurchase; } +/// The class represents the information of a purchase made with the Apple +/// AppStore, when using Storekit2 class SK2PurchaseDetails extends PurchaseDetails { SK2PurchaseDetails( {required super.productID, @@ -87,6 +89,5 @@ class SK2PurchaseDetails extends PurchaseDetails { required super.status}); @override - // but not restored bool get pendingCompletePurchase => status == PurchaseStatus.purchased; } From b75eec715739e20f78311ea5e63595148468c21e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 12 Sep 2024 21:57:54 -0700 Subject: [PATCH 09/53] . --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 21 +--- .../example/ios/Runner/Configuration.storekit | 57 +++++++++++ .../InAppPurchaseStoreKit2PluginTests.swift | 99 ++++++++++++++++++- .../sk2_transaction_wrapper.dart | 2 +- .../src/types/app_store_purchase_details.dart | 1 + .../test/fakes/fake_storekit_platform.dart | 3 +- 6 files changed, 161 insertions(+), 22 deletions(-) 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 8046b16c69c..7cc247464c9 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 @@ -46,19 +46,15 @@ extension InAppPurchasePlugin: InAppPurchase2API { let product = try await Product.products(for: [id]).first guard let product = product else { throw PigeonError( - code: "storekit2_failed_to_fetch_product", message: "failed to make purchase", - details: "") + code: "storekit2_failed_to_fetch_product", message: "Storekit has failed to fetch this product.", + details: "Storekit has failed to fetch this product.") } - print("native purchase") - print(id) let result = try await product.purchase() switch result { case .success(let verification): switch verification { case .verified(let transaction): - TransactionCache.shared.add(transaction: transaction) - print("purchase \(transaction)") self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) completion(.success(result.convertToPigeon())) case .unverified(_, let error): @@ -66,16 +62,10 @@ extension InAppPurchasePlugin: InAppPurchase2API { } case .pending: completion( - .failure( - PigeonError( - code: "storekit2_purchase_pending", message: "this transaction is still pending", - details: ""))) + .success(.pending)) case .userCancelled: completion( - .failure( - PigeonError( - code: "storekit2_purchase_cancelled", - message: "this transaction has been cancelled", details: ""))) + .success(.userCancelled)) @unknown default: fatalError() } @@ -123,11 +113,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { for await verificationResult in Transaction.updates { switch verificationResult { case .verified(let transaction): - print("listened \(transaction.productID)") - TransactionCache.shared.add(transaction: transaction) self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) case .unverified(let transaction, _): - print("unverified", transaction.id) break } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit index c1eb0ed0961..bd30c5d0eb8 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit @@ -43,6 +43,21 @@ "productID" : "upgrade", "referenceName" : "upgrade", "type" : "NonConsumable" + }, + { + "displayPrice" : "0.49", + "familyShareable" : false, + "internalID" : "491F25B6", + "localizations" : [ + { + "description" : "A consumable product.", + "displayName" : "Consumable", + "locale" : "en_US" + } + ], + "productID" : "consumable_discounted", + "referenceName" : "consumable_discounted", + "type" : "Consumable" } ], "settings" : { @@ -171,6 +186,48 @@ "type" : "RecurringSubscription", "winbackOffers" : [ + ] + } + ] + }, + { + "id" : "3B74452E", + "localizations" : [ + + ], + "name" : "Discounted Subscriptions", + "subscriptions" : [ + { + "adHocOffers" : [ + + ], + "codeOffers" : [ + + ], + "displayPrice" : "0.99", + "familyShareable" : false, + "groupNumber" : 1, + "internalID" : "ECFBF793", + "introductoryOffer" : { + "displayPrice" : "0.99", + "internalID" : "62DAF06C", + "paymentMode" : "payUpFront", + "subscriptionPeriod" : "P1M" + }, + "localizations" : [ + { + "description" : "", + "displayName" : "", + "locale" : "en_US" + } + ], + "productID" : "subscription_discounted", + "recurringSubscriptionPeriod" : "P1M", + "referenceName" : "subscription_discounted", + "subscriptionGroupID" : "3B74452E", + "type" : "RecurringSubscription", + "winbackOffers" : [ + ] } ] diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index c643bc4e1c3..37ee99d810b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -17,6 +17,7 @@ final class InAppPurchase2PluginTests: XCTestCase { self.session = try! SKTestSession(configurationFileNamed: "Configuration") self.session.clearTransactions() + session.disableDialogs = true let receiptManagerStub = FIAPReceiptManagerStub() plugin = InAppPurchasePluginStub(receiptManager: receiptManagerStub) { request in DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request)) @@ -52,6 +53,30 @@ final class InAppPurchase2PluginTests: XCTestCase { 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): + // Handle the 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") @@ -98,8 +123,78 @@ final class InAppPurchase2PluginTests: XCTestCase { } 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): + print("Purchase successful: \(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, *) + 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) + try await session.setSimulatedError(nil, forAPI: .loadProducts) + } + + 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): + print("Purchase successful: \(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, *) + func testFailedNetworkErrorPurchase2() async throws { +// try await session.setSimulatedError(.purchase(Product.PurchaseError.invalidOfferPrice) +// , forAPI: .purchase) + try await session.setSimulatedError(.purchase(.invalidOfferPrice) + , forAPI: .purchase) + let expectation = self.expectation(description: "products request should fail") plugin.purchase( - id: <#T##String#>, options: nil, - completion: <#T##(Result) -> Void#>) + 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) + try await session.setSimulatedError(nil, forAPI: .loadProducts) } } 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 index 1a75c65f42b..94992b01ecc 100644 --- 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 @@ -105,7 +105,7 @@ extension on SK2TransactionMessage { productID: productId, verificationData: PurchaseVerificationData( localVerificationData: '', serverVerificationData: '', source: ''), - transactionDate: '', + 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. 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 95c974ef8a7..335ff6ad264 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 @@ -81,6 +81,7 @@ class AppStorePurchaseDetails extends PurchaseDetails { /// 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, 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 2a4793c9596..8589bf30edd 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 @@ -331,8 +331,7 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { - // TODO: implement purchase - throw UnimplementedError(); + return SK2ProductPurchaseResultMessage. } @override From 4d2fd594432fe72e6be22180d9473aef488220f4 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 24 Sep 2024 16:37:10 -0700 Subject: [PATCH 10/53] FIXED TESTING ISSUE --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 4 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + .../example/ios/Runner/Configuration.storekit | 5 +- .../InAppPurchaseStoreKit2PluginTests.swift | 49 ++++++++++++++----- 4 files changed, 45 insertions(+), 14 deletions(-) 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 7cc247464c9..f35a1645101 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 @@ -55,7 +55,9 @@ extension InAppPurchasePlugin: InAppPurchase2API { case .success(let verification): switch verification { case .verified(let transaction): - self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) + DispatchQueue.main.async { + self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) + } completion(.success(result.convertToPigeon())) case .unverified(_, let error): completion(.failure(error)) 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 477131feb69..9c3ad5d54dd 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,6 +39,7 @@ skipped = "NO"> Date: Wed, 25 Sep 2024 10:52:28 -0700 Subject: [PATCH 11/53] native tests are donesies --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 6 +- .../example/ios/Runner/Configuration.storekit | 5 +- .../InAppPurchaseStoreKit2PluginTests.swift | 70 +++++++++++++------ .../test/fakes/fake_storekit_platform.dart | 15 ++-- 4 files changed, 64 insertions(+), 32 deletions(-) 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 f35a1645101..cc0538ac790 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 @@ -45,11 +45,13 @@ extension InAppPurchasePlugin: InAppPurchase2API { do { let product = try await Product.products(for: [id]).first guard let product = product else { - throw PigeonError( + let error = PigeonError( code: "storekit2_failed_to_fetch_product", message: "Storekit has failed to fetch this product.", details: "Storekit has failed to fetch this product.") + return completion(.failure(error)) } - let result = try await product.purchase() + + let result = try await product.purchase(options: []) switch result { case .success(let verification): diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit index d245143cb1e..6a9a4779b63 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner/Configuration.storekit @@ -92,7 +92,10 @@ "name" : "App Store Sync" }, { - "current" : null, + "current" : { + "index" : 2, + "type" : "generic" + }, "enabled" : false, "name" : "Subscription Status" }, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index b21584fe179..e2e4d86ab44 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -19,10 +19,9 @@ final class InAppPurchase2PluginTests: XCTestCase { session = try! SKTestSession(configurationFileNamed: "Configuration") session.resetToDefaultState() session.clearTransactions() - print("deleting") session.disableDialogs = true - let receiptManagerStub = FIAPReceiptManagerStub() - plugin = InAppPurchasePluginStub(receiptManager: receiptManagerStub) { request in + + plugin = InAppPurchasePluginStub(receiptManager: FIAPReceiptManagerStub()) { request in DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request)) } try plugin.startListeningToTransactions() @@ -126,9 +125,6 @@ final class InAppPurchase2PluginTests: XCTestCase { } } await fulfillment(of: [expectation], timeout: 5) - - // Reset test session -// try await session.setSimulatedError(nil, forAPI: .loadProducts) } func testSuccessfulPurchase() async throws { @@ -165,10 +161,46 @@ final class InAppPurchase2PluginTests: XCTestCase { } } await fulfillment(of: [expectation], timeout: 5) -// try await session.setSimulatedError(nil, forAPI: .loadProducts) } - func testDiscountedSubscriptionSuccess() async throws { + @available(iOS 17.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 @@ -185,10 +217,10 @@ final class InAppPurchase2PluginTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5) } - func testDiscountedProductSuccess() async throws { + func testDiscountedSubscriptionSuccess() async throws { let expectation = self.expectation(description: "Purchase request should succeed") plugin.purchase( - id: "consumable_discounted", options: nil + id: "subscription_discounted", options: nil ) { result in switch result { case .success(let purchaseResult): @@ -202,24 +234,20 @@ final class InAppPurchase2PluginTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5) } - @available(iOS 17.0, *) - func testPurchaseFailureReturnsCorrectError() async throws { - try await session.setSimulatedError( - .purchase(.productUnavailable), forAPI: .purchase) + func testDiscountedProductSuccess() async throws { let expectation = self.expectation(description: "Purchase request should succeed") plugin.purchase( - id: "consumable", options: nil + id: "consumable_discounted", options: nil ) { result in switch result { - case .success(_): - XCTFail("Purchase should NOT suceed.") - case .failure(let error): - XCTAssertEqual(error.localizedDescription, "Item Unavailable") + case .success(let purchaseResult): + print("Purchase successful: \(purchaseResult)") expectation.fulfill() + + case .failure(let error): + XCTFail("Purchase should NOT fail. Failed with \(error)") } } await fulfillment(of: [expectation], timeout: 5) -// try await session.setSimulatedError( -// nil, forAPI: .purchase) } } 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 8589bf30edd..cce3a1024e0 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 @@ -285,6 +285,7 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { late Set validProductIDs; late Map validProducts; PlatformException? queryProductException; + bool isListenerRegistered = false; void reset() { validProductIDs = {'123', '456'}; @@ -331,28 +332,26 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { - return SK2ProductPurchaseResultMessage. + return Future.value(SK2ProductPurchaseResultMessage.success); } @override Future finish(int id) { - // TODO: implement finish - throw UnimplementedError(); + return Future.value(); } @override Future> transactions() { - // TODO: implement transactions - throw UnimplementedError(); + return + Future>.value([SK2TransactionMessage(id: 123, originalId: 123, productId: "product_id", purchaseDate: "12-12")]); } @override void startListeningToTransactions() { - // TODO: implement startListeningToTransactions + isListenerRegistered = true; } @override void stopListeningToTransactions() { - // TODO: implement stopListeningToTransactions - } + isListenerRegistered = false; } } From acf499ff01d0903e3a5911ee8b74dd35318a8b32 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 15:13:52 -0700 Subject: [PATCH 12/53] moreeee clean up and tests, --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 5 +- .../Classes/StoreKit2/TransactionCache.swift | 32 --- .../Classes/StoreKit2/sk2_pigeon.g.swift | 143 ++++++----- .../in_app_purchase_storekit_platform.dart | 12 +- .../lib/src/sk2_pigeon.g.dart | 198 +++++++-------- .../sk2_transaction_wrapper.dart | 7 +- .../pigeons/sk2_pigeon.dart | 11 + .../test/fakes/fake_storekit_platform.dart | 20 ++ ...app_purchase_storekit_2_platform_test.dart | 84 ++++++ .../test/sk2_test_api.g.dart | 239 +++++++----------- 10 files changed, 398 insertions(+), 353 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift 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 cc0538ac790..9b5e5b574d9 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 @@ -69,7 +69,10 @@ extension InAppPurchasePlugin: InAppPurchase2API { .success(.pending)) case .userCancelled: completion( - .success(.userCancelled)) + .failure( + PigeonError( + code: "storekit2_purchase_cancelled", + message: "this transaction has been cancelled", details: ""))) @unknown default: fatalError() } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift deleted file mode 100644 index 7e14abccd49..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCache.swift +++ /dev/null @@ -1,32 +0,0 @@ -import StoreKit - -@available(iOS 15.0, *) -class TransactionCache { - private var cache: [Transaction] - - static var shared = TransactionCache() - private init() { - cache = [] - } - - func add(transaction: Transaction) { - cache.append(transaction) - } - - func remove(id: Int) -> Bool { - if cache.contains(where: { transaction in transaction.id == id }) { - cache.removeAll { transaction in - transaction.id == id - } - return true - } - return false - } - - func get(id: Int) -> Transaction? { - let res = cache.first(where: { transaction in - transaction.id == id - }) - return res - } -} 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 18055ae7dfd..4be5a1c372f 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 @@ -29,7 +29,7 @@ final class PigeonError: Error { var localizedDescription: String { return "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -59,9 +59,7 @@ 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: "") + return PigeonError(code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", details: "") } private func isNullish(_ value: Any?) -> Bool { @@ -117,14 +115,15 @@ struct SK2SubscriptionOfferMessage { var periodCount: Int64 var paymentMode: SK2SubscriptionOfferPaymentModeMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionOfferMessage? { let id: String? = nilOrValue(pigeonVar_list[0]) let price = pigeonVar_list[1] as! Double let type = pigeonVar_list[2] as! SK2SubscriptionOfferTypeMessage let period = pigeonVar_list[3] as! SK2SubscriptionPeriodMessage - let periodCount = - pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) + let periodCount = pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) let paymentMode = pigeonVar_list[5] as! SK2SubscriptionOfferPaymentModeMessage return SK2SubscriptionOfferMessage( @@ -155,10 +154,11 @@ struct SK2SubscriptionPeriodMessage { /// The unit of time that this period represents. var unit: SK2SubscriptionPeriodUnitMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = - pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) + let value = pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) let unit = pigeonVar_list[1] as! SK2SubscriptionPeriodUnitMessage return SK2SubscriptionPeriodMessage( @@ -185,6 +185,8 @@ struct SK2SubscriptionInfoMessage { /// The duration that this subscription lasts before auto-renewing. var subscriptionPeriod: SK2SubscriptionPeriodMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionInfoMessage? { let promotionalOffers = pigeonVar_list[0] as! [SK2SubscriptionOfferMessage?] @@ -228,6 +230,8 @@ struct SK2ProductMessage { /// The currency and locale information for this product var priceLocale: SK2PriceLocaleMessage + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductMessage? { let id = pigeonVar_list[0] as! String @@ -269,6 +273,8 @@ struct SK2PriceLocaleMessage { var currencyCode: String var currencySymbol: String + + // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2PriceLocaleMessage? { let currencyCode = pigeonVar_list[0] as! String @@ -292,14 +298,12 @@ 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)) + 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, @@ -323,19 +327,20 @@ struct SK2TransactionMessage { 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 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 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, @@ -344,7 +349,8 @@ struct SK2TransactionMessage { purchaseDate: purchaseDate, purchasedQuantity: purchasedQuantity, appAccountToken: appAccountToken, - restoring: restoring + restoring: restoring, + error: error ) } func toList() -> [Any?] { @@ -356,6 +362,36 @@ struct SK2TransactionMessage { 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, ] } } @@ -407,6 +443,8 @@ private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { 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) } @@ -451,6 +489,9 @@ private class sk2_pigeonPigeonCodecWriter: FlutterStandardWriter { } 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.writeValue(value.toList()) } else { super.writeValue(value) } @@ -471,14 +512,12 @@ class sk2_pigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = sk2_pigeonPigeonCodec(readerWriter: sk2_pigeonPigeonCodecReaderWriter()) } + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. 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 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 @@ -489,15 +528,9 @@ protocol InAppPurchase2API { class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { sk2_pigeonPigeonCodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. - static func setUp( - binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, - messageChannelSuffix: String = "" - ) { + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let canMakePaymentsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in do { @@ -510,10 +543,7 @@ class InAppPurchase2APISetup { } else { canMakePaymentsChannel.setMessageHandler(nil) } - let productsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -530,10 +560,7 @@ class InAppPurchase2APISetup { } else { productsChannel.setMessageHandler(nil) } - let purchaseChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -551,10 +578,7 @@ class InAppPurchase2APISetup { } else { purchaseChannel.setMessageHandler(nil) } - let transactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + 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 @@ -569,9 +593,7 @@ class InAppPurchase2APISetup { } else { transactionsChannel.setMessageHandler(nil) } - let finishChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -588,10 +610,7 @@ class InAppPurchase2APISetup { } else { finishChannel.setMessageHandler(nil) } - let startListeningToTransactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -604,10 +623,7 @@ class InAppPurchase2APISetup { } else { startListeningToTransactionsChannel.setMessageHandler(nil) } - let stopListeningToTransactionsChannel = FlutterBasicMessageChannel( - name: - "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions\(channelSuffix)", - binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -624,9 +640,7 @@ class InAppPurchase2APISetup { } /// 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) + func onTransactionsUpdated(newTransaction newTransactionArg: SK2TransactionMessage, completion: @escaping (Result) -> Void) } class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -638,14 +652,9 @@ class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { 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) + 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))) 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 6838e5c16f4..b35bc87ac1d 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 @@ -35,7 +35,6 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; - static late SK2TransactionObserver _sk2transactionObserver; @override @@ -47,6 +46,10 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @visibleForTesting static SKTransactionObserverWrapper get observer => _observer; + /// Callback handler for transaction status changes for StoreKit2 transactions + @visibleForTesting + static SK2TransactionObserver get sk2transactionObserver => _sk2transactionObserver; + /// Registers this class as the default instance of [InAppPurchasePlatform]. static void registerPlatform() { // Register the [InAppPurchaseStoreKitPlatformAddition] containing @@ -93,7 +96,12 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { if (_useStoreKit2) { - await SK2Product.purchase(purchaseParam.productDetails.id); + 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( 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 67c98e700a3..d683ba2c3e4 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 @@ -18,8 +18,7 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse( - {Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -32,13 +31,10 @@ List wrapResponse( enum SK2ProductTypeMessage { /// A consumable in-app purchase. consumable, - /// A non-consumable in-app purchase. nonConsumable, - /// A non-renewing subscription. nonRenewable, - /// An auto-renewable subscription. autoRenewable, } @@ -170,8 +166,7 @@ class SK2SubscriptionInfoMessage { static SK2SubscriptionInfoMessage decode(Object result) { result as List; return SK2SubscriptionInfoMessage( - promotionalOffers: - (result[0] as List?)!.cast(), + promotionalOffers: (result[0] as List?)!.cast(), subscriptionGroupID: result[1]! as String, subscriptionPeriod: result[2]! as SK2SubscriptionPeriodMessage, ); @@ -305,6 +300,7 @@ class SK2TransactionMessage { this.purchasedQuantity = 1, this.appAccountToken, this.restoring = false, + this.error, }); int id; @@ -321,6 +317,8 @@ class SK2TransactionMessage { bool restoring; + SK2ErrorMessage? error; + Object encode() { return [ id, @@ -330,6 +328,7 @@ class SK2TransactionMessage { purchasedQuantity, appAccountToken, restoring, + error, ]; } @@ -343,10 +342,43 @@ class SK2TransactionMessage { 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 @@ -354,42 +386,45 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is SK2ErrorMessage) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -398,43 +433,37 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); + case 141: + return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -445,11 +474,9 @@ class InAppPurchase2API { /// Constructor for [InAppPurchase2API]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - InAppPurchase2API( - {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + InAppPurchase2API({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -457,10 +484,8 @@ class InAppPurchase2API { final String pigeonVar_messageChannelSuffix; Future canMakePayments() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -486,10 +511,8 @@ class InAppPurchase2API { } Future> products(List identifiers) async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -510,17 +533,13 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); + return (pigeonVar_replyList[0] as List?)!.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( + 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, @@ -546,10 +565,8 @@ class InAppPurchase2API { } Future> transactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + 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, @@ -570,16 +587,13 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)! - .cast(); + 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( + 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, @@ -600,10 +614,8 @@ class InAppPurchase2API { } Future startListeningToTransactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + 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, @@ -624,10 +636,8 @@ class InAppPurchase2API { } Future stopListeningToTransactions() async { - final String pigeonVar_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = - BasicMessageChannel( + 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, @@ -653,29 +663,20 @@ abstract class InAppPurchase2CallbackAPI { void onTransactionsUpdated(SK2TransactionMessage newTransaction); - static void setUp( - InAppPurchase2CallbackAPI? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + 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, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + '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.'); + '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?); + 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 { @@ -683,9 +684,8 @@ abstract class InAppPurchase2CallbackAPI { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } 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_transaction_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_transaction_wrapper.dart index 94992b01ecc..90c75ba6f67 100644 --- 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 @@ -103,12 +103,17 @@ extension on SK2TransactionMessage { 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 + // 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(), ); 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 a3603380fd1..b276524a587 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 @@ -145,6 +145,7 @@ class SK2TransactionMessage { required this.purchaseDate, this.purchasedQuantity = 1, this.appAccountToken, + this.error, this.restoring = false}); final int id; final int originalId; @@ -153,6 +154,16 @@ class SK2TransactionMessage { 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 } 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 cce3a1024e0..90d198c7a3e 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,6 +8,7 @@ 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'; @@ -284,6 +285,11 @@ 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; @@ -332,6 +338,10 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { + final SK2TransactionMessage transaction = + createPendingTransaction(id); + + InAppPurchaseStoreKitPlatform.sk2transactionObserver.onTransactionsUpdated(transaction); return Future.value(SK2ProductPurchaseResultMessage.success); } @@ -355,3 +365,13 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { void stopListeningToTransactions() { isListenerRegistered = false; } } + +SK2TransactionMessage createPendingTransaction(String id, + {int quantity = 1}) { + return SK2TransactionMessage( + id: 1, + originalId: 2, + productId: id, + purchaseDate: 'purchaseDate', appAccountToken: + 'appAccountToken'); +} \ No newline at end of file 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 afc08cf71b9..f7fc5ba22f9 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,15 +2,30 @@ // 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: "\$") + ); + TestWidgetsFlutterBinding.ensureInitialized(); final FakeStoreKit2Platform fakeStoreKit2Platform = FakeStoreKit2Platform(); @@ -70,4 +85,73 @@ 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 d2d95b40e82..fbf298ca1a3 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 @@ -13,6 +13,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -20,42 +21,45 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); + } else if (value is SK2ErrorMessage) { + buffer.putUint8(141); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -64,43 +68,37 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null - ? null - : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); + case 141: + return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -108,16 +106,14 @@ class _PigeonCodec extends StandardMessageCodec { } abstract class TestInAppPurchase2Api { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => - TestDefaultBinaryMessengerBinding.instance; + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); bool canMakePayments(); Future> products(List identifiers); - Future purchase(String id, - {SK2ProductPurchaseOptionsMessage? options}); + Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); Future> transactions(); @@ -127,151 +123,107 @@ abstract class TestInAppPurchase2Api { void stopListeningToTransactions(); - static void setUp( - TestInAppPurchase2Api? api, { - BinaryMessenger? binaryMessenger, - String messageChannelSuffix = '', - }) { - messageChannelSuffix = - messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { + messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel< - Object?> pigeonVar_channel = BasicMessageChannel< - Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { try { final bool output = api.canMakePayments(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } 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.products$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); final List args = (message as List?)!; - final List? arg_identifiers = - (args[0] as List?)?.cast(); + final List? arg_identifiers = (args[0] as List?)?.cast(); assert(arg_identifiers != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null, expected non-null List.'); try { - final List output = - await api.products(arg_identifiers!); + final List output = await api.products(arg_identifiers!); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } 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.purchase$messageChannelSuffix', - pigeonChannelCodec, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase was 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?); + final SK2ProductPurchaseOptionsMessage? arg_options = (args[1] as SK2ProductPurchaseOptionsMessage?); try { - final SK2ProductPurchaseResultMessage output = - await api.purchase(arg_id!, options: arg_options); + 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())); + } 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, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { try { - final List output = - await api.transactions(); + 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())); + } 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, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish was 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, @@ -281,61 +233,46 @@ abstract class TestInAppPurchase2Api { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse( - error: PlatformException(code: 'error', message: e.toString())); + } 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, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _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())); + } 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, + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(pigeonVar_channel, - (Object? message) async { + _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())); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); } }); } From c3c7204dc6efc1933a1fe121fb29dd9b4012d05f Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 15:18:43 -0700 Subject: [PATCH 13/53] format, comments, remove prints --- .../StoreKit2/InAppPurchaseStoreKit2.swift | 6 +- .../StoreKit2/TransactionCallbacks.swift | 3 +- .../Classes/StoreKit2/sk2_pigeon.g.swift | 108 +++++--- .../InAppPurchaseStoreKit2PluginTests.swift | 15 +- .../in_app_purchase_storekit_platform.dart | 13 +- .../lib/src/sk2_pigeon.g.dart | 161 +++++++----- .../test/fakes/fake_storekit_platform.dart | 30 ++- ...app_purchase_storekit_2_platform_test.dart | 97 ++++--- .../test/sk2_test_api.g.dart | 238 +++++++++++------- 9 files changed, 403 insertions(+), 268 deletions(-) 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 9b5e5b574d9..f2801c3ac27 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 @@ -46,11 +46,12 @@ extension InAppPurchasePlugin: InAppPurchase2API { let product = try await Product.products(for: [id]).first guard let product = product else { let error = PigeonError( - code: "storekit2_failed_to_fetch_product", message: "Storekit has failed to fetch this product.", + code: "storekit2_failed_to_fetch_product", + message: "Storekit has failed to fetch this product.", details: "Storekit has failed to fetch this product.") return completion(.failure(error)) } - + let result = try await product.purchase(options: []) switch result { @@ -98,7 +99,6 @@ extension InAppPurchasePlugin: InAppPurchase2API { func finish(id: Int64, completion: @escaping (Result) -> Void) { Task { - print("native finish") let transaction = try await fetchTransaction(by: UInt64(id)) if let transaction = transaction { await transaction.finish() diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift index 38be3375fce..0997be8f7a2 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -12,8 +12,7 @@ class TransactionCallbacks: InAppPurchase2CallbackAPI { restoring: restoring) callbackAPI.onTransactionsUpdated(newTransaction: transactionMsg) { result in switch result { - case .success: - print("Transaction updates successfully sent") + case .success: break case .failure(let error): print("Failed to send transaction updates: \(error)") } 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 4be5a1c372f..6a327812444 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 @@ -29,7 +29,7 @@ final class PigeonError: Error { var localizedDescription: String { return "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" - } + } } private func wrapResult(_ result: Any?) -> [Any?] { @@ -59,7 +59,9 @@ 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: "") + return PigeonError( + code: "channel-error", message: "Unable to establish connection on channel: '\(channelName)'.", + details: "") } private func isNullish(_ value: Any?) -> Bool { @@ -115,15 +117,14 @@ struct SK2SubscriptionOfferMessage { var periodCount: Int64 var paymentMode: SK2SubscriptionOfferPaymentModeMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionOfferMessage? { let id: String? = nilOrValue(pigeonVar_list[0]) let price = pigeonVar_list[1] as! Double let type = pigeonVar_list[2] as! SK2SubscriptionOfferTypeMessage let period = pigeonVar_list[3] as! SK2SubscriptionPeriodMessage - let periodCount = pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) + let periodCount = + pigeonVar_list[4] is Int64 ? pigeonVar_list[4] as! Int64 : Int64(pigeonVar_list[4] as! Int32) let paymentMode = pigeonVar_list[5] as! SK2SubscriptionOfferPaymentModeMessage return SK2SubscriptionOfferMessage( @@ -154,11 +155,10 @@ struct SK2SubscriptionPeriodMessage { /// The unit of time that this period represents. var unit: SK2SubscriptionPeriodUnitMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) + let value = + pigeonVar_list[0] is Int64 ? pigeonVar_list[0] as! Int64 : Int64(pigeonVar_list[0] as! Int32) let unit = pigeonVar_list[1] as! SK2SubscriptionPeriodUnitMessage return SK2SubscriptionPeriodMessage( @@ -185,8 +185,6 @@ struct SK2SubscriptionInfoMessage { /// The duration that this subscription lasts before auto-renewing. var subscriptionPeriod: SK2SubscriptionPeriodMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionInfoMessage? { let promotionalOffers = pigeonVar_list[0] as! [SK2SubscriptionOfferMessage?] @@ -230,8 +228,6 @@ struct SK2ProductMessage { /// The currency and locale information for this product var priceLocale: SK2PriceLocaleMessage - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductMessage? { let id = pigeonVar_list[0] as! String @@ -273,8 +269,6 @@ struct SK2PriceLocaleMessage { var currencyCode: String var currencySymbol: String - - // swift-format-ignore: AlwaysUseLowerCamelCase static func fromList(_ pigeonVar_list: [Any?]) -> SK2PriceLocaleMessage? { let currencyCode = pigeonVar_list[0] as! String @@ -298,12 +292,14 @@ 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)) + 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, @@ -329,15 +325,16 @@ struct SK2TransactionMessage { 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 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 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]) @@ -373,11 +370,10 @@ struct SK2ErrorMessage { 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 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]) @@ -512,12 +508,14 @@ class sk2_pigeonPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { static let shared = sk2_pigeonPigeonCodec(readerWriter: sk2_pigeonPigeonCodecReaderWriter()) } - /// Generated protocol from Pigeon that represents a handler of messages from Flutter. 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 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 @@ -528,9 +526,15 @@ protocol InAppPurchase2API { class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { sk2_pigeonPigeonCodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, messageChannelSuffix: String = "") { + static func setUp( + binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?, + messageChannelSuffix: String = "" + ) { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" - let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + let canMakePaymentsChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in do { @@ -543,7 +547,10 @@ class InAppPurchase2APISetup { } else { canMakePaymentsChannel.setMessageHandler(nil) } - let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + let productsChannel = FlutterBasicMessageChannel( + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in let args = message as! [Any?] @@ -560,7 +567,10 @@ class InAppPurchase2APISetup { } else { productsChannel.setMessageHandler(nil) } - let purchaseChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -578,7 +588,10 @@ class InAppPurchase2APISetup { } else { purchaseChannel.setMessageHandler(nil) } - let transactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 @@ -593,7 +606,9 @@ class InAppPurchase2APISetup { } else { transactionsChannel.setMessageHandler(nil) } - let finishChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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?] @@ -610,7 +625,10 @@ class InAppPurchase2APISetup { } else { finishChannel.setMessageHandler(nil) } - let startListeningToTransactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -623,7 +641,10 @@ class InAppPurchase2APISetup { } else { startListeningToTransactionsChannel.setMessageHandler(nil) } - let stopListeningToTransactionsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + 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 { @@ -640,7 +661,9 @@ class InAppPurchase2APISetup { } /// 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) + func onTransactionsUpdated( + newTransaction newTransactionArg: SK2TransactionMessage, + completion: @escaping (Result) -> Void) } class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { private let binaryMessenger: FlutterBinaryMessenger @@ -652,9 +675,14 @@ class InAppPurchase2CallbackAPI: InAppPurchase2CallbackAPIProtocol { 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) + 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))) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index e2e4d86ab44..193083c79b8 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -117,7 +117,6 @@ final class InAppPurchase2PluginTests: XCTestCase { XCTFail("This `products` call should not succeed") case .failure(let error): expectation.fulfill() - print(error.localizedDescription) XCTAssert( error.localizedDescription == "The operation couldn’t be completed. (in_app_purchase_storekit.PigeonError error 1.)" @@ -134,9 +133,7 @@ final class InAppPurchase2PluginTests: XCTestCase { ) { result in switch result { case .success(let purchaseResult): - print("Purchase successful: \(purchaseResult)") expectation.fulfill() - case .failure(let error): XCTFail("Purchase should NOT fail. Failed with \(error)") } @@ -156,7 +153,9 @@ final class InAppPurchase2PluginTests: XCTestCase { case .success(_): XCTFail("Purchase should NOT suceed.") case .failure(let error): - XCTAssertEqual(error.localizedDescription, "The operation couldn’t be completed. (NSURLErrorDomain error -1009.)") + XCTAssertEqual( + error.localizedDescription, + "The operation couldn’t be completed. (NSURLErrorDomain error -1009.)") expectation.fulfill() } } @@ -191,7 +190,7 @@ final class InAppPurchase2PluginTests: XCTestCase { case .success(_): XCTFail("Purchase should NOT suceed.") case .failure(let error): - let pigeonError = error as! PigeonError; + let pigeonError = error as! PigeonError XCTAssertEqual(pigeonError.code, "storekit2_failed_to_fetch_product") expectation.fulfill() @@ -207,9 +206,7 @@ final class InAppPurchase2PluginTests: XCTestCase { ) { result in switch result { case .success(let purchaseResult): - print("Purchase successful: \(purchaseResult)") expectation.fulfill() - case .failure(let error): XCTFail("Purchase should NOT fail. Failed with \(error)") } @@ -224,9 +221,7 @@ final class InAppPurchase2PluginTests: XCTestCase { ) { result in switch result { case .success(let purchaseResult): - print("Purchase successful: \(purchaseResult)") expectation.fulfill() - case .failure(let error): XCTFail("Purchase should NOT fail. Failed with \(error)") } @@ -241,9 +236,7 @@ final class InAppPurchase2PluginTests: XCTestCase { ) { result in switch result { case .success(let purchaseResult): - print("Purchase successful: \(purchaseResult)") expectation.fulfill() - case .failure(let error): XCTFail("Purchase should NOT fail. Failed with \(error)") } 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 b35bc87ac1d..df494d8f9b0 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 @@ -48,7 +48,8 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Callback handler for transaction status changes for StoreKit2 transactions @visibleForTesting - static SK2TransactionObserver get sk2transactionObserver => _sk2transactionObserver; + static SK2TransactionObserver get sk2transactionObserver => + _sk2transactionObserver; /// Registers this class as the default instance of [InAppPurchasePlatform]. static void registerPlatform() { @@ -97,10 +98,12 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { 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); + quantity: purchaseParam is AppStorePurchaseParam + ? purchaseParam.quantity + : 1, + appAccountToken: purchaseParam.applicationUserName); + await SK2Product.purchase(purchaseParam.productDetails.id, + options: options); return true; } 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 d683ba2c3e4..1dcc2cad771 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 @@ -18,7 +18,8 @@ PlatformException _createConnectionError(String channelName) { ); } -List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { +List wrapResponse( + {Object? result, PlatformException? error, bool empty = false}) { if (empty) { return []; } @@ -31,10 +32,13 @@ List wrapResponse({Object? result, PlatformException? error, bool empty enum SK2ProductTypeMessage { /// A consumable in-app purchase. consumable, + /// A non-consumable in-app purchase. nonConsumable, + /// A non-renewing subscription. nonRenewable, + /// An auto-renewable subscription. autoRenewable, } @@ -166,7 +170,8 @@ class SK2SubscriptionInfoMessage { static SK2SubscriptionInfoMessage decode(Object result) { result as List; return SK2SubscriptionInfoMessage( - promotionalOffers: (result[0] as List?)!.cast(), + promotionalOffers: + (result[0] as List?)!.cast(), subscriptionGroupID: result[1]! as String, subscriptionPeriod: result[2]! as SK2SubscriptionPeriodMessage, ); @@ -378,7 +383,6 @@ class SK2ErrorMessage { } } - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -386,43 +390,43 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is SK2ErrorMessage) { + } else if (value is SK2ErrorMessage) { buffer.putUint8(141); writeValue(buffer, value.encode()); } else { @@ -433,36 +437,44 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null + ? null + : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null + ? null + : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null + ? null + : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); - case 141: + case 141: return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -474,9 +486,11 @@ class InAppPurchase2API { /// Constructor for [InAppPurchase2API]. The [binaryMessenger] named argument is /// available for dependency injection. If it is left null, the default /// BinaryMessenger will be used which routes to the host platform. - InAppPurchase2API({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + InAppPurchase2API( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) : pigeonVar_binaryMessenger = binaryMessenger, - pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; final BinaryMessenger? pigeonVar_binaryMessenger; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); @@ -484,8 +498,10 @@ class InAppPurchase2API { final String pigeonVar_messageChannelSuffix; Future canMakePayments() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -511,8 +527,10 @@ class InAppPurchase2API { } Future> products(List identifiers) async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = + BasicMessageChannel( pigeonVar_channelName, pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, @@ -533,13 +551,17 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)!.cast(); + return (pigeonVar_replyList[0] as List?)! + .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( + 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, @@ -565,8 +587,10 @@ class InAppPurchase2API { } Future> transactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -587,13 +611,16 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (pigeonVar_replyList[0] as List?)!.cast(); + 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( + 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, @@ -614,8 +641,10 @@ class InAppPurchase2API { } Future startListeningToTransactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -636,8 +665,10 @@ class InAppPurchase2API { } Future stopListeningToTransactions() async { - final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$pigeonVar_messageChannelSuffix'; - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( + 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, @@ -663,20 +694,29 @@ abstract class InAppPurchase2CallbackAPI { void onTransactionsUpdated(SK2TransactionMessage newTransaction); - static void setUp(InAppPurchase2CallbackAPI? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + InAppPurchase2CallbackAPI? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2CallbackAPI.onTransactionsUpdated$messageChannelSuffix', pigeonChannelCodec, + 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.'); + '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?); + 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 { @@ -684,8 +724,9 @@ abstract class InAppPurchase2CallbackAPI { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } 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 90d198c7a3e..9c9ddd27643 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 @@ -338,11 +338,12 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}) { - final SK2TransactionMessage transaction = - createPendingTransaction(id); + final SK2TransactionMessage transaction = createPendingTransaction(id); - InAppPurchaseStoreKitPlatform.sk2transactionObserver.onTransactionsUpdated(transaction); - return Future.value(SK2ProductPurchaseResultMessage.success); + InAppPurchaseStoreKitPlatform.sk2transactionObserver + .onTransactionsUpdated(transaction); + return Future.value( + SK2ProductPurchaseResultMessage.success); } @override @@ -352,8 +353,13 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future> transactions() { - return - Future>.value([SK2TransactionMessage(id: 123, originalId: 123, productId: "product_id", purchaseDate: "12-12")]); + return Future>.value([ + SK2TransactionMessage( + id: 123, + originalId: 123, + productId: "product_id", + purchaseDate: "12-12") + ]); } @override @@ -363,15 +369,15 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override void stopListeningToTransactions() { - isListenerRegistered = false; } + isListenerRegistered = false; + } } -SK2TransactionMessage createPendingTransaction(String id, - {int quantity = 1}) { +SK2TransactionMessage createPendingTransaction(String id, {int quantity = 1}) { return SK2TransactionMessage( id: 1, originalId: 2, productId: id, - purchaseDate: 'purchaseDate', appAccountToken: - 'appAccountToken'); -} \ No newline at end of file + 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 f7fc5ba22f9..94a1fd0677a 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 @@ -13,9 +13,7 @@ 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', @@ -23,8 +21,7 @@ void main() { description: 'desc', price: 0.99, type: SK2ProductType.consumable, - priceLocale: SK2PriceLocale(currencyCode: 'USD', currencySymbol: "\$") - ); + priceLocale: SK2PriceLocale(currencyCode: 'USD', currencySymbol: "\$")); TestWidgetsFlutterBinding.ensureInitialized(); @@ -89,67 +86,67 @@ void main() { group('make payment', () { test( 'buying non consumable, should get purchase objects in the purchase update callback', - () async { - final List details = []; - final Completer> completer = + () 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: + 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); + applicationUserName: 'appName'); + await iapStoreKitPlatform.buyNonConsumable(purchaseParam: purchaseParam); - final List result = await completer.future; - expect(result.length, 1); - expect(result.first.productID, dummyProductWrapper.id); - }); + 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 = + () 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: + 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); + applicationUserName: 'appName'); + await iapStoreKitPlatform.buyConsumable(purchaseParam: purchaseParam); - final List result = await completer.future; - expect(result.length, 1); - expect(result.first.productID, dummyProductWrapper.id); - }); + 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), + AppStoreProduct2Details.fromSK2Product(dummyProductWrapper), applicationUserName: 'appName'); expect( - () => iapStoreKitPlatform.buyConsumable( + () => 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 fbf298ca1a3..2bd0201d9f4 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 @@ -13,7 +13,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; - class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -21,43 +20,43 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is SK2ProductTypeMessage) { + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferTypeMessage) { + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferPaymentModeMessage) { + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionPeriodUnitMessage) { + } else if (value is SK2SubscriptionPeriodUnitMessage) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is SK2ProductPurchaseResultMessage) { + } else if (value is SK2ProductPurchaseResultMessage) { buffer.putUint8(133); writeValue(buffer, value.index); - } else if (value is SK2SubscriptionOfferMessage) { + } else if (value is SK2SubscriptionOfferMessage) { buffer.putUint8(134); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionPeriodMessage) { + } else if (value is SK2SubscriptionPeriodMessage) { buffer.putUint8(135); writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + } else if (value is SK2SubscriptionInfoMessage) { buffer.putUint8(136); writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + } else if (value is SK2ProductMessage) { buffer.putUint8(137); writeValue(buffer, value.encode()); - } else if (value is SK2PriceLocaleMessage) { + } else if (value is SK2PriceLocaleMessage) { buffer.putUint8(138); writeValue(buffer, value.encode()); - } else if (value is SK2ProductPurchaseOptionsMessage) { + } else if (value is SK2ProductPurchaseOptionsMessage) { buffer.putUint8(139); writeValue(buffer, value.encode()); - } else if (value is SK2TransactionMessage) { + } else if (value is SK2TransactionMessage) { buffer.putUint8(140); writeValue(buffer, value.encode()); - } else if (value is SK2ErrorMessage) { + } else if (value is SK2ErrorMessage) { buffer.putUint8(141); writeValue(buffer, value.encode()); } else { @@ -68,36 +67,44 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 129: + case 129: final int? value = readValue(buffer) as int?; return value == null ? null : SK2ProductTypeMessage.values[value]; - case 130: + case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferTypeMessage.values[value]; - case 131: + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; + case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionOfferPaymentModeMessage.values[value]; - case 132: + return value == null + ? null + : SK2SubscriptionOfferPaymentModeMessage.values[value]; + case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2SubscriptionPeriodUnitMessage.values[value]; - case 133: + return value == null + ? null + : SK2SubscriptionPeriodUnitMessage.values[value]; + case 133: final int? value = readValue(buffer) as int?; - return value == null ? null : SK2ProductPurchaseResultMessage.values[value]; - case 134: + return value == null + ? null + : SK2ProductPurchaseResultMessage.values[value]; + case 134: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 135: + case 135: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); - case 136: + case 136: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 137: + case 137: return SK2ProductMessage.decode(readValue(buffer)!); - case 138: + case 138: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 139: + case 139: return SK2ProductPurchaseOptionsMessage.decode(readValue(buffer)!); - case 140: + case 140: return SK2TransactionMessage.decode(readValue(buffer)!); - case 141: + case 141: return SK2ErrorMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -106,14 +113,16 @@ class _PigeonCodec extends StandardMessageCodec { } abstract class TestInAppPurchase2Api { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; static const MessageCodec pigeonChannelCodec = _PigeonCodec(); bool canMakePayments(); Future> products(List identifiers); - Future purchase(String id, {SK2ProductPurchaseOptionsMessage? options}); + Future purchase(String id, + {SK2ProductPurchaseOptionsMessage? options}); Future> transactions(); @@ -123,107 +132,151 @@ abstract class TestInAppPurchase2Api { void stopListeningToTransactions(); - static void setUp(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger, String messageChannelSuffix = '',}) { - messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + static void setUp( + TestInAppPurchase2Api? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', pigeonChannelCodec, + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { try { final bool output = api.canMakePayments(); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', pigeonChannelCodec, + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); final List args = (message as List?)!; - final List? arg_identifiers = (args[0] as List?)?.cast(); + final List? arg_identifiers = + (args[0] as List?)?.cast(); assert(arg_identifiers != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null, expected non-null List.'); try { - final List output = await api.products(arg_identifiers!); + final List output = + await api.products(arg_identifiers!); return [output]; } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.purchase was 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?); + final SK2ProductPurchaseOptionsMessage? arg_options = + (args[1] as SK2ProductPurchaseOptionsMessage?); try { - final SK2ProductPurchaseResultMessage output = await api.purchase(arg_id!, options: arg_options); + 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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.transactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { try { - final List output = await api.transactions(); + 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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, + (Object? message) async { assert(message != null, - 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.finish was 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, @@ -233,46 +286,61 @@ abstract class TestInAppPurchase2Api { return wrapResponse(empty: true); } on PlatformException catch (e) { return wrapResponse(error: e); - } catch (e) { - return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.startListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } } { - final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.stopListeningToTransactions$messageChannelSuffix', pigeonChannelCodec, + 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); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { + _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())); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); } }); } From 9021b6fea6ef641427bdcd8411e0a91c6672b60a Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 15:36:52 -0700 Subject: [PATCH 14/53] versioning, analyze --- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- .../test/fakes/fake_storekit_platform.dart | 6 +++--- .../test/in_app_purchase_storekit_2_platform_test.dart | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) 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 f072c092770..1fb5e1bb97d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 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/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml index ad1fac7ad97..fe60dcda583 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+1 +version: 0.3.18+2 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 9c9ddd27643..98e96e26d67 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 @@ -353,12 +353,12 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future> transactions() { - return Future>.value([ + return Future>.value([ SK2TransactionMessage( id: 123, originalId: 123, - productId: "product_id", - purchaseDate: "12-12") + productId: 'product_id', + purchaseDate: '12-12') ]); } 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 94a1fd0677a..02c80b9d9be 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 @@ -21,7 +21,7 @@ void main() { description: 'desc', price: 0.99, type: SK2ProductType.consumable, - priceLocale: SK2PriceLocale(currencyCode: 'USD', currencySymbol: "\$")); + priceLocale: SK2PriceLocale(currencyCode: 'USD', currencySymbol: r'$')); TestWidgetsFlutterBinding.ensureInitialized(); From 666df56145c572484e3fd8124e5479941960fdd0 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 16:20:38 -0700 Subject: [PATCH 15/53] symlink the test files --- .../InAppPurchaseStoreKit2PluginTests.swift | 247 +----------------- .../StoreKit2TranslatorTests.swift | 83 +----- .../InAppPurchaseStoreKit2PluginTests.swift | 1 + .../StoreKit2TranslatorTests.swift | 1 + .../InAppPurchaseStoreKit2PluginTests.swift | 246 +++++++++++++++++ .../StoreKit2TranslatorTests.swift | 82 ++++++ 6 files changed, 332 insertions(+), 328 deletions(-) mode change 100644 => 120000 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift mode change 100644 => 120000 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift create mode 120000 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift create mode 120000 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift deleted file mode 100644 index 193083c79b8..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ /dev/null @@ -1,246 +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, *) - -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): - // Handle the 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): - // Handle the 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, *) - 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, *) - 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, *) - 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/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift new file mode 120000 index 00000000000..82d2f50c86b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift deleted file mode 100644 index 44a64132dd8..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/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, *) -class StoreKit2TranslatorTests: XCTestCase { - var session: SKTestSession! - var plugin: InAppPurchasePlugin! - 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/example/ios/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift new file mode 120000 index 00000000000..ae4348b7893 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/StoreKit2TranslatorTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift new file mode 120000 index 00000000000..82d2f50c86b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift new file mode 120000 index 00000000000..ae4348b7893 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/StoreKit2TranslatorTests.swift \ No newline at end of file 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 new file mode 100644 index 00000000000..193083c79b8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1,246 @@ +// 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, *) + +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): + // Handle the 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): + // Handle the 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, *) + 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, *) + 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, *) + 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 new file mode 100644 index 00000000000..44a64132dd8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1,82 @@ +// 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, *) +class StoreKit2TranslatorTests: XCTestCase { + var session: SKTestSession! + var plugin: InAppPurchasePlugin! + 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) + } +} From 97b746f72e4892fe75420170fc9b2594e21784bc Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 16:36:47 -0700 Subject: [PATCH 16/53] fix mac cant build --- .../darwin/Classes/StoreKit2/TransactionCallbacks.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift index 0997be8f7a2..0777cfecb8c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -6,7 +6,7 @@ class TransactionCallbacks: InAppPurchase2CallbackAPI { super.init(binaryMessenger: binaryMessenger) } - @available(iOS 15.0, *) + @available(iOS 15.0, macOS 12.0, *) func transactionUpdated(updatedTransactions: Transaction, restoring: Bool = false) { let transactionMsg = updatedTransactions.convertToPigeon( restoring: restoring) From add5bf595171c2d1ff3efdbcfd93ed891111ae12 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 26 Sep 2024 16:55:47 -0700 Subject: [PATCH 17/53] podspec, licensing --- .../darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 2 +- .../darwin/Classes/StoreKit2/TransactionCallbacks.swift | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) 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 f2801c3ac27..8951c3d392c 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 @@ -121,7 +121,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { switch verificationResult { case .verified(let transaction): self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) - case .unverified(let transaction, _): + case .unverified(_, _): break } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift index 0777cfecb8c..e09276b0481 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -1,3 +1,7 @@ +// 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. + class TransactionCallbacks: InAppPurchase2CallbackAPI { var callbackAPI: InAppPurchase2CallbackAPI From 06b42d01734549cd20f315208046e389bd6e0260 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 27 Sep 2024 10:33:10 -0700 Subject: [PATCH 18/53] . --- .../lib/src/in_app_purchase_storekit_platform.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 df494d8f9b0..42de506d077 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 @@ -31,7 +31,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { InAppPurchaseStoreKitPlatform(); /// Experimental flag for StoreKit2. - static bool _useStoreKit2 = true; + static bool _useStoreKit2 = false; static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; From 0701d2af26f1b004d80bda671ba2a15b85d926c5 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 27 Sep 2024 10:40:37 -0700 Subject: [PATCH 19/53] remove any to get around old linter --- .../darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8951c3d392c..e52926d12d0 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 @@ -97,7 +97,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } - func finish(id: Int64, completion: @escaping (Result) -> Void) { + func finish(id: Int64, completion: @escaping (Result) -> Void) { Task { let transaction = try await fetchTransaction(by: UInt64(id)) if let transaction = transaction { From 8f225c020755b21c82aa64cbf3284c39814f78e5 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 27 Sep 2024 10:56:44 -0700 Subject: [PATCH 20/53] remove more anys to appease format gods --- .../darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e52926d12d0..d2590b422a3 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 @@ -39,7 +39,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { // https://developer.apple.com/documentation/storekit/product/3791971-purchase func purchase( id: String, options: SK2ProductPurchaseOptionsMessage?, - completion: @escaping (Result) -> Void + completion: @escaping (Result) -> Void ) { Task { do { @@ -85,7 +85,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { } func transactions( - completion: @escaping (Result<[SK2TransactionMessage], any Error>) -> Void + completion: @escaping (Result<[SK2TransactionMessage], Error>) -> Void ) { Task { do { From 72ce51c3fae1f72123293af440f2a8fbb3f5bc57 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 1 Oct 2024 10:49:18 -0700 Subject: [PATCH 21/53] a lil bit of refactioring --- .../darwin/Classes/InAppPurchasePlugin.swift | 1 + .../StoreKit2/InAppPurchaseStoreKit2.swift | 44 ++++++++++--------- .../StoreKit2/TransactionCallbacks.swift | 4 +- .../in_app_purchase_storekit_platform.dart | 2 +- 4 files changed, 27 insertions(+), 24 deletions(-) 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 826ef72990a..a4f1202ca43 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 @@ -26,6 +26,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { public var registrar: FlutterPluginRegistrar? // This property is optional, as it requires self to exist to be initialized. public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? + internal var updateListenerTask: Any? internal var transactionListenerAPI: TransactionCallbacks? = nil 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 d2590b422a3..3ec4ac05b9a 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 @@ -3,7 +3,7 @@ // found in the LICENSE file. @available(iOS 15.0, macOS 12.0, *) -extension InAppPurchasePlugin: InAppPurchase2API { +extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { // MARK: - Pigeon Functions // Wrapper method around StoreKit2's canMakePayments() method @@ -37,6 +37,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { // Gets the appropriate product, then calls purchase on it. // https://developer.apple.com/documentation/storekit/product/3791971-purchase + @MainActor func purchase( id: String, options: SK2ProductPurchaseOptionsMessage?, completion: @escaping (Result) -> Void @@ -58,32 +59,34 @@ extension InAppPurchasePlugin: InAppPurchase2API { case .success(let verification): switch verification { case .verified(let transaction): - DispatchQueue.main.async { - self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) - } + self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) completion(.success(result.convertToPigeon())) case .unverified(_, let error): completion(.failure(error)) } case .pending: completion( - .success(.pending)) + .failure( + PigeonError( + code: "storekit2_purchase_pending", + message: "This transaction is still pending.", details: ""))) case .userCancelled: completion( .failure( PigeonError( code: "storekit2_purchase_cancelled", - message: "this transaction has been cancelled", details: ""))) + message: "This transaction has been cancelled.", details: ""))) @unknown default: - fatalError() + 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 ) { @@ -97,26 +100,21 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } + /// 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.updateListenerTask = self.listenForTransactions() - } - - func stopListeningToTransactions() throws { - self.updateListenerTask = nil - } - - func listenForTransactions() -> Task { - return Task.detached { + self.updateListenerTask = Task { for await verificationResult in Transaction.updates { switch verificationResult { case .verified(let transaction): @@ -126,10 +124,14 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } } + } + func stopListeningToTransactions() throws { + self.updateListenerTask = nil } - func rawTransactions() async -> [Transaction] { + /// Helper function that fetches and unwraps all verified transactions + private func rawTransactions() async -> [Transaction] { var transactions: [Transaction] = [] for await verificationResult in Transaction.all { @@ -137,14 +139,14 @@ extension InAppPurchasePlugin: InAppPurchase2API { case .verified(let transaction): transactions.append(transaction) case .unverified(_, _): - // Handle unverified transactions if necessary break } } return transactions } - func fetchTransaction(by id: UInt64) async throws -> Transaction? { + /// 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): diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift index e09276b0481..b42a8d8353d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -class TransactionCallbacks: InAppPurchase2CallbackAPI { - var callbackAPI: InAppPurchase2CallbackAPI +final class TransactionCallbacks: InAppPurchase2CallbackAPI { + let callbackAPI: InAppPurchase2CallbackAPI init(binaryMessenger: FlutterBinaryMessenger) { callbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: binaryMessenger) 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 42de506d077..df494d8f9b0 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 @@ -31,7 +31,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { InAppPurchaseStoreKitPlatform(); /// Experimental flag for StoreKit2. - static bool _useStoreKit2 = false; + static bool _useStoreKit2 = true; static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; From 442c158b2e79df47ee7a4e20e739c4191eee48d4 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 1 Oct 2024 13:34:30 -0700 Subject: [PATCH 22/53] better error messages --- .../darwin/Classes/InAppPurchasePlugin.swift | 6 ++--- .../StoreKit2/InAppPurchaseStoreKit2.swift | 24 +++++++++++-------- .../in_app_purchase_storekit_platform.dart | 3 +++ 3 files changed, 20 insertions(+), 13 deletions(-) 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 a4f1202ca43..4d488850ef7 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 @@ -26,9 +26,9 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { public var registrar: FlutterPluginRegistrar? // This property is optional, as it requires self to exist to be initialized. public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? - - internal var updateListenerTask: Any? - internal var transactionListenerAPI: TransactionCallbacks? = nil + + var updateListenerTask: Any? + var transactionListenerAPI: TransactionCallbacks? = nil public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) 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 3ec4ac05b9a..82cb6f25ad4 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 @@ -6,14 +6,14 @@ extension InAppPurchasePlugin: @preconcurrency 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,8 +35,8 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { } } - // Gets the appropriate product, then calls purchase on it. - // https://developer.apple.com/documentation/storekit/product/3791971-purchase + /// Gets the appropriate product, then calls purchase on it. + /// https://developer.apple.com/documentation/storekit/product/3791971-purchase @MainActor func purchase( id: String, options: SK2ProductPurchaseOptionsMessage?, @@ -49,7 +49,7 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { let error = PigeonError( code: "storekit2_failed_to_fetch_product", message: "Storekit has failed to fetch this product.", - details: "Storekit has failed to fetch this product.") + details: "Product ID : \(id)") return completion(.failure(error)) } @@ -69,13 +69,16 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { .failure( PigeonError( code: "storekit2_purchase_pending", - message: "This transaction is still pending.", details: ""))) + 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.", details: ""))) + message: "This transaction has been cancelled by the user.", + details: "Product ID : \(id)"))) @unknown default: fatalError("An unknown StoreKit PurchaseResult has been encountered.") } @@ -112,7 +115,7 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { /// 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. + /// 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.updateListenerTask = Task { for await verificationResult in Transaction.updates { @@ -126,6 +129,7 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { } } + /// Stop subscribing to Transaction.updates func stopListeningToTransactions() throws { self.updateListenerTask = nil } 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 df494d8f9b0..0f6ac18896b 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,8 +33,11 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Experimental flag for StoreKit2. static bool _useStoreKit2 = true; + /// StoreKit1 static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; + + /// StoreKit2 static late SK2TransactionObserver _sk2transactionObserver; @override From f48510bf937bbafa0f1b7e278e8c98174a646a2e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 1 Oct 2024 16:31:32 -0700 Subject: [PATCH 23/53] some refactoring --- .../darwin/Classes/InAppPurchasePlugin.swift | 5 +- .../StoreKit2/InAppPurchaseStoreKit2.swift | 37 +++++++++---- .../StoreKit2/TransactionCallbacks.swift | 53 ++++++++++--------- .../in_app_purchase_storekit_platform.dart | 2 +- 4 files changed, 58 insertions(+), 39 deletions(-) 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 4d488850ef7..70849812f1c 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,8 +27,9 @@ 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 var updateListenerTask: Any? - var transactionListenerAPI: TransactionCallbacks? = nil + var transactionDelegate: InAppPurchase2CallbackAPI? = nil public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) @@ -96,7 +97,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { let messenger = registrar.messenger #endif setupTransactionObserverChannelIfNeeded(withMessenger: messenger) - self.transactionListenerAPI = TransactionCallbacks.init(binaryMessenger: messenger) + self.transactionDelegate = 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 82cb6f25ad4..1072ad8df56 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 @@ -3,7 +3,7 @@ // found in the LICENSE file. @available(iOS 15.0, macOS 12.0, *) -extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { +extension InAppPurchasePlugin: InAppPurchase2API { // MARK: - Pigeon Functions /// Wrapper method around StoreKit2's canMakePayments() method @@ -37,12 +37,12 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { /// Gets the appropriate product, then calls purchase on it. /// https://developer.apple.com/documentation/storekit/product/3791971-purchase - @MainActor func purchase( id: String, options: SK2ProductPurchaseOptionsMessage?, completion: @escaping (Result) -> Void ) { Task { + @MainActor in do { let product = try await Product.products(for: [id]).first guard let product = product else { @@ -59,7 +59,7 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { case .success(let verification): switch verification { case .verified(let transaction): - self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) + self.sendTransactionUpdate(transaction: transaction) completion(.success(result.convertToPigeon())) case .unverified(_, let error): completion(.failure(error)) @@ -94,6 +94,7 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { completion: @escaping (Result<[SK2TransactionMessage], Error>) -> Void ) { Task { + @MainActor in do { let transactionsMsgs = await rawTransactions().map { $0.convertToPigeon() @@ -117,12 +118,12 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { /// 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.updateListenerTask = Task { + self.updateListenerTask = Task { [weak self] in for await verificationResult in Transaction.updates { switch verificationResult { case .verified(let transaction): - self.transactionListenerAPI?.transactionUpdated(updatedTransactions: transaction) - case .unverified(_, _): + self?.sendTransactionUpdate(transaction: transaction) + case .unverified: break } } @@ -131,18 +132,29 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { /// Stop subscribing to Transaction.updates func stopListeningToTransactions() throws { - self.updateListenerTask = nil + getUpdateListenerTask().cancel() + } + + /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` + func sendTransactionUpdate(transaction: Transaction) { + let transactionMsg = transaction.convertToPigeon() + transactionDelegate?.onTransactionsUpdated(newTransaction: transactionMsg) { result in + switch result { + case .success: break + case .failure(let error): + print("Failed to send transaction updates: \(error)") + } + } } /// 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(_, _): + case .unverified: break } } @@ -157,10 +169,15 @@ extension InAppPurchasePlugin: @preconcurrency InAppPurchase2API { if transaction.id == id { return transaction } - case .unverified(_, _): + case .unverified: continue } } return nil } + + /// Helper function to cast updateListenerTask to a task + func getUpdateListenerTask() -> Task<(), Never> { + return self.updateListenerTask as! Task<(), Never> + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift index b42a8d8353d..653bef65464 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift @@ -1,26 +1,27 @@ -// 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. - -final class TransactionCallbacks: InAppPurchase2CallbackAPI { - let callbackAPI: InAppPurchase2CallbackAPI - - init(binaryMessenger: FlutterBinaryMessenger) { - callbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: binaryMessenger) - super.init(binaryMessenger: binaryMessenger) - } - - @available(iOS 15.0, macOS 12.0, *) - func transactionUpdated(updatedTransactions: Transaction, restoring: Bool = false) { - let transactionMsg = updatedTransactions.convertToPigeon( - restoring: restoring) - callbackAPI.onTransactionsUpdated(newTransaction: transactionMsg) { result in - switch result { - case .success: break - case .failure(let error): - print("Failed to send transaction updates: \(error)") - } - } - } - -} +//// 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. +// +///// TODO Move to main plugin +//final class TransactionCallbacks: InAppPurchase2CallbackAPI { +// let callbackAPI: InAppPurchase2CallbackAPI +// +// init(binaryMessenger: FlutterBinaryMessenger) { +// callbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: binaryMessenger) +// super.init(binaryMessenger: binaryMessenger) +// } +// +// @available(iOS 15.0, macOS 12.0, *) +// func transactionUpdated(updatedTransactions: Transaction, restoring: Bool = false) { +// let transactionMsg = updatedTransactions.convertToPigeon( +// restoring: restoring) +// callbackAPI.onTransactionsUpdated(newTransaction: transactionMsg) { result in +// switch result { +// case .success: break +// case .failure(let error): +// print("Failed to send transaction updates: \(error)") +// } +// } +// } +// +//} 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 0f6ac18896b..5bdb92b2be8 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 @@ -31,7 +31,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { InAppPurchaseStoreKitPlatform(); /// Experimental flag for StoreKit2. - static bool _useStoreKit2 = true; + static bool _useStoreKit2 = false; /// StoreKit1 static late SKPaymentQueueWrapper _skPaymentQueueWrapper; From 6076bf1360334f078fabedebcddf3fc3a732667f Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 1 Oct 2024 16:50:13 -0700 Subject: [PATCH 24/53] . --- .../example/macos/Runner.xcodeproj/project.pbxproj | 12 ++++++++++-- .../InAppPurchaseStoreKit2PluginTests.swift | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 3407adfdc92..b7c1ae32a59 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -31,9 +31,11 @@ F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */; }; + F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */; }; + F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; - F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; + F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 045C0F7D19875EDA98DF0B7F /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ @@ -95,10 +97,12 @@ F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; + F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = SOURCE_ROOT; }; + F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = SOURCE_ROOT; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; - F2D527252C583C1C00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; + F2D527252C583C1C00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F79BDC152905FC0500E3999D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../../shared/RunnerTests/Info.plist; sourceTree = ""; }; F79BDC1B2905FC3200E3999D /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; @@ -217,6 +221,8 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( + F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */, + F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -469,6 +475,8 @@ F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, + F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, + F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */, ); 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 index 193083c79b8..d959cc4a118 100644 --- 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 @@ -7,7 +7,7 @@ import XCTest @testable import in_app_purchase_storekit -@available(iOS 15.0, *) +@available(iOS 15.0, macOS 12.0, *) final class InAppPurchase2PluginTests: XCTestCase { private var session: SKTestSession! From d8dcad2cc754a65c5d52b5da7678f6f20c49f0e0 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 2 Oct 2024 11:50:45 -0700 Subject: [PATCH 25/53] remove uneeded filed --- .../StoreKit2/TransactionCallbacks.swift | 27 ------------- .../InAppPurchaseStoreKit2PluginTests.swift | 39 ++++++------------- .../in_app_purchase_storekit_platform.dart | 19 ++++----- .../sk2_transaction_wrapper.dart | 6 +-- 4 files changed, 24 insertions(+), 67 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift deleted file mode 100644 index 653bef65464..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/TransactionCallbacks.swift +++ /dev/null @@ -1,27 +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. -// -///// TODO Move to main plugin -//final class TransactionCallbacks: InAppPurchase2CallbackAPI { -// let callbackAPI: InAppPurchase2CallbackAPI -// -// init(binaryMessenger: FlutterBinaryMessenger) { -// callbackAPI = InAppPurchase2CallbackAPI(binaryMessenger: binaryMessenger) -// super.init(binaryMessenger: binaryMessenger) -// } -// -// @available(iOS 15.0, macOS 12.0, *) -// func transactionUpdated(updatedTransactions: Transaction, restoring: Bool = false) { -// let transactionMsg = updatedTransactions.convertToPigeon( -// restoring: restoring) -// callbackAPI.onTransactionsUpdated(newTransaction: transactionMsg) { result in -// switch result { -// case .success: break -// case .failure(let error): -// print("Failed to send transaction updates: \(error)") -// } -// } -// } -// -//} 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 index d959cc4a118..c2fcfbdfedf 100644 --- 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 @@ -47,7 +47,6 @@ final class InAppPurchase2PluginTests: XCTestCase { fetchedProductMsg = productMessages.first expectation.fulfill() case .failure(let error): - // Handle the error print("Failed to fetch products: \(error.localizedDescription)") } } @@ -70,9 +69,7 @@ final class InAppPurchase2PluginTests: XCTestCase { case .success(let productMessages): fetchedProductMsg = productMessages.first expectation.fulfill() - case .failure(let error): - // Handle the error - print("Failed to fetch products: \(error.localizedDescription)") + case .failure(let error): print("Failed to fetch products: \(error.localizedDescription)") } } await fulfillment(of: [expectation], timeout: 5) @@ -104,7 +101,7 @@ final class InAppPurchase2PluginTests: XCTestCase { } //TODO(louisehsu): Add testing for lower versions. - @available(iOS 17.0, *) + @available(iOS 17.0, macOS 14.0, *) func testGetProductsWithStoreKitError() async throws { try await session.setSimulatedError( .generic(.networkError(URLError(.badURL))), forAPI: .loadProducts) @@ -128,9 +125,7 @@ final class InAppPurchase2PluginTests: XCTestCase { func testSuccessfulPurchase() async throws { let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase( - id: "consumable", options: nil - ) { result in + plugin.purchase(id: "consumable", options: nil) { result in switch result { case .success(let purchaseResult): expectation.fulfill() @@ -141,14 +136,12 @@ final class InAppPurchase2PluginTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5) } - @available(iOS 17.0, *) + @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 + plugin.purchase(id: "consumable", options: nil) { result in switch result { case .success(_): XCTFail("Purchase should NOT suceed.") @@ -162,14 +155,12 @@ final class InAppPurchase2PluginTests: XCTestCase { await fulfillment(of: [expectation], timeout: 5) } - @available(iOS 17.0, *) + @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 + plugin.purchase(id: "consumable", options: nil) { result in switch result { case .success(_): XCTFail("Purchase should NOT suceed.") @@ -183,9 +174,7 @@ final class InAppPurchase2PluginTests: XCTestCase { func testInvalidProductPurchase() async throws { let expectation = self.expectation(description: "products request should fail") - plugin.purchase( - id: "invalid_product", options: nil - ) { result in + plugin.purchase(id: "invalid_product", options: nil) { result in switch result { case .success(_): XCTFail("Purchase should NOT suceed.") @@ -201,9 +190,7 @@ final class InAppPurchase2PluginTests: XCTestCase { func testPurchaseUpgradeConsumableSuccess() async throws { let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase( - id: "subscription_discounted", options: nil - ) { result in + plugin.purchase(id: "subscription_discounted", options: nil) { result in switch result { case .success(let purchaseResult): expectation.fulfill() @@ -216,9 +203,7 @@ final class InAppPurchase2PluginTests: XCTestCase { func testDiscountedSubscriptionSuccess() async throws { let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase( - id: "subscription_discounted", options: nil - ) { result in + plugin.purchase(id: "subscription_discounted", options: nil) { result in switch result { case .success(let purchaseResult): expectation.fulfill() @@ -231,9 +216,7 @@ final class InAppPurchase2PluginTests: XCTestCase { func testDiscountedProductSuccess() async throws { let expectation = self.expectation(description: "Purchase request should succeed") - plugin.purchase( - id: "consumable_discounted", options: nil - ) { result in + plugin.purchase(id: "consumable_discounted", options: nil) { result in switch result { case .success(let purchaseResult): expectation.fulfill() 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 5bdb92b2be8..f930e57a8e7 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 @@ -35,23 +35,23 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// StoreKit1 static late SKPaymentQueueWrapper _skPaymentQueueWrapper; - static late _TransactionObserver _observer; + static late _TransactionObserver _sk1transactionObserver; /// StoreKit2 - static late SK2TransactionObserver _sk2transactionObserver; + static late SK2TransactionObserverWrapper _sk2transactionObserver; @override Stream> get purchaseStream => _useStoreKit2 ? _sk2transactionObserver.transactionsCreatedController.stream - : _observer.purchaseUpdatedController.stream; + : _sk1transactionObserver.purchaseUpdatedController.stream; /// Callback handler for transaction status changes. @visibleForTesting - static SKTransactionObserverWrapper get observer => _observer; + static SKTransactionObserverWrapper get observer => _sk1transactionObserver; /// Callback handler for transaction status changes for StoreKit2 transactions @visibleForTesting - static SK2TransactionObserver get sk2transactionObserver => + static SK2TransactionObserverWrapper get sk2transactionObserver => _sk2transactionObserver; /// Registers this class as the default instance of [InAppPurchasePlatform]. @@ -73,7 +73,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { onListen: () => SK2Transaction.startListeningToTransactions(), onCancel: () => SK2Transaction.stopListeningToTransactions(), ); - _sk2transactionObserver = SK2TransactionObserver( + _sk2transactionObserver = SK2TransactionObserverWrapper( transactionsCreatedController: updateController2); InAppPurchase2CallbackAPI.setUp(_sk2transactionObserver); } else { @@ -84,7 +84,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { onListen: () => _skPaymentQueueWrapper.startObservingTransactionQueue(), onCancel: () => _skPaymentQueueWrapper.stopObservingTransactionQueue(), ); - _observer = _TransactionObserver(updateController); + _sk1transactionObserver = _TransactionObserver(updateController); _skPaymentQueueWrapper.setTransactionObserver(observer); } } @@ -149,11 +149,12 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future restorePurchases({String? applicationUserName}) async { - return _observer + return _sk1transactionObserver .restoreTransactions( queue: _skPaymentQueueWrapper, applicationUserName: applicationUserName) - .whenComplete(() => _observer.cleanUpRestoredTransactions()); + .whenComplete( + () => _sk1transactionObserver.cleanUpRestoredTransactions()); } /// Query the product detail list. 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 index 90c75ba6f67..c6ec3bf0d5a 100644 --- 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 @@ -121,9 +121,9 @@ extension on SK2TransactionMessage { } /// An observer that listens to all transactions created -class SK2TransactionObserver implements InAppPurchase2CallbackAPI { - /// Creates a new instance of [SK2TransactionObserver] - SK2TransactionObserver({required this.transactionsCreatedController}); +class SK2TransactionObserverWrapper implements InAppPurchase2CallbackAPI { + /// Creates a new instance of [SK2TransactionObserverWrapper] + SK2TransactionObserverWrapper({required this.transactionsCreatedController}); /// The transactions stream to listen to final StreamController> transactionsCreatedController; From 9c2893bd6fcdd810ecd53947504a271282a6463b Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 2 Oct 2024 11:58:03 -0700 Subject: [PATCH 26/53] oops pathing --- .../example/macos/Runner.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index b7c1ae32a59..d94129beca1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -33,6 +33,7 @@ F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */; }; F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */; }; F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; + F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; @@ -99,6 +100,7 @@ F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = SOURCE_ROOT; }; F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = SOURCE_ROOT; }; + F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; @@ -232,6 +234,7 @@ F79BDC1F2906023C00E3999D /* Stubs.h */, F79BDC152905FC0500E3999D /* Info.plist */, F79BDC1B2905FC3200E3999D /* Stubs.m */, + F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */, F2C3A7402BD9D33D000D35F2 /* Stubs.swift */, F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */, ); @@ -347,6 +350,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 1a74fb76db59e9fb7d610175640a8459708158ef Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 2 Oct 2024 14:51:24 -0700 Subject: [PATCH 27/53] . --- .../darwin/Classes/InAppPurchasePlugin.swift | 4 ++-- .../Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) 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 70849812f1c..dab49f65d39 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 @@ -29,7 +29,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This should be an Task, but Task is on available >= iOS 13 var updateListenerTask: Any? - var transactionDelegate: InAppPurchase2CallbackAPI? = nil + var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) @@ -97,7 +97,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { let messenger = registrar.messenger #endif setupTransactionObserverChannelIfNeeded(withMessenger: messenger) - self.transactionDelegate = InAppPurchase2CallbackAPI(binaryMessenger: 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 1072ad8df56..7492226fdc5 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 @@ -41,11 +41,9 @@ extension InAppPurchasePlugin: InAppPurchase2API { id: String, options: SK2ProductPurchaseOptionsMessage?, completion: @escaping (Result) -> Void ) { - Task { - @MainActor in + Task { @MainActor in do { - let product = try await Product.products(for: [id]).first - guard let product = product else { + 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.", @@ -96,10 +94,10 @@ extension InAppPurchasePlugin: InAppPurchase2API { Task { @MainActor in do { - let transactionsMsgs = await rawTransactions().map { + let transactionsMessages = await rawTransactions().map { $0.convertToPigeon() } - completion(.success(transactionsMsgs)) + completion(.success(transactionsMessages)) } } } @@ -137,8 +135,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` func sendTransactionUpdate(transaction: Transaction) { - let transactionMsg = transaction.convertToPigeon() - transactionDelegate?.onTransactionsUpdated(newTransaction: transactionMsg) { result in + let transactionMessage = transaction.convertToPigeon() + transactionCallbackAPI?.onTransactionsUpdated(newTransaction: transactionMessage) { result in switch result { case .success: break case .failure(let error): From 1b688ae80e81d7b94407abad6a75ceb27ca2dcfa Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 2 Oct 2024 15:00:47 -0700 Subject: [PATCH 28/53] . --- .../darwin/Classes/InAppPurchasePlugin.swift | 1 + .../StoreKit2/InAppPurchaseStoreKit2.swift | 15 +++++++-------- 2 files changed, 8 insertions(+), 8 deletions(-) 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 dab49f65d39..69aefc3e4ad 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 @@ -29,6 +29,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This should be an Task, but Task is on available >= iOS 13 var updateListenerTask: Any? + var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil public static func register(with registrar: FlutterPluginRegistrar) { 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 7492226fdc5..8a2cc358743 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,6 +4,10 @@ @available(iOS 15.0, macOS 12.0, *) extension InAppPurchasePlugin: InAppPurchase2API { + var updateListenerTaskAsTask: Task<(), Never> { + return self.updateListenerTask as! Task<(), Never> + } + // MARK: - Pigeon Functions /// Wrapper method around StoreKit2's canMakePayments() method @@ -94,10 +98,10 @@ extension InAppPurchasePlugin: InAppPurchase2API { Task { @MainActor in do { - let transactionsMessages = await rawTransactions().map { + let transactionsMsgs = await rawTransactions().map { $0.convertToPigeon() } - completion(.success(transactionsMessages)) + completion(.success(transactionsMsgs)) } } } @@ -130,7 +134,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { /// Stop subscribing to Transaction.updates func stopListeningToTransactions() throws { - getUpdateListenerTask().cancel() + updateListenerTaskAsTask.cancel() } /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` @@ -173,9 +177,4 @@ extension InAppPurchasePlugin: InAppPurchase2API { } return nil } - - /// Helper function to cast updateListenerTask to a task - func getUpdateListenerTask() -> Task<(), Never> { - return self.updateListenerTask as! Task<(), Never> - } } From 74c2de38c986aca23166ac9b732748746bc9dc98 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 4 Oct 2024 13:00:13 -0700 Subject: [PATCH 29/53] . --- .../darwin/Classes/InAppPurchasePlugin.swift | 9 +++++++-- .../Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 3 --- .../example/ios/Runner.xcodeproj/project.pbxproj | 8 ++++---- .../shared/RunnerTests/StoreKit2TranslatorTests.swift | 8 ++++---- 4 files changed, 15 insertions(+), 13 deletions(-) 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 69aefc3e4ad..5e2c9b03b55 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 @@ -28,8 +28,13 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? // This should be an Task, but Task is on available >= iOS 13 - var updateListenerTask: Any? - + private var _updateListenerTask: Any? + + @available(iOS 13.0, *) + var updateListenerTaskAsTask: Task<(), Never> { + return self._updateListenerTask as! Task<(), Never> + } + var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil public static func register(with registrar: FlutterPluginRegistrar) { 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 8a2cc358743..53a56035a6e 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,9 +4,6 @@ @available(iOS 15.0, macOS 12.0, *) extension InAppPurchasePlugin: InAppPurchase2API { - var updateListenerTaskAsTask: Task<(), Never> { - return self.updateListenerTask as! Task<(), Never> - } // MARK: - Pigeon Functions 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 caf8c62f6e8..c68eed7b3ed 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,11 +18,11 @@ 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 */; }; @@ -78,11 +78,11 @@ 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 = ""; }; @@ -191,6 +191,7 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( + F22FD7A12CB080AE0006F28F /* StoreKit2TranslatorTests.swift */, F2D527292C583C4A00C137C7 /* TranslatorTests.swift */, F2D527192C50627500C137C7 /* PaymentQueueTests.swift */, F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */, @@ -201,7 +202,6 @@ F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, - F2858EE62C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift */, F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */, ); path = RunnerTests; @@ -444,7 +444,7 @@ F2D5271A2C50627500C137C7 /* PaymentQueueTests.swift in Sources */, F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, - F2858EE72C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, + F22FD7A22CB080AE0006F28F /* StoreKit2TranslatorTests.swift in Sources */, F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, 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 index 44a64132dd8..6fbd1f8444f 100644 --- 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 @@ -9,10 +9,10 @@ import XCTest @testable import in_app_purchase_storekit @available(iOS 15.0, macOS 12.0, *) -class StoreKit2TranslatorTests: XCTestCase { - var session: SKTestSession! - var plugin: InAppPurchasePlugin! - var product: Product! +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 = From 71c9a1bdbcfaebd8633fb0ab0b059a0a1fecd3da Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 4 Oct 2024 13:49:48 -0700 Subject: [PATCH 30/53] . --- .../darwin/Classes/InAppPurchasePlugin.swift | 11 +++++++--- .../StoreKit2/InAppPurchaseStoreKit2.swift | 21 ++++++++++--------- 2 files changed, 19 insertions(+), 13 deletions(-) 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 5e2c9b03b55..61d58b543d4 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 @@ -30,9 +30,14 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This should be an Task, but Task is on available >= iOS 13 private var _updateListenerTask: Any? - @available(iOS 13.0, *) - var updateListenerTaskAsTask: Task<(), Never> { - return self._updateListenerTask as! Task<(), Never> + @available(iOS 13.0, macOS 14.0, *) + var getListenerTaskAsTask: Task<(), Never> { + return self._updateListenerTask as! Task<(), Never> + } + + @available(iOS 13.0, macOS 14.0, *) + func setListenerTaskAsTask(task: Task<(), Never>) { + self._updateListenerTask = task } var transactionCallbackAPI: InAppPurchase2CallbackAPI? = nil 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 53a56035a6e..a21d1f7cf6b 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 @@ -117,21 +117,22 @@ extension InAppPurchasePlugin: InAppPurchase2API { /// 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.updateListenerTask = Task { [weak self] in - for await verificationResult in Transaction.updates { - switch verificationResult { - case .verified(let transaction): - self?.sendTransactionUpdate(transaction: transaction) - case .unverified: - break + 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 { - updateListenerTaskAsTask.cancel() + getListenerTaskAsTask.cancel() } /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` From 582156b7ae105efa5327bcc28debb7e8fecd18e7 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 4 Oct 2024 14:06:12 -0700 Subject: [PATCH 31/53] no message --- .../darwin/Classes/InAppPurchasePlugin.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 61d58b543d4..fc749b0b8fa 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 @@ -30,12 +30,12 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This should be an Task, but Task is on available >= iOS 13 private var _updateListenerTask: Any? - @available(iOS 13.0, macOS 14.0, *) + @available(iOS 13.0, *) var getListenerTaskAsTask: Task<(), Never> { return self._updateListenerTask as! Task<(), Never> } - @available(iOS 13.0, macOS 14.0, *) + @available(iOS 13.0, *) func setListenerTaskAsTask(task: Task<(), Never>) { self._updateListenerTask = task } From e6bec70b1106e579fb1aba9f25f15e125a24e068 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 7 Oct 2024 10:17:07 -0700 Subject: [PATCH 32/53] . --- .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) 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 c68eed7b3ed..02f9e0d3a45 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 @@ -24,7 +24,6 @@ F27694112C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694102C49BF6F00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694172C49DBCA00277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694162C49DBCA00277144 /* FIATransactionCacheTests.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 */; }; F2D5271A2C50627500C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527192C50627500C137C7 /* PaymentQueueTests.swift */; }; F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527292C583C4A00C137C7 /* TranslatorTests.swift */; }; @@ -83,7 +82,6 @@ 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 = ""; }; - 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 = ""; }; F2D527192C50627500C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; @@ -202,7 +200,6 @@ F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, - F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -445,7 +442,6 @@ F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, F22FD7A22CB080AE0006F28F /* StoreKit2TranslatorTests.swift in Sources */, - F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */, From 80795b9c746826695f65333cde2b2cabe6453122 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 7 Oct 2024 16:34:18 -0700 Subject: [PATCH 33/53] . --- .../example/ios/Runner.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) 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 02f9e0d3a45..9d01ee6865f 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 @@ -25,6 +25,7 @@ F27694172C49DBCA00277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */; }; F2858EE82C76A4230063A092 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F6E5D5F926131C4800C68BED /* Configuration.storekit */; }; 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 */ @@ -84,6 +85,7 @@ F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.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 = ""; }; @@ -189,6 +191,7 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( + F2D127482CB4A76D005FA2E5 /* InAppPurchaseStoreKit2PluginTests.swift */, F22FD7A12CB080AE0006F28F /* StoreKit2TranslatorTests.swift */, F2D527292C583C4A00C137C7 /* TranslatorTests.swift */, F2D527192C50627500C137C7 /* PaymentQueueTests.swift */, @@ -446,6 +449,7 @@ 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; From 66362c31f5ecec40f67ee2b5b814297611a3d729 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 7 Oct 2024 21:30:03 -0700 Subject: [PATCH 34/53] . --- .../example/macos/Runner.xcodeproj/project.pbxproj | 2 +- .../example/macos/Runner.xcworkspace/contents.xcworkspacedata | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index d94129beca1..471d8082972 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -214,6 +214,7 @@ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 33E51914231749380026EE4D /* Release.entitlements */, + F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */, 33CC11242044D66E0003C045 /* Resources */, 33BA886A226E78AF003329D5 /* Configs */, ); @@ -234,7 +235,6 @@ F79BDC1F2906023C00E3999D /* Stubs.h */, F79BDC152905FC0500E3999D /* Info.plist */, F79BDC1B2905FC3200E3999D /* Stubs.m */, - F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */, F2C3A7402BD9D33D000D35F2 /* Stubs.swift */, F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */, ); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 7f167c1063d..21a3cc14c74 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -1,9 +1,6 @@ - - From e554f5e1b4bcb0d839b81b28070bb4cca3230f4c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 8 Oct 2024 12:59:53 -0700 Subject: [PATCH 35/53] rename getter --- .../darwin/Classes/InAppPurchasePlugin.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fc749b0b8fa..43d500a8e3d 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 @@ -31,7 +31,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { private var _updateListenerTask: Any? @available(iOS 13.0, *) - var getListenerTaskAsTask: Task<(), Never> { + var updateListenerTask: Task<(), Never> { return self._updateListenerTask as! Task<(), Never> } From 5a08a582972adf01a47bf1582e5540726e117e9c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 8 Oct 2024 13:04:42 -0700 Subject: [PATCH 36/53] . --- .../darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a21d1f7cf6b..98d24d52adc 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 @@ -132,7 +132,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { /// Stop subscribing to Transaction.updates func stopListeningToTransactions() throws { - getListenerTaskAsTask.cancel() + updateListenerTask.cancel() } /// Sends an transaction back to Dart. Access these transactions with `purchaseStream` From 04288290a5e03a5db57f69d72626a4f9f33f5c6d Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 8 Oct 2024 14:37:44 -0700 Subject: [PATCH 37/53] try embeding? --- .../example/macos/Runner.xcodeproj/project.pbxproj | 4 ++++ .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 471d8082972..3bb201a383f 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; + F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; @@ -103,6 +104,7 @@ F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; + F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKitTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/StoreKitTest.framework; sourceTree = DEVELOPER_DIR; }; F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; F2D527252C583C1C00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -126,6 +128,7 @@ buildActionMask = 2147483647; files = ( F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */, + F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -148,6 +151,7 @@ 0B83E9AD591375BF30FD1CA9 /* Frameworks */ = { isa = PBXGroup; children = ( + F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */, AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */, 045C0F7D19875EDA98DF0B7F /* Pods_RunnerTests.framework */, ); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 5eb222feadb..93c6fc09160 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -38,7 +38,8 @@ + skipped = "NO" + parallelizable = "NO"> Date: Tue, 8 Oct 2024 16:17:42 -0700 Subject: [PATCH 38/53] rerun tests --- .../darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift | 2 ++ 1 file changed, 2 insertions(+) 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 98d24d52adc..cd8e96b5d13 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 @@ -147,6 +147,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { } } + // MARK: - Convenience Functions + /// Helper function that fetches and unwraps all verified transactions private func rawTransactions() async -> [Transaction] { var transactions: [Transaction] = [] From ec5c05c87517b081e81f3d9c4fd61e3385bd5a57 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 10 Oct 2024 15:25:29 -0700 Subject: [PATCH 39/53] change files so theyre relative to project rather than group --- .../example/macos/Runner.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 3bb201a383f..1c3c1d78844 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -99,8 +99,8 @@ F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; - F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = SOURCE_ROOT; }; - F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = SOURCE_ROOT; }; + F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; From 44bcde27bd29c8053d554bf2c9b9e6047c07eb90 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 14 Oct 2024 12:45:45 -0700 Subject: [PATCH 40/53] . --- .../example/macos/Runner.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 1c3c1d78844..fbb96195b8a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -760,6 +760,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -793,6 +794,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -824,6 +826,7 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( From 192b0041c3e5ca86ca0fe956b45f4cbbbf83f2b1 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 14:58:00 -0700 Subject: [PATCH 41/53] change from symlink --- .../InAppPurchaseStoreKit2PluginTests.swift | 230 +++++++- .../StoreKit2TranslatorTests.swift | 83 ++- .../macos/RunnerTests/TranslatorTests.swift | 552 +++++++++++++++++- 3 files changed, 862 insertions(+), 3 deletions(-) mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift deleted file mode 120000 index 82d2f50c86b..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift +++ /dev/null @@ -1 +0,0 @@ -../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift new file mode 100644 index 00000000000..c2fcfbdfedf --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1,229 @@ +// 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/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift deleted file mode 120000 index ae4348b7893..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift +++ /dev/null @@ -1 +0,0 @@ -../../shared/RunnerTests/StoreKit2TranslatorTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift new file mode 100644 index 00000000000..6fbd1f8444f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1,82 @@ +// 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/example/macos/RunnerTests/TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift deleted file mode 120000 index 743854417c0..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift +++ /dev/null @@ -1 +0,0 @@ -../../shared/RunnerTests/TranslatorTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift new file mode 100644 index 00000000000..204b2f2dbfc --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift @@ -0,0 +1,551 @@ +// 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 StoreKit +import XCTest + +@testable import in_app_purchase_storekit + +final class ObjectTranslatorTest: XCTestCase { + private var periodMap: [String: Any] { + ["numberOfUnits": 0, "unit": 0] + } + private var discountMap: [String: Any] { + var map: [String: Any] = [ + "price": "1", + "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), + "numberOfPeriods": 1, + "subscriptionPeriod": periodMap, + "paymentMode": 1, + ] + if #available(iOS 12.2, *) { + map["identifier"] = "test offer id" + // Type is being instantiated like this because of Swift naming weirdness + let type: SKProductDiscount.`Type` = .introductory + map["type"] = type.rawValue + } + return map + } + private var discountMissingIdentifierMap: [String: Any] { + [ + "price": "1", + "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), + "numberOfPeriods": 1, + "subscriptionPeriod": periodMap, + "paymentMode": 1, + "identifier": NSNull(), + "type": 0, + ] + } + private var productMap: [String: Any] { + var map: [String: Any] = [ + "price": "1", + "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), + "productIdentifier": "123", + "localizedTitle": "title", + "localizedDescription": "des", + "subscriptionPeriod": periodMap, + "introductoryPrice": discountMap, + "subscriptionGroupIdentifier": "com.group", + ] + if #available(iOS 12.2, *) { + map["discounts"] = [discountMap] + } + return map + } + private var productResponseMap: [String: Any] { + ["products": [productMap], "invalidProductIdentifiers": []] + } + private var paymentMap: [String: Any] { + [ + "productIdentifier": "123", + "requestData": "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", + "quantity": 2, + "applicationUsername": "app user name", + "simulatesAskToBuyInSandbox": false, + ] + } + private var paymentDiscountMap: [String: Any] { + [ + "identifier": "payment_discount_identifier", + "keyIdentifier": "payment_discount_key_identifier", + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": "this is an encrypted signature", + "timestamp": Int(Date().timeIntervalSince1970), + ] + } + private var transactionMap: [String: Any] { + [ + "transactionIdentifier": "567", + "transactionState": SKPaymentTransactionState.purchasing.rawValue, + "payment": NSNull(), + "error": FIAObjectTranslator.getMapFrom( + NSError(domain: "test_stub", code: 123, userInfo: [:])), + "transactionTimeStamp": Int(Date().timeIntervalSince1970), + "originalTransaction": originalTransactionMap, + ] + } + private var originalTransactionMap: [String: Any] { + [ + "transactionIdentifier": "567", + "transactionState": SKPaymentTransactionState.purchasing.rawValue, + "payment": NSNull(), + "error": FIAObjectTranslator.getMapFrom( + NSError(domain: "test_stub", code: 123, userInfo: [:])), + "transactionTimeStamp": Int(Date().timeIntervalSince1970), + "originalTransaction": NSNull(), + ] + } + private var errorMap: [String: Any] { + [ + "code": 123, + "domain": "test_domain", + "userInfo": ["key": "value"], + ] + } + private var storefrontMap: [String: Any] { + [ + "countryCode": "USA", + "identifier": "unique_identifier", + ] + } + private var storefrontAndPaymentTransactionMap: [String: Any] { + [ + "storefront": storefrontMap, + "transaction": transactionMap, + ] + } + + func testSKProductSubscriptionPeriodStubToMap() { + let period = SKProductSubscriptionPeriodStub(map: periodMap) + let map = FIAObjectTranslator.getMapFrom(period) + + XCTAssertEqual(map as NSDictionary, periodMap as NSDictionary) + } + + func testSKProductDiscountStubToMap() { + let discount = SKProductDiscountStub(map: discountMap) + let map = FIAObjectTranslator.getMapFrom(discount) + + XCTAssertEqual(map as NSDictionary, discountMap as NSDictionary) + } + + func testProductToMap() { + let product = SKProductStub(map: productMap) + let map = FIAObjectTranslator.getMapFrom(product) + + XCTAssertEqual(map as NSDictionary, productMap as NSDictionary) + } + + func testProductResponseToMap() { + let response = SKProductsResponseStub(map: productResponseMap) + let map = FIAObjectTranslator.getMapFrom(response) + + XCTAssertEqual(map as NSDictionary, productResponseMap as NSDictionary) + } + + func testPaymentToMap() { + let payment = FIAObjectTranslator.getSKMutablePayment(fromMap: paymentMap) + let map = FIAObjectTranslator.getMapFrom(payment) + + XCTAssertEqual(map as NSDictionary, paymentMap as NSDictionary) + } + + func testPaymentTransactionToMap() { + let paymentTransaction = SKPaymentTransactionStub(map: transactionMap) + let map = FIAObjectTranslator.getMapFrom(paymentTransaction) + + XCTAssertEqual(map as NSDictionary, transactionMap as NSDictionary) + } + + func testError() { + let error = NSErrorStub(map: errorMap) + let map = FIAObjectTranslator.getMapFrom(error) + + XCTAssertEqual(map as NSDictionary, errorMap as NSDictionary) + } + + func testErrorWithNSNumberAsUserInfo() { + let error = NSError(domain: SKErrorDomain, code: 3, userInfo: ["key": 42]) + let expectedMap: [String: Any] = [ + "domain": SKErrorDomain, + "code": 3, + "userInfo": ["key": 42], + ] + let map = FIAObjectTranslator.getMapFrom(error) + + XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) + } + + func testErrorWithMultipleUnderlyingErrors() { + let underlyingErrorOne = NSError(domain: SKErrorDomain, code: 2, userInfo: nil) + let underlyingErrorTwo = NSError(domain: SKErrorDomain, code: 1, userInfo: nil) + let mainError = NSError( + domain: SKErrorDomain, + code: 3, + userInfo: ["underlyingErrors": [underlyingErrorOne, underlyingErrorTwo]] + ) + let expectedMap: [String: Any] = [ + "domain": SKErrorDomain, + "code": 3, + "userInfo": [ + "underlyingErrors": [ + ["domain": SKErrorDomain, "code": 2, "userInfo": [:]], + ["domain": SKErrorDomain, "code": 1, "userInfo": [:]], + ] + ], + ] + let map = FIAObjectTranslator.getMapFrom(mainError) + + XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) + } + + func testErrorWithNestedUnderlyingError() { + let underlyingError = NSError(domain: SKErrorDomain, code: 2, userInfo: nil) + let mainError = NSError( + domain: SKErrorDomain, + code: 3, + userInfo: ["nesting": ["underlyingError": underlyingError]] + ) + let expectedMap: [String: Any] = [ + "domain": SKErrorDomain, + "code": 3, + "userInfo": [ + "nesting": [ + "underlyingError": ["domain": SKErrorDomain, "code": 2, "userInfo": [:]] + ] + ], + ] + let map = FIAObjectTranslator.getMapFrom(mainError) + + XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) + } + + func testErrorWithUnsupportedUserInfo() { + let error = NSError( + domain: SKErrorDomain, + code: 3, + userInfo: ["user_info": NSObject()] + ) + let expectedMap: [String: Any] = [ + "domain": SKErrorDomain, + "code": 3, + "userInfo": [ + "user_info": String( + format: """ + Unable to encode native userInfo object of type %@ to map. \ + Please submit an issue at https://github.com/flutter/flutter/issues/new \ + with the title "[in_app_purchase_storekit] Unable to encode userInfo of type %@\" \ + and add reproduction steps and the error details in the description field. + """, + NSStringFromClass(NSObject.self), NSStringFromClass(NSObject.self) + ) + ], + ] + let map = FIAObjectTranslator.getMapFrom(error) + + XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) + } + + func testLocaleToMap() { + let system = Locale(identifier: "en_US") + let map = FIAObjectTranslator.getMapFrom(system) + + XCTAssertEqual(map["currencySymbol"] as? String, system.currencySymbol) + XCTAssertEqual(map["countryCode"] as? String, system.regionCode) + } + + func testSKStorefrontToMap() { + if #available(iOS 13.0, *) { + let storefront = SKStorefrontStub(map: storefrontMap) + let map = FIAObjectTranslator.getMapFrom(storefront) + + XCTAssertEqual(map as NSDictionary, storefrontMap as NSDictionary) + } + } + + func testSKStorefrontAndSKPaymentTransactionToMap() { + if #available(iOS 13.0, *) { + let storefront = SKStorefrontStub(map: storefrontMap) + let transaction = SKPaymentTransactionStub(map: transactionMap) + let map = FIAObjectTranslator.getMapFrom(storefront, andSKPaymentTransaction: transaction) + + XCTAssertEqual(map as NSDictionary, storefrontAndPaymentTransactionMap as NSDictionary) + } + } + + func testSKPaymentDiscountFromMap() throws { + if #available(iOS 12.2, *) { + var error: NSString? + let paymentDiscount = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: paymentDiscountMap, withError: &error) + + XCTAssertNil(error) + + let unwrappedDiscount = try XCTUnwrap(paymentDiscount) + let unwrappedNonce = try XCTUnwrap(paymentDiscountMap["nonce"] as? String) + + XCTAssertEqual(unwrappedDiscount.identifier, paymentDiscountMap["identifier"] as? String) + XCTAssertEqual( + unwrappedDiscount.keyIdentifier, paymentDiscountMap["keyIdentifier"] as? String) + XCTAssertEqual( + unwrappedDiscount.nonce, UUID(uuidString: unwrappedNonce)) + XCTAssertEqual(unwrappedDiscount.signature, paymentDiscountMap["signature"] as? String) + XCTAssertEqual(unwrappedDiscount.timestamp as? Int, paymentDiscountMap["timestamp"] as? Int) + } + } + + func testSKPaymentDiscountFromMapMissingIdentifier() { + if #available(iOS 12.2, *) { + let invalidValues: [Any?] = [NSNull(), 1, ""] + for value in invalidValues { + let discountMap: [String: Any?] = [ + "identifier": value, + "keyIdentifier": "payment_discount_key_identifier", + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": "this is an encrypted signature", + "timestamp": Int(Date().timeIntervalSince1970), + ] + var error: NSString? + let _ = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap as [String: Any], withError: &error) + + XCTAssertNotNil(error) + XCTAssertEqual( + error, "When specifying a payment discount the 'identifier' field is mandatory.") + } + } + } + + func testGetMapFromSKProductDiscountMissingIdentifier() { + if #available(iOS 12.2, *) { + let discount = SKProductDiscountStub(map: discountMissingIdentifierMap) + let map = FIAObjectTranslator.getMapFrom(discount) + + XCTAssertEqual(map as NSDictionary, discountMissingIdentifierMap as NSDictionary) + } + } + + func testSKPaymentDiscountFromMapMissingKeyIdentifier() { + if #available(iOS 12.2, *) { + let invalidValues: [Any?] = [NSNull(), 1, ""] + for value in invalidValues { + let discountMap: [String: Any?] = [ + "identifier": "payment_discount_identifier", + "keyIdentifier": value, + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": "this is an encrypted signature", + "timestamp": Int(Date().timeIntervalSince1970), + ] + var error: NSString? + let _ = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap as [String: Any], withError: &error) + + XCTAssertNotNil(error) + XCTAssertEqual( + error, "When specifying a payment discount the 'keyIdentifier' field is mandatory.") + } + } + } + + func testSKPaymentDiscountFromMapMissingNonce() { + if #available(iOS 12.2, *) { + let invalidValues: [Any?] = [NSNull(), 1, ""] + for value in invalidValues { + let discountMap: [String: Any?] = [ + "identifier": "payment_discount_identifier", + "keyIdentifier": "payment_discount_key_identifier", + "nonce": value, + "signature": "this is an encrypted signature", + "timestamp": Int(Date().timeIntervalSince1970), + ] + var error: NSString? + let _ = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap as [String: Any], withError: &error) + + XCTAssertNotNil(error) + XCTAssertEqual(error, "When specifying a payment discount the 'nonce' field is mandatory.") + } + } + } + + func testSKPaymentDiscountFromMapMissingSignature() { + if #available(iOS 12.2, *) { + let invalidValues: [Any?] = [NSNull(), 1, ""] + for value in invalidValues { + let discountMap: [String: Any?] = [ + "identifier": "payment_discount_identifier", + "keyIdentifier": "payment_discount_key_identifier", + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": value, + "timestamp": Int(Date().timeIntervalSince1970), + ] + var error: NSString? + let _ = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap as [String: Any], withError: &error) + + XCTAssertNotNil(error) + XCTAssertEqual( + error, "When specifying a payment discount the 'signature' field is mandatory.") + } + } + } + + func testSKPaymentDiscountFromMapMissingTimestamp() { + if #available(iOS 12.2, *) { + let invalidValues: [Any?] = [NSNull(), "", -1] + for value in invalidValues { + let discountMap: [String: Any?] = [ + "identifier": "payment_discount_identifier", + "keyIdentifier": "payment_discount_key_identifier", + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": "this is an encrypted signature", + "timestamp": value, + ] + var error: NSString? + let _ = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap as [String: Any], withError: &error) + + XCTAssertNotNil(error) + XCTAssertEqual( + error, "When specifying a payment discount the 'timestamp' field is mandatory.") + } + } + } + + func testSKPaymentDiscountFromMapOverflowingTimestamp() throws { + if #available(iOS 12.2, *) { + let discountMap: [String: Any] = [ + "identifier": "payment_discount_identifier", + "keyIdentifier": "payment_discount_key_identifier", + "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", + "signature": "this is an encrypted signature", + "timestamp": 1_665_044_583_595, // timestamp 2022 Oct + ] + var error: NSString? + let paymentDiscount = FIAObjectTranslator.getSKPaymentDiscount( + fromMap: discountMap, withError: &error) + XCTAssertNil(error) + + let unwrappedPaymentDiscount = try XCTUnwrap(paymentDiscount) + let identifier = try XCTUnwrap(discountMap["identifier"] as? String) + XCTAssertEqual(unwrappedPaymentDiscount.identifier, identifier) + + let keyIdentifier = try XCTUnwrap(discountMap["keyIdentifier"] as? String) + XCTAssertEqual(unwrappedPaymentDiscount.keyIdentifier, keyIdentifier) + + let nonceString = try XCTUnwrap(discountMap["nonce"] as? String) + let nonce = try XCTUnwrap(UUID(uuidString: nonceString)) + XCTAssertEqual(unwrappedPaymentDiscount.nonce, nonce) + + let signature = try XCTUnwrap(discountMap["signature"] as? String) + XCTAssertEqual(unwrappedPaymentDiscount.signature, signature) + + let timestamp = try XCTUnwrap(discountMap["timestamp"] as? Int) + XCTAssertEqual(unwrappedPaymentDiscount.timestamp as? Int, timestamp) + } + } + + func testSKPaymentDiscountConvertToPigeon() throws { + if #available(iOS 12.2, *) { + var error: NSString? + let paymentDiscount = try XCTUnwrap( + FIAObjectTranslator.getSKPaymentDiscount( + fromMap: paymentDiscountMap, withError: &error)) + let paymentDiscountPigeon = try XCTUnwrap( + FIAObjectTranslator.convertPaymentDiscount( + toPigeon: paymentDiscount)) + + XCTAssertNotNil(paymentDiscountPigeon) + XCTAssertEqual(paymentDiscount.identifier, paymentDiscountPigeon.identifier) + XCTAssertEqual(paymentDiscount.keyIdentifier, paymentDiscount.keyIdentifier) + XCTAssertEqual(paymentDiscount.nonce, UUID(uuidString: paymentDiscountPigeon.nonce)) + XCTAssertEqual(paymentDiscount.signature, paymentDiscountPigeon.signature) + + let paymentDiscountTimestamp = paymentDiscount.timestamp as? Int + let paymentDiscountPigeonTimestamp = paymentDiscountPigeon.timestamp + + XCTAssertEqual(paymentDiscountTimestamp, paymentDiscountPigeonTimestamp) + } + } + + func testSKErrorConvertToPigeon() throws { + let error = NSError(domain: SKErrorDomain, code: 3, userInfo: ["key": 42]) + let msg = SKErrorMessage.make( + withCode: 3, domain: SKErrorDomain, userInfo: ["key": 42] as [String: Any]) + let skerror = try XCTUnwrap(FIAObjectTranslator.convertSKError(toPigeon: error)) + + XCTAssertEqual(skerror.domain, msg.domain) + XCTAssertEqual(skerror.code, msg.code) + + let skerrorUserInfo = skerror.userInfo + let msgUserInfo = try XCTUnwrap(msg.userInfo) + + XCTAssertEqual(skerrorUserInfo as NSDictionary?, msgUserInfo as NSDictionary) + } + + func testSKPaymentConvertToPigeon() throws { + if #available(iOS 12.2, *) { + let payment = FIAObjectTranslator.getSKMutablePayment(fromMap: paymentMap) + let msg = try XCTUnwrap(FIAObjectTranslator.convertPayment(toPigeon: payment)) + let msgRequestData = try XCTUnwrap(msg.requestData) + + XCTAssertEqual(payment.productIdentifier, msg.productIdentifier) + XCTAssertEqual(payment.requestData, msgRequestData.data(using: .utf8)) + XCTAssertEqual(payment.quantity, msg.quantity) + XCTAssertEqual(payment.applicationUsername, msg.applicationUsername) + XCTAssertEqual(payment.simulatesAskToBuyInSandbox, msg.simulatesAskToBuyInSandbox) + } + } + + func testSKPaymentTransactionConvertToPigeon() throws { + let paymentTransaction = SKPaymentTransactionStub(map: transactionMap) + let msg = FIAObjectTranslator.convertTransaction(toPigeon: paymentTransaction) + + let unwrappedMsg = try XCTUnwrap(msg) + let unwrappedTimeStamp = try XCTUnwrap(unwrappedMsg.transactionTimeStamp) + + XCTAssertEqual(unwrappedMsg.transactionState, SKPaymentTransactionStateMessage.purchasing) + XCTAssertEqual( + paymentTransaction.transactionDate, + Date(timeIntervalSince1970: TimeInterval(truncating: unwrappedTimeStamp))) + XCTAssertEqual(paymentTransaction.transactionIdentifier, unwrappedMsg.transactionIdentifier) + } + + func testSKProductResponseCovertToPigeon() throws { + let response = SKProductsResponseStub(map: productResponseMap) + let responseMsg = FIAObjectTranslator.convertProductsResponse(toPigeon: response) + let unwrappedMsg = try XCTUnwrap(responseMsg) + + let products = try XCTUnwrap(unwrappedMsg.products) + XCTAssertEqual(products.count, 1) + + let invalidProductIdentifiers = try XCTUnwrap(unwrappedMsg.invalidProductIdentifiers) + XCTAssertTrue(invalidProductIdentifiers.isEmpty) + + let productMsg = try XCTUnwrap(unwrappedMsg.products?.first) + XCTAssertEqual(productMsg.productIdentifier, "123") + XCTAssertEqual(productMsg.localizedTitle, "title") + XCTAssertEqual(productMsg.localizedDescription, "des") + XCTAssertEqual(productMsg.subscriptionGroupIdentifier, "com.group") + + let localeMsg = try XCTUnwrap(productMsg.priceLocale) + XCTAssertEqual(localeMsg.countryCode, "") + XCTAssertEqual(localeMsg.currencyCode, "") + XCTAssertEqual(localeMsg.currencySymbol, "\u{00a4}") + + let subPeriod = try XCTUnwrap(productMsg.subscriptionPeriod) + XCTAssertEqual(subPeriod.unit, SKSubscriptionPeriodUnitMessage.day) + XCTAssertEqual(subPeriod.numberOfUnits, 0) + + let introDiscount = try XCTUnwrap(productMsg.introductoryPrice) + XCTAssertEqual(introDiscount.price, "1") + XCTAssertEqual(introDiscount.numberOfPeriods, 1) + XCTAssertEqual(introDiscount.paymentMode, SKProductDiscountPaymentModeMessage.payUpFront) + + let discounts = try XCTUnwrap(productMsg.discounts) + XCTAssertEqual(discounts.count, 1) + } +} From 63be3eabc3d652663ed61b086a456062c1cbf84a Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 14:58:55 -0700 Subject: [PATCH 42/53] . --- .../macos/Runner.xcodeproj/project.pbxproj | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index fbb96195b8a..5c6eba02d3a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,17 +27,17 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + F20387652CBF1B79000AF71F /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */; }; + F20387662CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */; }; + F20387672CBF1B79000AF71F /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387642CBF1B79000AF71F /* TranslatorTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */; }; - F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */; }; - F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; - F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 045C0F7D19875EDA98DF0B7F /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ @@ -95,18 +95,18 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TranslatorTests.swift; sourceTree = ""; }; + F20387642CBF1B79000AF71F /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; - F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKitTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/StoreKitTest.framework; sourceTree = DEVELOPER_DIR; }; F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; - F2D527252C583C1C00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F79BDC152905FC0500E3999D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../../shared/RunnerTests/Info.plist; sourceTree = ""; }; F79BDC1B2905FC3200E3999D /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; @@ -228,9 +228,9 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */, - F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */, - F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, + F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */, + F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */, + F20387642CBF1B79000AF71F /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */, @@ -477,14 +477,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F20387652CBF1B79000AF71F /* StoreKit2TranslatorTests.swift in Sources */, + F20387662CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, + F20387672CBF1B79000AF71F /* TranslatorTests.swift in Sources */, F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */, F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, - F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, - F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, - F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */, ); From edead37969ef2b17006522c72f2cd907fb193485 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 15:14:06 -0700 Subject: [PATCH 43/53] -m "revert" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 63be3eabc3d652663ed61b086a456062c1cbf84a. :wq ç --- .../macos/Runner.xcodeproj/project.pbxproj | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 5c6eba02d3a..fbb96195b8a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,17 +27,17 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - F20387652CBF1B79000AF71F /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */; }; - F20387662CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */; }; - F20387672CBF1B79000AF71F /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F20387642CBF1B79000AF71F /* TranslatorTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */; }; + F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */; }; + F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; + F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 045C0F7D19875EDA98DF0B7F /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ @@ -95,18 +95,18 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TranslatorTests.swift; sourceTree = ""; }; - F20387642CBF1B79000AF71F /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; + F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKitTest.framework; path = Platforms/MacOSX.platform/Developer/Library/Frameworks/StoreKitTest.framework; sourceTree = DEVELOPER_DIR; }; F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PaymentQueueTests.swift; path = ../../shared/RunnerTests/PaymentQueueTests.swift; sourceTree = ""; }; + F2D527252C583C1C00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; F700DD0228E652A10004836B /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F79BDC152905FC0500E3999D /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../../shared/RunnerTests/Info.plist; sourceTree = ""; }; F79BDC1B2905FC3200E3999D /* Stubs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Stubs.m; path = ../../shared/RunnerTests/Stubs.m; sourceTree = ""; }; @@ -228,9 +228,9 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F20387622CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */, - F20387632CBF1B79000AF71F /* StoreKit2TranslatorTests.swift */, - F20387642CBF1B79000AF71F /* TranslatorTests.swift */, + F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */, + F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */, + F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */, @@ -477,14 +477,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - F20387652CBF1B79000AF71F /* StoreKit2TranslatorTests.swift in Sources */, - F20387662CBF1B79000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, - F20387672CBF1B79000AF71F /* TranslatorTests.swift in Sources */, F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */, F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, + F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, + F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, + F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */, ); From 3ece0be10dbc6a0763bb8f8f714bb50064dcb62b Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 15:18:26 -0700 Subject: [PATCH 44/53] Revert "change from symlink" This reverts commit 192b0041c3e5ca86ca0fe956b45f4cbbbf83f2b1. --- .../InAppPurchaseStoreKit2PluginTests.swift | 230 +------- .../StoreKit2TranslatorTests.swift | 83 +-- .../macos/RunnerTests/TranslatorTests.swift | 552 +----------------- 3 files changed, 3 insertions(+), 862 deletions(-) mode change 100644 => 120000 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift mode change 100644 => 120000 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift mode change 100644 => 120000 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift deleted file mode 100644 index c2fcfbdfedf..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/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/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift new file mode 120000 index 00000000000..82d2f50c86b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift deleted file mode 100644 index 6fbd1f8444f..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/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/example/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift new file mode 120000 index 00000000000..ae4348b7893 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/StoreKit2TranslatorTests.swift \ No newline at end of file diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift deleted file mode 100644 index 204b2f2dbfc..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift +++ /dev/null @@ -1,551 +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 StoreKit -import XCTest - -@testable import in_app_purchase_storekit - -final class ObjectTranslatorTest: XCTestCase { - private var periodMap: [String: Any] { - ["numberOfUnits": 0, "unit": 0] - } - private var discountMap: [String: Any] { - var map: [String: Any] = [ - "price": "1", - "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), - "numberOfPeriods": 1, - "subscriptionPeriod": periodMap, - "paymentMode": 1, - ] - if #available(iOS 12.2, *) { - map["identifier"] = "test offer id" - // Type is being instantiated like this because of Swift naming weirdness - let type: SKProductDiscount.`Type` = .introductory - map["type"] = type.rawValue - } - return map - } - private var discountMissingIdentifierMap: [String: Any] { - [ - "price": "1", - "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), - "numberOfPeriods": 1, - "subscriptionPeriod": periodMap, - "paymentMode": 1, - "identifier": NSNull(), - "type": 0, - ] - } - private var productMap: [String: Any] { - var map: [String: Any] = [ - "price": "1", - "priceLocale": FIAObjectTranslator.getMapFrom(NSLocale.system), - "productIdentifier": "123", - "localizedTitle": "title", - "localizedDescription": "des", - "subscriptionPeriod": periodMap, - "introductoryPrice": discountMap, - "subscriptionGroupIdentifier": "com.group", - ] - if #available(iOS 12.2, *) { - map["discounts"] = [discountMap] - } - return map - } - private var productResponseMap: [String: Any] { - ["products": [productMap], "invalidProductIdentifiers": []] - } - private var paymentMap: [String: Any] { - [ - "productIdentifier": "123", - "requestData": "abcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefghabcdefgh", - "quantity": 2, - "applicationUsername": "app user name", - "simulatesAskToBuyInSandbox": false, - ] - } - private var paymentDiscountMap: [String: Any] { - [ - "identifier": "payment_discount_identifier", - "keyIdentifier": "payment_discount_key_identifier", - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": "this is an encrypted signature", - "timestamp": Int(Date().timeIntervalSince1970), - ] - } - private var transactionMap: [String: Any] { - [ - "transactionIdentifier": "567", - "transactionState": SKPaymentTransactionState.purchasing.rawValue, - "payment": NSNull(), - "error": FIAObjectTranslator.getMapFrom( - NSError(domain: "test_stub", code: 123, userInfo: [:])), - "transactionTimeStamp": Int(Date().timeIntervalSince1970), - "originalTransaction": originalTransactionMap, - ] - } - private var originalTransactionMap: [String: Any] { - [ - "transactionIdentifier": "567", - "transactionState": SKPaymentTransactionState.purchasing.rawValue, - "payment": NSNull(), - "error": FIAObjectTranslator.getMapFrom( - NSError(domain: "test_stub", code: 123, userInfo: [:])), - "transactionTimeStamp": Int(Date().timeIntervalSince1970), - "originalTransaction": NSNull(), - ] - } - private var errorMap: [String: Any] { - [ - "code": 123, - "domain": "test_domain", - "userInfo": ["key": "value"], - ] - } - private var storefrontMap: [String: Any] { - [ - "countryCode": "USA", - "identifier": "unique_identifier", - ] - } - private var storefrontAndPaymentTransactionMap: [String: Any] { - [ - "storefront": storefrontMap, - "transaction": transactionMap, - ] - } - - func testSKProductSubscriptionPeriodStubToMap() { - let period = SKProductSubscriptionPeriodStub(map: periodMap) - let map = FIAObjectTranslator.getMapFrom(period) - - XCTAssertEqual(map as NSDictionary, periodMap as NSDictionary) - } - - func testSKProductDiscountStubToMap() { - let discount = SKProductDiscountStub(map: discountMap) - let map = FIAObjectTranslator.getMapFrom(discount) - - XCTAssertEqual(map as NSDictionary, discountMap as NSDictionary) - } - - func testProductToMap() { - let product = SKProductStub(map: productMap) - let map = FIAObjectTranslator.getMapFrom(product) - - XCTAssertEqual(map as NSDictionary, productMap as NSDictionary) - } - - func testProductResponseToMap() { - let response = SKProductsResponseStub(map: productResponseMap) - let map = FIAObjectTranslator.getMapFrom(response) - - XCTAssertEqual(map as NSDictionary, productResponseMap as NSDictionary) - } - - func testPaymentToMap() { - let payment = FIAObjectTranslator.getSKMutablePayment(fromMap: paymentMap) - let map = FIAObjectTranslator.getMapFrom(payment) - - XCTAssertEqual(map as NSDictionary, paymentMap as NSDictionary) - } - - func testPaymentTransactionToMap() { - let paymentTransaction = SKPaymentTransactionStub(map: transactionMap) - let map = FIAObjectTranslator.getMapFrom(paymentTransaction) - - XCTAssertEqual(map as NSDictionary, transactionMap as NSDictionary) - } - - func testError() { - let error = NSErrorStub(map: errorMap) - let map = FIAObjectTranslator.getMapFrom(error) - - XCTAssertEqual(map as NSDictionary, errorMap as NSDictionary) - } - - func testErrorWithNSNumberAsUserInfo() { - let error = NSError(domain: SKErrorDomain, code: 3, userInfo: ["key": 42]) - let expectedMap: [String: Any] = [ - "domain": SKErrorDomain, - "code": 3, - "userInfo": ["key": 42], - ] - let map = FIAObjectTranslator.getMapFrom(error) - - XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) - } - - func testErrorWithMultipleUnderlyingErrors() { - let underlyingErrorOne = NSError(domain: SKErrorDomain, code: 2, userInfo: nil) - let underlyingErrorTwo = NSError(domain: SKErrorDomain, code: 1, userInfo: nil) - let mainError = NSError( - domain: SKErrorDomain, - code: 3, - userInfo: ["underlyingErrors": [underlyingErrorOne, underlyingErrorTwo]] - ) - let expectedMap: [String: Any] = [ - "domain": SKErrorDomain, - "code": 3, - "userInfo": [ - "underlyingErrors": [ - ["domain": SKErrorDomain, "code": 2, "userInfo": [:]], - ["domain": SKErrorDomain, "code": 1, "userInfo": [:]], - ] - ], - ] - let map = FIAObjectTranslator.getMapFrom(mainError) - - XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) - } - - func testErrorWithNestedUnderlyingError() { - let underlyingError = NSError(domain: SKErrorDomain, code: 2, userInfo: nil) - let mainError = NSError( - domain: SKErrorDomain, - code: 3, - userInfo: ["nesting": ["underlyingError": underlyingError]] - ) - let expectedMap: [String: Any] = [ - "domain": SKErrorDomain, - "code": 3, - "userInfo": [ - "nesting": [ - "underlyingError": ["domain": SKErrorDomain, "code": 2, "userInfo": [:]] - ] - ], - ] - let map = FIAObjectTranslator.getMapFrom(mainError) - - XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) - } - - func testErrorWithUnsupportedUserInfo() { - let error = NSError( - domain: SKErrorDomain, - code: 3, - userInfo: ["user_info": NSObject()] - ) - let expectedMap: [String: Any] = [ - "domain": SKErrorDomain, - "code": 3, - "userInfo": [ - "user_info": String( - format: """ - Unable to encode native userInfo object of type %@ to map. \ - Please submit an issue at https://github.com/flutter/flutter/issues/new \ - with the title "[in_app_purchase_storekit] Unable to encode userInfo of type %@\" \ - and add reproduction steps and the error details in the description field. - """, - NSStringFromClass(NSObject.self), NSStringFromClass(NSObject.self) - ) - ], - ] - let map = FIAObjectTranslator.getMapFrom(error) - - XCTAssertEqual(map as NSDictionary, expectedMap as NSDictionary) - } - - func testLocaleToMap() { - let system = Locale(identifier: "en_US") - let map = FIAObjectTranslator.getMapFrom(system) - - XCTAssertEqual(map["currencySymbol"] as? String, system.currencySymbol) - XCTAssertEqual(map["countryCode"] as? String, system.regionCode) - } - - func testSKStorefrontToMap() { - if #available(iOS 13.0, *) { - let storefront = SKStorefrontStub(map: storefrontMap) - let map = FIAObjectTranslator.getMapFrom(storefront) - - XCTAssertEqual(map as NSDictionary, storefrontMap as NSDictionary) - } - } - - func testSKStorefrontAndSKPaymentTransactionToMap() { - if #available(iOS 13.0, *) { - let storefront = SKStorefrontStub(map: storefrontMap) - let transaction = SKPaymentTransactionStub(map: transactionMap) - let map = FIAObjectTranslator.getMapFrom(storefront, andSKPaymentTransaction: transaction) - - XCTAssertEqual(map as NSDictionary, storefrontAndPaymentTransactionMap as NSDictionary) - } - } - - func testSKPaymentDiscountFromMap() throws { - if #available(iOS 12.2, *) { - var error: NSString? - let paymentDiscount = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: paymentDiscountMap, withError: &error) - - XCTAssertNil(error) - - let unwrappedDiscount = try XCTUnwrap(paymentDiscount) - let unwrappedNonce = try XCTUnwrap(paymentDiscountMap["nonce"] as? String) - - XCTAssertEqual(unwrappedDiscount.identifier, paymentDiscountMap["identifier"] as? String) - XCTAssertEqual( - unwrappedDiscount.keyIdentifier, paymentDiscountMap["keyIdentifier"] as? String) - XCTAssertEqual( - unwrappedDiscount.nonce, UUID(uuidString: unwrappedNonce)) - XCTAssertEqual(unwrappedDiscount.signature, paymentDiscountMap["signature"] as? String) - XCTAssertEqual(unwrappedDiscount.timestamp as? Int, paymentDiscountMap["timestamp"] as? Int) - } - } - - func testSKPaymentDiscountFromMapMissingIdentifier() { - if #available(iOS 12.2, *) { - let invalidValues: [Any?] = [NSNull(), 1, ""] - for value in invalidValues { - let discountMap: [String: Any?] = [ - "identifier": value, - "keyIdentifier": "payment_discount_key_identifier", - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": "this is an encrypted signature", - "timestamp": Int(Date().timeIntervalSince1970), - ] - var error: NSString? - let _ = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap as [String: Any], withError: &error) - - XCTAssertNotNil(error) - XCTAssertEqual( - error, "When specifying a payment discount the 'identifier' field is mandatory.") - } - } - } - - func testGetMapFromSKProductDiscountMissingIdentifier() { - if #available(iOS 12.2, *) { - let discount = SKProductDiscountStub(map: discountMissingIdentifierMap) - let map = FIAObjectTranslator.getMapFrom(discount) - - XCTAssertEqual(map as NSDictionary, discountMissingIdentifierMap as NSDictionary) - } - } - - func testSKPaymentDiscountFromMapMissingKeyIdentifier() { - if #available(iOS 12.2, *) { - let invalidValues: [Any?] = [NSNull(), 1, ""] - for value in invalidValues { - let discountMap: [String: Any?] = [ - "identifier": "payment_discount_identifier", - "keyIdentifier": value, - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": "this is an encrypted signature", - "timestamp": Int(Date().timeIntervalSince1970), - ] - var error: NSString? - let _ = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap as [String: Any], withError: &error) - - XCTAssertNotNil(error) - XCTAssertEqual( - error, "When specifying a payment discount the 'keyIdentifier' field is mandatory.") - } - } - } - - func testSKPaymentDiscountFromMapMissingNonce() { - if #available(iOS 12.2, *) { - let invalidValues: [Any?] = [NSNull(), 1, ""] - for value in invalidValues { - let discountMap: [String: Any?] = [ - "identifier": "payment_discount_identifier", - "keyIdentifier": "payment_discount_key_identifier", - "nonce": value, - "signature": "this is an encrypted signature", - "timestamp": Int(Date().timeIntervalSince1970), - ] - var error: NSString? - let _ = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap as [String: Any], withError: &error) - - XCTAssertNotNil(error) - XCTAssertEqual(error, "When specifying a payment discount the 'nonce' field is mandatory.") - } - } - } - - func testSKPaymentDiscountFromMapMissingSignature() { - if #available(iOS 12.2, *) { - let invalidValues: [Any?] = [NSNull(), 1, ""] - for value in invalidValues { - let discountMap: [String: Any?] = [ - "identifier": "payment_discount_identifier", - "keyIdentifier": "payment_discount_key_identifier", - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": value, - "timestamp": Int(Date().timeIntervalSince1970), - ] - var error: NSString? - let _ = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap as [String: Any], withError: &error) - - XCTAssertNotNil(error) - XCTAssertEqual( - error, "When specifying a payment discount the 'signature' field is mandatory.") - } - } - } - - func testSKPaymentDiscountFromMapMissingTimestamp() { - if #available(iOS 12.2, *) { - let invalidValues: [Any?] = [NSNull(), "", -1] - for value in invalidValues { - let discountMap: [String: Any?] = [ - "identifier": "payment_discount_identifier", - "keyIdentifier": "payment_discount_key_identifier", - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": "this is an encrypted signature", - "timestamp": value, - ] - var error: NSString? - let _ = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap as [String: Any], withError: &error) - - XCTAssertNotNil(error) - XCTAssertEqual( - error, "When specifying a payment discount the 'timestamp' field is mandatory.") - } - } - } - - func testSKPaymentDiscountFromMapOverflowingTimestamp() throws { - if #available(iOS 12.2, *) { - let discountMap: [String: Any] = [ - "identifier": "payment_discount_identifier", - "keyIdentifier": "payment_discount_key_identifier", - "nonce": "d18981e0-9003-4365-98a2-4b90e3b62c52", - "signature": "this is an encrypted signature", - "timestamp": 1_665_044_583_595, // timestamp 2022 Oct - ] - var error: NSString? - let paymentDiscount = FIAObjectTranslator.getSKPaymentDiscount( - fromMap: discountMap, withError: &error) - XCTAssertNil(error) - - let unwrappedPaymentDiscount = try XCTUnwrap(paymentDiscount) - let identifier = try XCTUnwrap(discountMap["identifier"] as? String) - XCTAssertEqual(unwrappedPaymentDiscount.identifier, identifier) - - let keyIdentifier = try XCTUnwrap(discountMap["keyIdentifier"] as? String) - XCTAssertEqual(unwrappedPaymentDiscount.keyIdentifier, keyIdentifier) - - let nonceString = try XCTUnwrap(discountMap["nonce"] as? String) - let nonce = try XCTUnwrap(UUID(uuidString: nonceString)) - XCTAssertEqual(unwrappedPaymentDiscount.nonce, nonce) - - let signature = try XCTUnwrap(discountMap["signature"] as? String) - XCTAssertEqual(unwrappedPaymentDiscount.signature, signature) - - let timestamp = try XCTUnwrap(discountMap["timestamp"] as? Int) - XCTAssertEqual(unwrappedPaymentDiscount.timestamp as? Int, timestamp) - } - } - - func testSKPaymentDiscountConvertToPigeon() throws { - if #available(iOS 12.2, *) { - var error: NSString? - let paymentDiscount = try XCTUnwrap( - FIAObjectTranslator.getSKPaymentDiscount( - fromMap: paymentDiscountMap, withError: &error)) - let paymentDiscountPigeon = try XCTUnwrap( - FIAObjectTranslator.convertPaymentDiscount( - toPigeon: paymentDiscount)) - - XCTAssertNotNil(paymentDiscountPigeon) - XCTAssertEqual(paymentDiscount.identifier, paymentDiscountPigeon.identifier) - XCTAssertEqual(paymentDiscount.keyIdentifier, paymentDiscount.keyIdentifier) - XCTAssertEqual(paymentDiscount.nonce, UUID(uuidString: paymentDiscountPigeon.nonce)) - XCTAssertEqual(paymentDiscount.signature, paymentDiscountPigeon.signature) - - let paymentDiscountTimestamp = paymentDiscount.timestamp as? Int - let paymentDiscountPigeonTimestamp = paymentDiscountPigeon.timestamp - - XCTAssertEqual(paymentDiscountTimestamp, paymentDiscountPigeonTimestamp) - } - } - - func testSKErrorConvertToPigeon() throws { - let error = NSError(domain: SKErrorDomain, code: 3, userInfo: ["key": 42]) - let msg = SKErrorMessage.make( - withCode: 3, domain: SKErrorDomain, userInfo: ["key": 42] as [String: Any]) - let skerror = try XCTUnwrap(FIAObjectTranslator.convertSKError(toPigeon: error)) - - XCTAssertEqual(skerror.domain, msg.domain) - XCTAssertEqual(skerror.code, msg.code) - - let skerrorUserInfo = skerror.userInfo - let msgUserInfo = try XCTUnwrap(msg.userInfo) - - XCTAssertEqual(skerrorUserInfo as NSDictionary?, msgUserInfo as NSDictionary) - } - - func testSKPaymentConvertToPigeon() throws { - if #available(iOS 12.2, *) { - let payment = FIAObjectTranslator.getSKMutablePayment(fromMap: paymentMap) - let msg = try XCTUnwrap(FIAObjectTranslator.convertPayment(toPigeon: payment)) - let msgRequestData = try XCTUnwrap(msg.requestData) - - XCTAssertEqual(payment.productIdentifier, msg.productIdentifier) - XCTAssertEqual(payment.requestData, msgRequestData.data(using: .utf8)) - XCTAssertEqual(payment.quantity, msg.quantity) - XCTAssertEqual(payment.applicationUsername, msg.applicationUsername) - XCTAssertEqual(payment.simulatesAskToBuyInSandbox, msg.simulatesAskToBuyInSandbox) - } - } - - func testSKPaymentTransactionConvertToPigeon() throws { - let paymentTransaction = SKPaymentTransactionStub(map: transactionMap) - let msg = FIAObjectTranslator.convertTransaction(toPigeon: paymentTransaction) - - let unwrappedMsg = try XCTUnwrap(msg) - let unwrappedTimeStamp = try XCTUnwrap(unwrappedMsg.transactionTimeStamp) - - XCTAssertEqual(unwrappedMsg.transactionState, SKPaymentTransactionStateMessage.purchasing) - XCTAssertEqual( - paymentTransaction.transactionDate, - Date(timeIntervalSince1970: TimeInterval(truncating: unwrappedTimeStamp))) - XCTAssertEqual(paymentTransaction.transactionIdentifier, unwrappedMsg.transactionIdentifier) - } - - func testSKProductResponseCovertToPigeon() throws { - let response = SKProductsResponseStub(map: productResponseMap) - let responseMsg = FIAObjectTranslator.convertProductsResponse(toPigeon: response) - let unwrappedMsg = try XCTUnwrap(responseMsg) - - let products = try XCTUnwrap(unwrappedMsg.products) - XCTAssertEqual(products.count, 1) - - let invalidProductIdentifiers = try XCTUnwrap(unwrappedMsg.invalidProductIdentifiers) - XCTAssertTrue(invalidProductIdentifiers.isEmpty) - - let productMsg = try XCTUnwrap(unwrappedMsg.products?.first) - XCTAssertEqual(productMsg.productIdentifier, "123") - XCTAssertEqual(productMsg.localizedTitle, "title") - XCTAssertEqual(productMsg.localizedDescription, "des") - XCTAssertEqual(productMsg.subscriptionGroupIdentifier, "com.group") - - let localeMsg = try XCTUnwrap(productMsg.priceLocale) - XCTAssertEqual(localeMsg.countryCode, "") - XCTAssertEqual(localeMsg.currencyCode, "") - XCTAssertEqual(localeMsg.currencySymbol, "\u{00a4}") - - let subPeriod = try XCTUnwrap(productMsg.subscriptionPeriod) - XCTAssertEqual(subPeriod.unit, SKSubscriptionPeriodUnitMessage.day) - XCTAssertEqual(subPeriod.numberOfUnits, 0) - - let introDiscount = try XCTUnwrap(productMsg.introductoryPrice) - XCTAssertEqual(introDiscount.price, "1") - XCTAssertEqual(introDiscount.numberOfPeriods, 1) - XCTAssertEqual(introDiscount.paymentMode, SKProductDiscountPaymentModeMessage.payUpFront) - - let discounts = try XCTUnwrap(productMsg.discounts) - XCTAssertEqual(discounts.count, 1) - } -} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift new file mode 120000 index 00000000000..743854417c0 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/TranslatorTests.swift @@ -0,0 +1 @@ +../../shared/RunnerTests/TranslatorTests.swift \ No newline at end of file From 1681503ad51f945e418237ed9fb67b536f7deb66 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 15:34:18 -0700 Subject: [PATCH 45/53] remove dev team? --- .../example/macos/Runner.xcodeproj/project.pbxproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index fbb96195b8a..1c3c1d78844 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -760,7 +760,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -794,7 +793,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( @@ -826,7 +824,6 @@ CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = RunnerTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( From 1f6957fe2704d5af30e2d723688e48740ec35c67 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 15 Oct 2024 16:20:30 -0700 Subject: [PATCH 46/53] . --- .../macos/Runner.xcodeproj/project.pbxproj | 18 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 - .../InAppPurchaseStoreKit2PluginTests.swift | 230 +++++++++++++++++- .../StoreKit2TranslatorTests.swift | 83 ++++++- 4 files changed, 319 insertions(+), 13 deletions(-) mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift mode change 120000 => 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 1c3c1d78844..2bb59d0ed32 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,15 +27,14 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + F203876D2CBF302F000AF71F /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */; }; + F203876E2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */; }; - F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */; }; - F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */; }; F29F1CA02CADCF1E00CC1FEE /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */; }; F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2C3A7402BD9D33D000D35F2 /* Stubs.swift */; }; - F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; F2D5271E2C50645600C137C7 /* PaymentQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */; }; F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527252C583C1C00C137C7 /* TranslatorTests.swift */; }; F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F79BDC1B2905FC3200E3999D /* Stubs.m */; }; @@ -95,12 +94,12 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIATransactionCacheTests.swift; path = ../../shared/RunnerTests/FIATransactionCacheTests.swift; sourceTree = ""; }; - F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F29F1C9F2CADCF1E00CC1FEE /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; name = Configuration.storekit; path = ../../ios/Runner/Configuration.storekit; sourceTree = ""; }; F2C3A73F2BD9D33D000D35F2 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F2C3A7402BD9D33D000D35F2 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; @@ -128,7 +127,6 @@ buildActionMask = 2147483647; files = ( F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */, - F2D1274B2CB5DB61005FA2E5 /* StoreKitTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -228,8 +226,8 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F29F1C9B2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift */, - F29F1C9C2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift */, + F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */, + F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -481,10 +479,10 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, + F203876D2CBF302F000AF71F /* StoreKit2TranslatorTests.swift in Sources */, + F203876E2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, - F29F1C9D2CACC17B00CC1FEE /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, - F29F1C9E2CACC17B00CC1FEE /* StoreKit2TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */, ); diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 93c6fc09160..e78a0034ec1 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -39,7 +39,6 @@ Date: Tue, 15 Oct 2024 16:34:38 -0700 Subject: [PATCH 47/53] . --- .../Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index e78a0034ec1..5eb222feadb 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -38,7 +38,7 @@ Date: Wed, 16 Oct 2024 11:56:45 -0700 Subject: [PATCH 48/53] try without test files at all --- .../macos/Runner.xcodeproj/project.pbxproj | 8 - .../InAppPurchaseStoreKit2PluginTests.swift | 229 ------------------ .../StoreKit2TranslatorTests.swift | 82 ------- 3 files changed, 319 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 2bb59d0ed32..2526dafe686 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - F203876D2CBF302F000AF71F /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */; }; - F203876E2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; @@ -94,8 +92,6 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; @@ -226,8 +222,6 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F203876B2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift */, - F203876C2CBF302F000AF71F /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -479,8 +473,6 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, - F203876D2CBF302F000AF71F /* StoreKit2TranslatorTests.swift in Sources */, - F203876E2CBF302F000AF71F /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift deleted file mode 100644 index c2fcfbdfedf..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/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/macos/RunnerTests/StoreKit2TranslatorTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/RunnerTests/StoreKit2TranslatorTests.swift deleted file mode 100644 index 6fbd1f8444f..00000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/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) - } -} From f47a111d429b2458d7ba206a5aa18d5bbd67da0e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 16 Oct 2024 12:38:08 -0700 Subject: [PATCH 49/53] pls --- .../example/macos/Runner.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 2526dafe686..01c6b6cdada 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */; }; + F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; @@ -92,6 +94,8 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; @@ -222,6 +226,8 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( + F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */, + F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -473,6 +479,8 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, + F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */, + F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, From dbc619c08f35016baf6e89d45f5063cff6289264 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 16 Oct 2024 12:52:30 -0700 Subject: [PATCH 50/53] remove tests again --- .../macos/Runner.xcodeproj/project.pbxproj | 8 - .../InAppPurchaseStoreKit2PluginTests.swift | 229 ------------------ .../StoreKit2TranslatorTests.swift | 82 ------- 3 files changed, 319 deletions(-) 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 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 01c6b6cdada..2526dafe686 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */; }; - F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; @@ -94,8 +92,6 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; @@ -226,8 +222,6 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */, - F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -479,8 +473,6 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, - F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */, - F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, 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 c2fcfbdfedf..00000000000 --- 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 6fbd1f8444f..00000000000 --- 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) - } -} From 7eb3d9dc4a5e394df5fe7fa7348c49aaf2d2656c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 16 Oct 2024 13:11:35 -0700 Subject: [PATCH 51/53] Revert "remove tests again" This reverts commit dbc619c08f35016baf6e89d45f5063cff6289264. --- .../macos/Runner.xcodeproj/project.pbxproj | 8 + .../InAppPurchaseStoreKit2PluginTests.swift | 229 ++++++++++++++++++ .../StoreKit2TranslatorTests.swift | 82 +++++++ 3 files changed, 319 insertions(+) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 2526dafe686..01c6b6cdada 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,6 +27,8 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */; }; + F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; @@ -92,6 +94,8 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; + F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; @@ -222,6 +226,8 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( + F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */, + F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -473,6 +479,8 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, + F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */, + F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */, 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 new file mode 100644 index 00000000000..c2fcfbdfedf --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -0,0 +1,229 @@ +// 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 new file mode 100644 index 00000000000..6fbd1f8444f --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/StoreKit2TranslatorTests.swift @@ -0,0 +1,82 @@ +// 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) + } +} From 1af5bff3050a15dac63640c41e81a6b118aad43c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 16 Oct 2024 13:24:50 -0700 Subject: [PATCH 52/53] link both storekittest and storekit --- .../example/macos/Runner.xcodeproj/project.pbxproj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 01c6b6cdada..7fbaa822754 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -29,6 +29,8 @@ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */; }; F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */; }; + F219F19D2CC056C100142FC2 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; + F219F1A12CC0586700142FC2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F219F1A02CC0586700142FC2 /* StoreKit.framework */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */; }; F27694132C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */; }; @@ -96,6 +98,7 @@ B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; + F219F1A02CC0586700142FC2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; F27694122C49BF7B00277144 /* FIAPPaymentQueueDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = FIAPPaymentQueueDeleteTests.swift; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.swift; sourceTree = ""; }; @@ -126,7 +129,9 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + F219F1A12CC0586700142FC2 /* StoreKit.framework in Frameworks */, F8270DE1AEF80A8CF2C45688 /* Pods_RunnerTests.framework in Frameworks */, + F219F19D2CC056C100142FC2 /* StoreKitTest.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -149,6 +154,7 @@ 0B83E9AD591375BF30FD1CA9 /* Frameworks */ = { isa = PBXGroup; children = ( + F219F1A02CC0586700142FC2 /* StoreKit.framework */, F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */, AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */, 045C0F7D19875EDA98DF0B7F /* Pods_RunnerTests.framework */, From 32458c3c0c0e2102dbd3d1bdec4e1baa13089af6 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 16 Oct 2024 15:49:13 -0700 Subject: [PATCH 53/53] remove mac tests --- .../example/macos/Runner.xcodeproj/project.pbxproj | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index 7fbaa822754..0ea27593c2c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -27,8 +27,6 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */; }; - F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */; }; F219F19D2CC056C100142FC2 /* StoreKitTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F2D1274A2CB5DB61005FA2E5 /* StoreKitTest.framework */; }; F219F1A12CC0586700142FC2 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F219F1A02CC0586700142FC2 /* StoreKit.framework */; }; F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */; }; @@ -96,8 +94,6 @@ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; AEB2EC182EA43F26A351EE3E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B537BC9F2D936311267ABC65 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = InAppPurchaseStoreKit2PluginTests.swift; path = ../../shared/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift; sourceTree = ""; }; - F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = StoreKit2TranslatorTests.swift; path = ../../shared/RunnerTests/StoreKit2TranslatorTests.swift; sourceTree = ""; }; F219F1A02CC0586700142FC2 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; F24C45E32C409D87000C6C72 /* InAppPurchasePluginTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InAppPurchasePluginTests.swift; path = ../../shared/RunnerTests/InAppPurchasePluginTests.swift; sourceTree = ""; }; F27694082C4724B200277144 /* ProductRequestHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProductRequestHandlerTests.swift; path = ../../shared/RunnerTests/ProductRequestHandlerTests.swift; sourceTree = ""; }; @@ -232,8 +228,6 @@ F700DD0328E652A10004836B /* RunnerTests */ = { isa = PBXGroup; children = ( - F219F1972CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift */, - F219F1982CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift */, F2D527252C583C1C00C137C7 /* TranslatorTests.swift */, F2D5271D2C50645600C137C7 /* PaymentQueueTests.swift */, F27694182C49DBE800277144 /* FIATransactionCacheTests.swift */, @@ -485,8 +479,6 @@ F24C45E42C409D87000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F79BDC1C2905FC3200E3999D /* Stubs.m in Sources */, F27694092C4724B200277144 /* ProductRequestHandlerTests.swift in Sources */, - F219F1992CC04B0F00142FC2 /* StoreKit2TranslatorTests.swift in Sources */, - F219F19A2CC04B0F00142FC2 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2C3A7412BD9D33D000D35F2 /* Stubs.swift in Sources */, F2D527262C583C1C00C137C7 /* TranslatorTests.swift in Sources */, F27694192C49DBE800277144 /* FIATransactionCacheTests.swift in Sources */,