From fba8430189ff2d439cb85624b060fa600e822631 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 21 Aug 2024 12:53:08 -0700 Subject: [PATCH 01/18] compiles --- .../StoreKit2/InAppPurchasePlugin2.swift | 34 ++ .../StoreKit2/StoreKit2Translators.swift | 124 ++++++ .../darwin/Classes/StoreKit2/messages.g.swift | 348 ++++++++++++++++ .../Classes/StoreKit2/sk2_pigeon.g.swift | 348 ++++++++++++++++ .../in_app_purchase_storekit_platform.dart | 34 +- .../lib/src/sk2_pigeon.g.dart | 370 ++++++++++++++++++ .../sk2_appstore_wrapper.dart | 15 + .../sk2_product_wrapper.dart | 283 ++++++++++++++ .../lib/store_kit_2_wrappers.dart | 7 + .../pigeons/sk2_pigeon.dart | 142 +++++++ .../test/sk2_test_api.g.dart | 113 ++++++ 11 files changed, 1817 insertions(+), 1 deletion(-) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift new file mode 100644 index 000000000000..ca12635ec029 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift @@ -0,0 +1,34 @@ +// 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. + +@available(iOS 15.0, macOS 12.0, *) +extension InAppPurchasePlugin: InAppPurchase2API { + // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments + func canMakePayments() throws -> Bool { + return AppStore.canMakePayments + } + + // Pigeon method + func products( + identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], any Error>) -> Void + ) { + Task { + do { + let products = try await rawProducts(identifiers: identifiers) + let productMessages = products.map { product in + product.convertToPigeon() + } + completion(.success(productMessages)) + } catch { + completion(.failure(error)) + } + } + } + + // Raw storekit calls + + func rawProducts(identifiers: [String]) async throws -> [Product] { + return try await Product.products(for: identifiers) + } +} 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 new file mode 100644 index 000000000000..4ba82dad960a --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift @@ -0,0 +1,124 @@ +// 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 + +@available(iOS 15.0, macOS 12.0, *) +extension Product { + func convertToPigeon() -> SK2ProductMessage { + + return SK2ProductMessage( + id: id, + displayName: displayName, + description: description, + price: NSDecimalNumber(decimal: price).doubleValue, + displayPrice: displayPrice, + type: type.convertToPigeon(), + subscription: subscription?.convertToPigeon(), + priceLocale: priceFormatStyle.locale.convertToPigeon() + ) + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.ProductType { + func convertToPigeon() -> SK2ProductTypeMessage { + switch self { + case Product.ProductType.autoRenewable: + return SK2ProductTypeMessage.autoRenewable + case Product.ProductType.consumable: + return SK2ProductTypeMessage.consumable + case Product.ProductType.nonConsumable: + return SK2ProductTypeMessage.nonConsumable + case Product.ProductType.nonRenewable: + return SK2ProductTypeMessage.nonRenewable + default: + fatalError("An unknown ProductType was passed in") + } + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionInfo { + func convertToPigeon() -> SK2SubscriptionInfoMessage { + return SK2SubscriptionInfoMessage( + promotionalOffers: promotionalOffers.map({ $0.convertToPigeon() }), + subscriptionGroupID: subscriptionGroupID, + subscriptionPeriod: subscriptionPeriod.convertToPigeon()) + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionOffer { + func convertToPigeon() -> SK2SubscriptionOfferMessage { + return SK2SubscriptionOfferMessage( + /// ID is always `nil` for introductory offers and never `nil` for other offer types. + id: id, + price: NSDecimalNumber(decimal: price).doubleValue, + type: type.convertToPigeon(), + period: period.convertToPigeon(), + periodCount: Int64(periodCount), + paymentMode: paymentMode.convertToPigeon() + ) + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionOffer.OfferType { + func convertToPigeon() -> SK2SubscriptionOfferTypeMessage { + switch self { + case .introductory: + return SK2SubscriptionOfferTypeMessage.introductory + case .promotional: + return SK2SubscriptionOfferTypeMessage.promotional + default: + fatalError("An unknown OfferType was passed in") + } + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionPeriod { + func convertToPigeon() -> SK2SubscriptionPeriodMessage { + return SK2SubscriptionPeriodMessage( + value: Int64(value), + unit: unit.convertToPigeon()) + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionPeriod.Unit { + func convertToPigeon() -> SK2SubscriptionPeriodUnitMessage { + switch self { + case .day: + return SK2SubscriptionPeriodUnitMessage.day + case .week: + return SK2SubscriptionPeriodUnitMessage.week + case .month: + return SK2SubscriptionPeriodUnitMessage.month + case .year: + return SK2SubscriptionPeriodUnitMessage.year + @unknown default: + fatalError("unknown SubscriptionPeriodUnit encountered") + } + } +} + +@available(iOS 15.0, macOS 12.0, *) +extension Product.SubscriptionOffer.PaymentMode { + func convertToPigeon() -> SK2SubscriptionOfferPaymentModeMessage { + switch self { + case .freeTrial: + return SK2SubscriptionOfferPaymentModeMessage.freeTrial + case .payUpFront: + return SK2SubscriptionOfferPaymentModeMessage.payUpFront + case .payAsYouGo: + return SK2SubscriptionOfferPaymentModeMessage.payAsYouGo + default: + fatalError("Encountered an unknown PaymentMode") + } + } +} + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift new file mode 100644 index 000000000000..ab19a3381dad --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift @@ -0,0 +1,348 @@ +// 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. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +enum SK2ProductTypeMessage: Int { + /// A consumable in-app purchase. + case consumable = 0 + /// A non-consumable in-app purchase. + case nonConsumable = 1 + /// A non-renewing subscription. + case nonRenewable = 2 + /// An auto-renewable subscription. + case autoRenewable = 3 +} + +enum SK2SubscriptionOfferTypeMessage: Int { + case introductory = 0 + case promotional = 1 +} + +enum SK2SubscriptionOfferPaymentModeMessage: Int { + case payAsYouGo = 0 + case payUpFront = 1 + case freeTrial = 2 +} + +enum SK2SubscriptionPeriodUnitMessage: Int { + case day = 0 + case week = 1 + case month = 2 + case year = 3 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionOfferMessage { + var id: String? = nil + var price: Double + var type: SK2SubscriptionOfferTypeMessage + var period: SK2SubscriptionPeriodMessage + var periodCount: Int64 + var paymentMode: SK2SubscriptionOfferPaymentModeMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionOfferMessage? { + let id: String? = nilOrValue(list[0]) + let price = list[1] as! Double + let type = SK2SubscriptionOfferTypeMessage(rawValue: list[2] as! Int)! + let period = SK2SubscriptionPeriodMessage.fromList(list[3] as! [Any?])! + let periodCount = list[4] is Int64 ? list[4] as! Int64 : Int64(list[4] as! Int32) + let paymentMode = SK2SubscriptionOfferPaymentModeMessage(rawValue: list[5] as! Int)! + + return SK2SubscriptionOfferMessage( + id: id, + price: price, + type: type, + period: period, + periodCount: periodCount, + paymentMode: paymentMode + ) + } + func toList() -> [Any?] { + return [ + id, + price, + type.rawValue, + period.toList(), + periodCount, + paymentMode.rawValue, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionPeriodMessage { + /// The number of units that the period represents. + var value: Int64 + /// The unit of time that this period represents. + var unit: SK2SubscriptionPeriodUnitMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionPeriodMessage? { + let value = list[0] is Int64 ? list[0] as! Int64 : Int64(list[0] as! Int32) + let unit = SK2SubscriptionPeriodUnitMessage(rawValue: list[1] as! Int)! + + return SK2SubscriptionPeriodMessage( + value: value, + unit: unit + ) + } + func toList() -> [Any?] { + return [ + value, + unit.rawValue, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionInfoMessage { + /// An array of all the promotional offers configured for this subscription. + /// This should be List but pigeon doesnt support + /// null-safe generics. https://github.com/flutter/flutter/issues/97848 + var promotionalOffers: [SK2SubscriptionOfferMessage?] + /// The group identifier for this subscription. + var subscriptionGroupID: String + /// The duration that this subscription lasts before auto-renewing. + var subscriptionPeriod: SK2SubscriptionPeriodMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionInfoMessage? { + let promotionalOffers = list[0] as! [SK2SubscriptionOfferMessage?] + let subscriptionGroupID = list[1] as! String + let subscriptionPeriod = SK2SubscriptionPeriodMessage.fromList(list[2] as! [Any?])! + + return SK2SubscriptionInfoMessage( + promotionalOffers: promotionalOffers, + subscriptionGroupID: subscriptionGroupID, + subscriptionPeriod: subscriptionPeriod + ) + } + func toList() -> [Any?] { + return [ + promotionalOffers, + subscriptionGroupID, + subscriptionPeriod.toList(), + ] + } +} + +/// A Pigeon message class representing a Product +/// https://developer.apple.com/documentation/storekit/product +/// +/// Generated class from Pigeon that represents data sent in messages. +struct SK2ProductMessage { + /// The unique product identifier. + var id: String + /// The localized display name of the product, if it exists. + var displayName: String + /// The localized description of the product. + var description: String + /// The localized string representation of the product price, suitable for display. + var price: Double + /// The localized price of the product as a string. + var displayPrice: String + /// The types of in-app purchases. + var type: SK2ProductTypeMessage + /// The subscription information for an auto-renewable subscription. + var subscription: SK2SubscriptionInfoMessage? = nil + /// The currency and locale information for this product + var priceLocale: SK2PriceLocaleMessage + + static func fromList(_ list: [Any?]) -> SK2ProductMessage? { + let id = list[0] as! String + let displayName = list[1] as! String + let description = list[2] as! String + let price = list[3] as! Double + let displayPrice = list[4] as! String + let type = SK2ProductTypeMessage(rawValue: list[5] as! Int)! + var subscription: SK2SubscriptionInfoMessage? = nil + if let subscriptionList: [Any?] = nilOrValue(list[6]) { + subscription = SK2SubscriptionInfoMessage.fromList(subscriptionList) + } + let priceLocale = SK2PriceLocaleMessage.fromList(list[7] as! [Any?])! + + return SK2ProductMessage( + id: id, + displayName: displayName, + description: description, + price: price, + displayPrice: displayPrice, + type: type, + subscription: subscription, + priceLocale: priceLocale + ) + } + func toList() -> [Any?] { + return [ + id, + displayName, + description, + price, + displayPrice, + type.rawValue, + subscription?.toList(), + priceLocale.toList(), + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2PriceLocaleMessage { + var currencyCode: String + var currencySymbol: String + + static func fromList(_ list: [Any?]) -> SK2PriceLocaleMessage? { + let currencyCode = list[0] as! String + let currencySymbol = list[1] as! String + + return SK2PriceLocaleMessage( + currencyCode: currencyCode, + currencySymbol: currencySymbol + ) + } + func toList() -> [Any?] { + return [ + currencyCode, + currencySymbol, + ] + } +} + +private class InAppPurchase2APICodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) + case 129: + return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + case 130: + return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) + case 131: + return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) + case 132: + return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class InAppPurchase2APICodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? SK2PriceLocaleMessage { + super.writeByte(128) + super.writeValue(value.toList()) + } else if let value = value as? SK2ProductMessage { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionInfoMessage { + super.writeByte(130) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionOfferMessage { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionPeriodMessage { + super.writeByte(132) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class InAppPurchase2APICodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return InAppPurchase2APICodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return InAppPurchase2APICodecWriter(data: data) + } +} + +class InAppPurchase2APICodec: FlutterStandardMessageCodec { + static let shared = InAppPurchase2APICodec(readerWriter: InAppPurchase2APICodecReaderWriter()) +} + +/// 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) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class InAppPurchase2APISetup { + /// The codec used by InAppPurchase2API. + static var codec: FlutterStandardMessageCodec { InAppPurchase2APICodec.shared } + /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?) { + let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + canMakePaymentsChannel.setMessageHandler { _, reply in + do { + let result = try api.canMakePayments() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + canMakePaymentsChannel.setMessageHandler(nil) + } + let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + productsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let identifiersArg = args[0] as! [String] + api.products(identifiers: identifiersArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + productsChannel.setMessageHandler(nil) + } + } +} 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 new file mode 100644 index 000000000000..ab19a3381dad --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift @@ -0,0 +1,348 @@ +// 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. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation + +#if os(iOS) + import Flutter +#elseif os(macOS) + import FlutterMacOS +#else + #error("Unsupported platform.") +#endif + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details, + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)", + ] +} + +private func isNullish(_ value: Any?) -> Bool { + return value is NSNull || value == nil +} + +private func nilOrValue(_ value: Any?) -> T? { + if value is NSNull { return nil } + return value as! T? +} + +enum SK2ProductTypeMessage: Int { + /// A consumable in-app purchase. + case consumable = 0 + /// A non-consumable in-app purchase. + case nonConsumable = 1 + /// A non-renewing subscription. + case nonRenewable = 2 + /// An auto-renewable subscription. + case autoRenewable = 3 +} + +enum SK2SubscriptionOfferTypeMessage: Int { + case introductory = 0 + case promotional = 1 +} + +enum SK2SubscriptionOfferPaymentModeMessage: Int { + case payAsYouGo = 0 + case payUpFront = 1 + case freeTrial = 2 +} + +enum SK2SubscriptionPeriodUnitMessage: Int { + case day = 0 + case week = 1 + case month = 2 + case year = 3 +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionOfferMessage { + var id: String? = nil + var price: Double + var type: SK2SubscriptionOfferTypeMessage + var period: SK2SubscriptionPeriodMessage + var periodCount: Int64 + var paymentMode: SK2SubscriptionOfferPaymentModeMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionOfferMessage? { + let id: String? = nilOrValue(list[0]) + let price = list[1] as! Double + let type = SK2SubscriptionOfferTypeMessage(rawValue: list[2] as! Int)! + let period = SK2SubscriptionPeriodMessage.fromList(list[3] as! [Any?])! + let periodCount = list[4] is Int64 ? list[4] as! Int64 : Int64(list[4] as! Int32) + let paymentMode = SK2SubscriptionOfferPaymentModeMessage(rawValue: list[5] as! Int)! + + return SK2SubscriptionOfferMessage( + id: id, + price: price, + type: type, + period: period, + periodCount: periodCount, + paymentMode: paymentMode + ) + } + func toList() -> [Any?] { + return [ + id, + price, + type.rawValue, + period.toList(), + periodCount, + paymentMode.rawValue, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionPeriodMessage { + /// The number of units that the period represents. + var value: Int64 + /// The unit of time that this period represents. + var unit: SK2SubscriptionPeriodUnitMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionPeriodMessage? { + let value = list[0] is Int64 ? list[0] as! Int64 : Int64(list[0] as! Int32) + let unit = SK2SubscriptionPeriodUnitMessage(rawValue: list[1] as! Int)! + + return SK2SubscriptionPeriodMessage( + value: value, + unit: unit + ) + } + func toList() -> [Any?] { + return [ + value, + unit.rawValue, + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2SubscriptionInfoMessage { + /// An array of all the promotional offers configured for this subscription. + /// This should be List but pigeon doesnt support + /// null-safe generics. https://github.com/flutter/flutter/issues/97848 + var promotionalOffers: [SK2SubscriptionOfferMessage?] + /// The group identifier for this subscription. + var subscriptionGroupID: String + /// The duration that this subscription lasts before auto-renewing. + var subscriptionPeriod: SK2SubscriptionPeriodMessage + + static func fromList(_ list: [Any?]) -> SK2SubscriptionInfoMessage? { + let promotionalOffers = list[0] as! [SK2SubscriptionOfferMessage?] + let subscriptionGroupID = list[1] as! String + let subscriptionPeriod = SK2SubscriptionPeriodMessage.fromList(list[2] as! [Any?])! + + return SK2SubscriptionInfoMessage( + promotionalOffers: promotionalOffers, + subscriptionGroupID: subscriptionGroupID, + subscriptionPeriod: subscriptionPeriod + ) + } + func toList() -> [Any?] { + return [ + promotionalOffers, + subscriptionGroupID, + subscriptionPeriod.toList(), + ] + } +} + +/// A Pigeon message class representing a Product +/// https://developer.apple.com/documentation/storekit/product +/// +/// Generated class from Pigeon that represents data sent in messages. +struct SK2ProductMessage { + /// The unique product identifier. + var id: String + /// The localized display name of the product, if it exists. + var displayName: String + /// The localized description of the product. + var description: String + /// The localized string representation of the product price, suitable for display. + var price: Double + /// The localized price of the product as a string. + var displayPrice: String + /// The types of in-app purchases. + var type: SK2ProductTypeMessage + /// The subscription information for an auto-renewable subscription. + var subscription: SK2SubscriptionInfoMessage? = nil + /// The currency and locale information for this product + var priceLocale: SK2PriceLocaleMessage + + static func fromList(_ list: [Any?]) -> SK2ProductMessage? { + let id = list[0] as! String + let displayName = list[1] as! String + let description = list[2] as! String + let price = list[3] as! Double + let displayPrice = list[4] as! String + let type = SK2ProductTypeMessage(rawValue: list[5] as! Int)! + var subscription: SK2SubscriptionInfoMessage? = nil + if let subscriptionList: [Any?] = nilOrValue(list[6]) { + subscription = SK2SubscriptionInfoMessage.fromList(subscriptionList) + } + let priceLocale = SK2PriceLocaleMessage.fromList(list[7] as! [Any?])! + + return SK2ProductMessage( + id: id, + displayName: displayName, + description: description, + price: price, + displayPrice: displayPrice, + type: type, + subscription: subscription, + priceLocale: priceLocale + ) + } + func toList() -> [Any?] { + return [ + id, + displayName, + description, + price, + displayPrice, + type.rawValue, + subscription?.toList(), + priceLocale.toList(), + ] + } +} + +/// Generated class from Pigeon that represents data sent in messages. +struct SK2PriceLocaleMessage { + var currencyCode: String + var currencySymbol: String + + static func fromList(_ list: [Any?]) -> SK2PriceLocaleMessage? { + let currencyCode = list[0] as! String + let currencySymbol = list[1] as! String + + return SK2PriceLocaleMessage( + currencyCode: currencyCode, + currencySymbol: currencySymbol + ) + } + func toList() -> [Any?] { + return [ + currencyCode, + currencySymbol, + ] + } +} + +private class InAppPurchase2APICodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 128: + return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) + case 129: + return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + case 130: + return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) + case 131: + return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) + case 132: + return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) + default: + return super.readValue(ofType: type) + } + } +} + +private class InAppPurchase2APICodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? SK2PriceLocaleMessage { + super.writeByte(128) + super.writeValue(value.toList()) + } else if let value = value as? SK2ProductMessage { + super.writeByte(129) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionInfoMessage { + super.writeByte(130) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionOfferMessage { + super.writeByte(131) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionPeriodMessage { + super.writeByte(132) + super.writeValue(value.toList()) + } else { + super.writeValue(value) + } + } +} + +private class InAppPurchase2APICodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return InAppPurchase2APICodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return InAppPurchase2APICodecWriter(data: data) + } +} + +class InAppPurchase2APICodec: FlutterStandardMessageCodec { + static let shared = InAppPurchase2APICodec(readerWriter: InAppPurchase2APICodecReaderWriter()) +} + +/// 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) +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class InAppPurchase2APISetup { + /// The codec used by InAppPurchase2API. + static var codec: FlutterStandardMessageCodec { InAppPurchase2APICodec.shared } + /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?) { + let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + canMakePaymentsChannel.setMessageHandler { _, reply in + do { + let result = try api.canMakePayments() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + canMakePaymentsChannel.setMessageHandler(nil) + } + let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + productsChannel.setMessageHandler { message, reply in + let args = message as! [Any?] + let identifiersArg = args[0] as! [String] + api.products(identifiers: identifiersArg) { result in + switch result { + case .success(let res): + reply(wrapResult(res)) + case .failure(let error): + reply(wrapError(error)) + } + } + } + } else { + productsChannel.setMessageHandler(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 713a8c909e69..79a30df2cc3f 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 @@ -9,6 +9,7 @@ import 'package:flutter/services.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../in_app_purchase_storekit.dart'; +import '../store_kit_2_wrappers.dart'; import '../store_kit_wrappers.dart'; /// [IAPError.code] code for failed purchases. @@ -17,6 +18,8 @@ const String kPurchaseErrorCode = 'purchase_error'; /// Indicates store front is Apple AppStore. const String kIAPSource = 'app_store'; +const bool useStoreKit2 = true; + /// An [InAppPurchasePlatform] that wraps StoreKit. /// /// This translates various `StoreKit` calls and responses into the @@ -65,7 +68,12 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { } @override - Future isAvailable() => SKPaymentQueueWrapper.canMakePayments(); + Future isAvailable() { + if (useStoreKit2) { + return AppStore().canMakePayments(); + } + return SKPaymentQueueWrapper.canMakePayments(); + } @override Future buyNonConsumable({required PurchaseParam purchaseParam}) async { @@ -119,6 +127,30 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future queryProductDetails( Set identifiers) async { + if (useStoreKit2) { + List products = []; + Set invalidProductIdentifiers; + PlatformException? exception; + try { + products = await SK2Product.products(identifiers.toList()); + // Storekit 2 no longer automatically returns a list of invalid identifiers, + // so get the difference between given identifiers and returned products + invalidProductIdentifiers = identifiers.difference( + products.map((SK2Product product) => product.id).toSet()); + } on PlatformException catch (e) { + exception = e; + invalidProductIdentifiers = identifiers; + } + List productDetails; + productDetails = products + .map((SK2Product productWrapper) => + AppStoreProduct2Details.fromSK2Product(productWrapper)) + .toList(); + final ProductDetailsResponse response = ProductDetailsResponse( + productDetails: productDetails, + notFoundIDs: invalidProductIdentifiers.toList()); + return response; + } final SKRequestMaker requestMaker = SKRequestMaker(); SkProductResponseWrapper response; PlatformException? exception; 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 new file mode 100644 index 000000000000..1ec4a0831740 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart @@ -0,0 +1,370 @@ +// 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. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +PlatformException _createConnectionError(String channelName) { + return PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel: "$channelName".', + ); +} + +List wrapResponse({Object? result, PlatformException? error, bool empty = false}) { + if (empty) { + return []; + } + if (error == null) { + return [result]; + } + return [error.code, error.message, error.details]; +} + +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, +} + +enum SK2SubscriptionOfferTypeMessage { + introductory, + promotional, +} + +enum SK2SubscriptionOfferPaymentModeMessage { + payAsYouGo, + payUpFront, + freeTrial, +} + +enum SK2SubscriptionPeriodUnitMessage { + day, + week, + month, + year, +} + +class SK2SubscriptionOfferMessage { + SK2SubscriptionOfferMessage({ + this.id, + required this.price, + required this.type, + required this.period, + required this.periodCount, + required this.paymentMode, + }); + + String? id; + + double price; + + SK2SubscriptionOfferTypeMessage type; + + SK2SubscriptionPeriodMessage period; + + int periodCount; + + SK2SubscriptionOfferPaymentModeMessage paymentMode; + + Object encode() { + return [ + id, + price, + type.index, + period.encode(), + periodCount, + paymentMode.index, + ]; + } + + static SK2SubscriptionOfferMessage decode(Object result) { + result as List; + return SK2SubscriptionOfferMessage( + id: result[0] as String?, + price: result[1]! as double, + type: SK2SubscriptionOfferTypeMessage.values[result[2]! as int], + period: SK2SubscriptionPeriodMessage.decode(result[3]! as List), + periodCount: result[4]! as int, + paymentMode: SK2SubscriptionOfferPaymentModeMessage.values[result[5]! as int], + ); + } +} + +class SK2SubscriptionPeriodMessage { + SK2SubscriptionPeriodMessage({ + required this.value, + required this.unit, + }); + + /// The number of units that the period represents. + int value; + + /// The unit of time that this period represents. + SK2SubscriptionPeriodUnitMessage unit; + + Object encode() { + return [ + value, + unit.index, + ]; + } + + static SK2SubscriptionPeriodMessage decode(Object result) { + result as List; + return SK2SubscriptionPeriodMessage( + value: result[0]! as int, + unit: SK2SubscriptionPeriodUnitMessage.values[result[1]! as int], + ); + } +} + +class SK2SubscriptionInfoMessage { + SK2SubscriptionInfoMessage({ + required this.promotionalOffers, + required this.subscriptionGroupID, + required this.subscriptionPeriod, + }); + + /// An array of all the promotional offers configured for this subscription. + /// This should be List but pigeon doesnt support + /// null-safe generics. https://github.com/flutter/flutter/issues/97848 + List promotionalOffers; + + /// The group identifier for this subscription. + String subscriptionGroupID; + + /// The duration that this subscription lasts before auto-renewing. + SK2SubscriptionPeriodMessage subscriptionPeriod; + + Object encode() { + return [ + promotionalOffers, + subscriptionGroupID, + subscriptionPeriod.encode(), + ]; + } + + static SK2SubscriptionInfoMessage decode(Object result) { + result as List; + return SK2SubscriptionInfoMessage( + promotionalOffers: (result[0] as List?)!.cast(), + subscriptionGroupID: result[1]! as String, + subscriptionPeriod: SK2SubscriptionPeriodMessage.decode(result[2]! as List), + ); + } +} + +/// A Pigeon message class representing a Product +/// https://developer.apple.com/documentation/storekit/product +class SK2ProductMessage { + SK2ProductMessage({ + required this.id, + required this.displayName, + required this.description, + required this.price, + required this.displayPrice, + required this.type, + this.subscription, + required this.priceLocale, + }); + + /// The unique product identifier. + String id; + + /// The localized display name of the product, if it exists. + String displayName; + + /// The localized description of the product. + String description; + + /// The localized string representation of the product price, suitable for display. + double price; + + /// The localized price of the product as a string. + String displayPrice; + + /// The types of in-app purchases. + SK2ProductTypeMessage type; + + /// The subscription information for an auto-renewable subscription. + SK2SubscriptionInfoMessage? subscription; + + /// The currency and locale information for this product + SK2PriceLocaleMessage priceLocale; + + Object encode() { + return [ + id, + displayName, + description, + price, + displayPrice, + type.index, + subscription?.encode(), + priceLocale.encode(), + ]; + } + + static SK2ProductMessage decode(Object result) { + result as List; + return SK2ProductMessage( + id: result[0]! as String, + displayName: result[1]! as String, + description: result[2]! as String, + price: result[3]! as double, + displayPrice: result[4]! as String, + type: SK2ProductTypeMessage.values[result[5]! as int], + subscription: result[6] != null + ? SK2SubscriptionInfoMessage.decode(result[6]! as List) + : null, + priceLocale: SK2PriceLocaleMessage.decode(result[7]! as List), + ); + } +} + +class SK2PriceLocaleMessage { + SK2PriceLocaleMessage({ + required this.currencyCode, + required this.currencySymbol, + }); + + String currencyCode; + + String currencySymbol; + + Object encode() { + return [ + currencyCode, + currencySymbol, + ]; + } + + static SK2PriceLocaleMessage decode(Object result) { + result as List; + return SK2PriceLocaleMessage( + currencyCode: result[0]! as String, + currencySymbol: result[1]! as String, + ); + } +} + +class _InAppPurchase2APICodec extends StandardMessageCodec { + const _InAppPurchase2APICodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SK2PriceLocaleMessage) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is SK2ProductMessage) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionInfoMessage) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionOfferMessage) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionPeriodMessage) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SK2PriceLocaleMessage.decode(readValue(buffer)!); + case 129: + return SK2ProductMessage.decode(readValue(buffer)!); + case 130: + return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + case 131: + return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + case 132: + return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +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}) + : __pigeon_binaryMessenger = binaryMessenger; + final BinaryMessenger? __pigeon_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _InAppPurchase2APICodec(); + + Future canMakePayments() async { + const String __pigeon_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments'; + final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as bool?)!; + } + } + + Future> products(List identifiers) async { + const String __pigeon_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products'; + final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([identifiers]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as List?)!.cast(); + } + } +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart new file mode 100644 index 000000000000..e43f837452d8 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart @@ -0,0 +1,15 @@ +// 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 '../../store_kit_2_wrappers.dart'; + +InAppPurchase2API _hostApi = InAppPurchase2API(); + +/// Wrapper for StoreKit2's AppStore +/// (https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments) +final class AppStore { + Future canMakePayments() { + return _hostApi.canMakePayments(); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000000..51212210a36b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart @@ -0,0 +1,283 @@ +// 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 '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(); + +/// https://developer.apple.com/documentation/storekit/product/producttype +/// The types of in-app purchases. +enum SK2ProductType { + /// A consumable in-app purchase. + consumable, + + /// A non-consumable in-app purchase. + nonConsumable, + + /// A non-renewing subscription. + nonRenewable, + + /// An auto-renewable subscription. + autoRenewable; +} + +extension on SK2ProductTypeMessage { + /// Convert the equivalent pigeon class of [SK2ProductTypeMessage] into an instance of [SK2ProductType] + SK2ProductType convertFromPigeon() { + switch (this) { + case SK2ProductTypeMessage.autoRenewable: + return SK2ProductType.autoRenewable; + case SK2ProductTypeMessage.consumable: + return SK2ProductType.consumable; + case SK2ProductTypeMessage.nonConsumable: + return SK2ProductType.nonConsumable; + case SK2ProductTypeMessage.nonRenewable: + return SK2ProductType.nonRenewable; + } + } +} + +enum SK2SubscriptionOfferType { introductory, promotional } + +extension on SK2SubscriptionOfferTypeMessage { + SK2SubscriptionOfferType convertFromPigeon() { + switch (this) { + case SK2SubscriptionOfferTypeMessage.introductory: + return SK2SubscriptionOfferType.introductory; + case SK2SubscriptionOfferTypeMessage.promotional: + return SK2SubscriptionOfferType.promotional; + } + } +} + +class SK2SubscriptionOffer { + const SK2SubscriptionOffer({ + this.id, + required this.price, + required this.type, + required this.period, + required this.periodCount, + required this.paymentMode, + }); + final String? id; + final double price; + final SK2SubscriptionOfferType type; + final SK2SubscriptionPeriod period; + final int periodCount; + final SK2SubscriptionOfferPaymentMode paymentMode; +} + +extension on SK2SubscriptionOfferMessage { + SK2SubscriptionOffer convertFromPigeon() { + return SK2SubscriptionOffer( + id: id, + price: price, + type: type.convertFromPigeon(), + period: period.convertFromPigeon(), + periodCount: periodCount, + paymentMode: paymentMode.convertFromPigeon()); + } +} + +/// https://developer.apple.com/documentation/storekit/product/subscriptioninfo +/// Information about an auto-renewable subscription, such as its status, period, subscription group, and subscription offer details. +class SK2SubscriptionInfo { + /// Constructor + const SK2SubscriptionInfo({ + required this.subscriptionGroupID, + required this.promotionalOffers, + required this.subscriptionPeriod, + }); + + /// An array of all the promotional offers configured for this subscription. + final List promotionalOffers; + + /// The group identifier for this subscription. + final String subscriptionGroupID; + + /// The duration that this subscription lasts before auto-renewing. + final SK2SubscriptionPeriod subscriptionPeriod; +} + +extension on SK2SubscriptionInfoMessage { + SK2SubscriptionInfo convertFromPigeon() { + return SK2SubscriptionInfo( + subscriptionGroupID: subscriptionGroupID, + // Note that promotionalOffers should NOT be nullable, but is only declared + // so because of pigeon weirdness. There should be NO NULLS. + promotionalOffers: promotionalOffers + .whereType() + .map((SK2SubscriptionOfferMessage offer) => + offer.convertFromPigeon()) + .toList(), + subscriptionPeriod: subscriptionPeriod.convertFromPigeon()); + } +} + +/// https://developer.apple.com/documentation/storekit/product/subscriptionperiod?changes=latest_minor +/// Values that represent the duration of time between subscription renewals. +class SK2SubscriptionPeriod { + const SK2SubscriptionPeriod({required this.value, required this.unit}); + + /// The number of units that the period represents. + final int value; + + /// The unit of time that this period represents. + final SK2SubscriptionPeriodUnit unit; +} + +extension on SK2SubscriptionPeriodMessage { + SK2SubscriptionPeriod convertFromPigeon() { + return SK2SubscriptionPeriod(value: value, unit: unit.convertFromPigeon()); + } +} + +/// https://developer.apple.com/documentation/storekit/product/subscriptionperiod/3749576-unit +/// The increment of time for the subscription period. +enum SK2SubscriptionPeriodUnit { + /// A subscription period unit of a day. + day, + + /// A subscription period unit of a week. + week, + + /// A subscription period unit of a month. + month, + + /// A subscription period unit of a year. + year +} + +extension on SK2SubscriptionPeriodUnitMessage { + SK2SubscriptionPeriodUnit convertFromPigeon() { + switch (this) { + case SK2SubscriptionPeriodUnitMessage.day: + return SK2SubscriptionPeriodUnit.day; + case SK2SubscriptionPeriodUnitMessage.week: + return SK2SubscriptionPeriodUnit.week; + case SK2SubscriptionPeriodUnitMessage.month: + return SK2SubscriptionPeriodUnit.month; + case SK2SubscriptionPeriodUnitMessage.year: + return SK2SubscriptionPeriodUnit.year; + } + } +} + +/// https://developer.apple.com/documentation/storekit/product/subscriptionoffer/paymentmode +/// The payment modes for subscription offers that apply to a transaction. +enum SK2SubscriptionOfferPaymentMode { + /// A payment mode of a product discount that applies over a single billing period or multiple billing periods. + payAsYouGo, + + /// A payment mode of a product discount that applies the discount up front. + payUpFront, + + /// A payment mode of a product discount that indicates a free trial offer. + freeTrial; +} + +extension on SK2SubscriptionOfferPaymentModeMessage { + SK2SubscriptionOfferPaymentMode convertFromPigeon() { + switch (this) { + case SK2SubscriptionOfferPaymentModeMessage.payAsYouGo: + return SK2SubscriptionOfferPaymentMode.payAsYouGo; + case SK2SubscriptionOfferPaymentModeMessage.payUpFront: + return SK2SubscriptionOfferPaymentMode.payUpFront; + case SK2SubscriptionOfferPaymentModeMessage.freeTrial: + return SK2SubscriptionOfferPaymentMode.freeTrial; + } + } +} + +class SK2PriceLocale { + SK2PriceLocale({required this.currencyCode, required this.currencySymbol}); + final String currencyCode; + final String currencySymbol; +} + +extension on SK2PriceLocaleMessage { + SK2PriceLocale convertFromPigeon() { + return SK2PriceLocale( + currencyCode: currencyCode, currencySymbol: currencySymbol); + } +} + +/// Dart 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. +class SK2Product { + /// Creates a new [SKStorefrontWrapper] with the provided information. + SK2Product({ + required this.id, + required this.displayName, + required this.displayPrice, + required this.description, + required this.price, + required this.type, + required this.priceLocale, + this.subscription, + }); + + /// The unique product identifier. + final String id; + + /// The localized display name of the product, if it exists. + final String displayName; + + /// The localized description of the product. + final String description; + + /// The localized string representation of the product price, suitable for display. + final double price; + + /// The localized price of the product as a string. + final String displayPrice; + + /// The types of in-app purchases. + final SK2ProductType type; + + /// The subscription information for an auto-renewable subscription. + final SK2SubscriptionInfo? subscription; + + /// The locale and currency information for this product. + final SK2PriceLocale priceLocale; + + /// https://developer.apple.com/documentation/storekit/product/3851116-products + /// Given a list of identifiers, return a list of products + /// If any of the identifiers are invalid or can't be found, they are excluded + /// from the returned list. + static Future> products(List identifiers) async { + final List productsMsg = + await _hostApi.products(identifiers); + if (productsMsg.isEmpty && identifiers.isNotEmpty) { + throw PlatformException( + code: 'storekit_no_response', + message: 'StoreKit: Failed to get response from platform.', + ); + } + + return productsMsg + .whereType() + .map((SK2ProductMessage product) => product.convertFromPigeon()) + .toList(); + } +} + +// let me test what i wanted you to type +extension on SK2ProductMessage { + SK2Product convertFromPigeon() { + return SK2Product( + id: id, + displayName: displayName, + displayPrice: displayPrice, + price: price, + description: description, + type: type.convertFromPigeon(), + subscription: subscription?.convertFromPigeon(), + priceLocale: priceLocale.convertFromPigeon()); + } +} \ No newline at end of file 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 new file mode 100644 index 000000000000..746cc3f378a1 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/store_kit_2_wrappers.dart @@ -0,0 +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. + +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'; 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 new file mode 100644 index 000000000000..9532814b24f3 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart @@ -0,0 +1,142 @@ + +// 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 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon(PigeonOptions( + dartOut: 'lib/src/sk2_pigeon.g.dart', + dartTestOut: 'test/sk2_test_api.g.dart', + swiftOut: 'darwin/Classes/StoreKit2/sk2_pigeon.g.swift', + copyrightHeader: 'pigeons/copyright.txt', +)) +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 +} + +enum SK2SubscriptionOfferTypeMessage { introductory, promotional } + +enum SK2SubscriptionOfferPaymentModeMessage { + payAsYouGo, + payUpFront, + freeTrial, +} + +class SK2SubscriptionOfferMessage { + const SK2SubscriptionOfferMessage({ + this.id, + required this.price, + required this.type, + required this.period, + required this.periodCount, + required this.paymentMode, + }); + final String? id; + final double price; + final SK2SubscriptionOfferTypeMessage type; + final SK2SubscriptionPeriodMessage period; + final int periodCount; + final SK2SubscriptionOfferPaymentModeMessage paymentMode; +} + +enum SK2SubscriptionPeriodUnitMessage { day, week, month, year } + +class SK2SubscriptionPeriodMessage { + const SK2SubscriptionPeriodMessage({required this.value, required this.unit}); + + /// The number of units that the period represents. + final int value; + + /// The unit of time that this period represents. + final SK2SubscriptionPeriodUnitMessage unit; +} + +class SK2SubscriptionInfoMessage { + const SK2SubscriptionInfoMessage({ + required this.subscriptionGroupID, + required this.promotionalOffers, + required this.subscriptionPeriod, + }); + + /// An array of all the promotional offers configured for this subscription. + /// This should be List but pigeon doesnt support + /// null-safe generics. https://github.com/flutter/flutter/issues/97848 + final List promotionalOffers; + + /// The group identifier for this subscription. + final String subscriptionGroupID; + + /// The duration that this subscription lasts before auto-renewing. + final SK2SubscriptionPeriodMessage subscriptionPeriod; +} + +/// A Pigeon message class representing a Product +/// https://developer.apple.com/documentation/storekit/product +class SK2ProductMessage { + const SK2ProductMessage( + {required this.id, + required this.displayName, + required this.displayPrice, + required this.description, + required this.price, + required this.type, + this.subscription, + required this.priceLocale}); + + /// The unique product identifier. + final String id; + + /// The localized display name of the product, if it exists. + final String displayName; + + /// The localized description of the product. + final String description; + + /// The localized string representation of the product price, suitable for display. + final double price; + + /// The localized price of the product as a string. + final String displayPrice; + + /// The types of in-app purchases. + final SK2ProductTypeMessage type; + + /// The subscription information for an auto-renewable subscription. + final SK2SubscriptionInfoMessage? subscription; + + /// The currency and locale information for this product + final SK2PriceLocaleMessage priceLocale; +} + +class SK2PriceLocaleMessage { + SK2PriceLocaleMessage({ + required this.currencyCode, + required this.currencySymbol, + }); + + final String currencyCode; + final String currencySymbol; +} + +@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); +} \ No newline at end of file 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 new file mode 100644 index 000000000000..20f43b33b78b --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart @@ -0,0 +1,113 @@ +// 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. +// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers +// ignore_for_file: avoid_relative_lib_imports +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; + +class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { + const _TestInAppPurchase2ApiCodec(); + @override + void writeValue(WriteBuffer buffer, Object? value) { + if (value is SK2PriceLocaleMessage) { + buffer.putUint8(128); + writeValue(buffer, value.encode()); + } else if (value is SK2ProductMessage) { + buffer.putUint8(129); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionInfoMessage) { + buffer.putUint8(130); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionOfferMessage) { + buffer.putUint8(131); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionPeriodMessage) { + buffer.putUint8(132); + writeValue(buffer, value.encode()); + } else { + super.writeValue(buffer, value); + } + } + + @override + Object? readValueOfType(int type, ReadBuffer buffer) { + switch (type) { + case 128: + return SK2PriceLocaleMessage.decode(readValue(buffer)!); + case 129: + return SK2ProductMessage.decode(readValue(buffer)!); + case 130: + return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + case 131: + return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + case 132: + return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); + default: + return super.readValueOfType(type, buffer); + } + } +} + +abstract class TestInAppPurchase2Api { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec pigeonChannelCodec = _TestInAppPurchase2ApiCodec(); + + bool canMakePayments(); + + Future> products(List identifiers); + + static void setup(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments', pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_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())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products', pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { + assert(message != 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(); + 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!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse(error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + } +} From 552b9310a94abd05894ff2ca7a8e16cae561d68e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 21 Aug 2024 13:15:56 -0700 Subject: [PATCH 02/18] missing type --- .../darwin/Classes/InAppPurchasePlugin.swift | 3 ++ .../StoreKit2/StoreKit2Translators.swift | 9 +++++ .../src/types/app_store_product_details.dart | 36 +++++++++++++++++++ 3 files changed, 48 insertions(+) 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 dad613571388..f24e88ae7745 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 @@ -41,6 +41,9 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { registrar.addMethodCallDelegate(instance, channel: channel) registrar.addApplicationDelegate(instance) SetUpInAppPurchaseAPI(messenger, instance) + if #available(iOS 15.0, *) { + InAppPurchase2APISetup.setUp(binaryMessenger: messenger, api: instance) + } } // This init is used for tests 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 4ba82dad960a..d2daf8e7dd4a 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 @@ -122,3 +122,12 @@ extension Product.SubscriptionOffer.PaymentMode { } } +extension Locale { + func convertToPigeon() -> SK2PriceLocaleMessage { + return SK2PriceLocaleMessage( + currencyCode: currencyCode ?? "", + currencySymbol: currencySymbol ?? "" + ) + } +} + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart index 47bcf616fa40..c36a2d94ded8 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart @@ -5,6 +5,7 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; import '../../store_kit_wrappers.dart'; +import '../store_kit_2_wrappers/sk2_product_wrapper.dart'; /// The class represents the information of a product as registered in the Apple /// AppStore. @@ -42,3 +43,38 @@ class AppStoreProductDetails extends ProductDetails { /// this [AppStoreProductDetails] object. final SKProductWrapper skProduct; } + +/// The class represents the information of a product as registered in the Apple +/// AppStore. +class AppStoreProduct2Details extends ProductDetails { + /// Creates a new AppStore specific product details object with the provided + /// details. + AppStoreProduct2Details({ + required super.id, + required super.title, + required super.description, + required super.price, + required super.rawPrice, + required super.currencyCode, + required this.sk2Product, + required super.currencySymbol, + }); + + /// Generate a [AppStoreProductDetails] object based on an iOS [SKProductWrapper] object. + factory AppStoreProduct2Details.fromSK2Product(SK2Product product) { + return AppStoreProduct2Details( + id: product.id, + title: product.displayName, + description: product.description, + price: product.priceLocale.currencySymbol + product.price.toString(), + rawPrice: product.price, + currencyCode: product.priceLocale.currencyCode, + currencySymbol: product.priceLocale.currencySymbol, + sk2Product: product, + ); + } + + /// Points back to the [SKProductWrapper] object that was used to generate + /// this [AppStoreProductDetails] object. + final SK2Product sk2Product; +} From 4beaabd5c9bcf58a778e404944a4a8859380d539 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 21 Aug 2024 13:18:30 -0700 Subject: [PATCH 03/18] oops extra pigeon file --- .../darwin/Classes/StoreKit2/messages.g.swift | 348 ------------------ 1 file changed, 348 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift deleted file mode 100644 index ab19a3381dad..000000000000 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/messages.g.swift +++ /dev/null @@ -1,348 +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. -// Autogenerated from Pigeon (v16.0.5), do not edit directly. -// See also: https://pub.dev/packages/pigeon - -import Foundation - -#if os(iOS) - import Flutter -#elseif os(macOS) - import FlutterMacOS -#else - #error("Unsupported platform.") -#endif - -private func wrapResult(_ result: Any?) -> [Any?] { - return [result] -} - -private func wrapError(_ error: Any) -> [Any?] { - if let flutterError = error as? FlutterError { - return [ - flutterError.code, - flutterError.message, - flutterError.details, - ] - } - return [ - "\(error)", - "\(type(of: error))", - "Stacktrace: \(Thread.callStackSymbols)", - ] -} - -private func isNullish(_ value: Any?) -> Bool { - return value is NSNull || value == nil -} - -private func nilOrValue(_ value: Any?) -> T? { - if value is NSNull { return nil } - return value as! T? -} - -enum SK2ProductTypeMessage: Int { - /// A consumable in-app purchase. - case consumable = 0 - /// A non-consumable in-app purchase. - case nonConsumable = 1 - /// A non-renewing subscription. - case nonRenewable = 2 - /// An auto-renewable subscription. - case autoRenewable = 3 -} - -enum SK2SubscriptionOfferTypeMessage: Int { - case introductory = 0 - case promotional = 1 -} - -enum SK2SubscriptionOfferPaymentModeMessage: Int { - case payAsYouGo = 0 - case payUpFront = 1 - case freeTrial = 2 -} - -enum SK2SubscriptionPeriodUnitMessage: Int { - case day = 0 - case week = 1 - case month = 2 - case year = 3 -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2SubscriptionOfferMessage { - var id: String? = nil - var price: Double - var type: SK2SubscriptionOfferTypeMessage - var period: SK2SubscriptionPeriodMessage - var periodCount: Int64 - var paymentMode: SK2SubscriptionOfferPaymentModeMessage - - static func fromList(_ list: [Any?]) -> SK2SubscriptionOfferMessage? { - let id: String? = nilOrValue(list[0]) - let price = list[1] as! Double - let type = SK2SubscriptionOfferTypeMessage(rawValue: list[2] as! Int)! - let period = SK2SubscriptionPeriodMessage.fromList(list[3] as! [Any?])! - let periodCount = list[4] is Int64 ? list[4] as! Int64 : Int64(list[4] as! Int32) - let paymentMode = SK2SubscriptionOfferPaymentModeMessage(rawValue: list[5] as! Int)! - - return SK2SubscriptionOfferMessage( - id: id, - price: price, - type: type, - period: period, - periodCount: periodCount, - paymentMode: paymentMode - ) - } - func toList() -> [Any?] { - return [ - id, - price, - type.rawValue, - period.toList(), - periodCount, - paymentMode.rawValue, - ] - } -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2SubscriptionPeriodMessage { - /// The number of units that the period represents. - var value: Int64 - /// The unit of time that this period represents. - var unit: SK2SubscriptionPeriodUnitMessage - - static func fromList(_ list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = list[0] is Int64 ? list[0] as! Int64 : Int64(list[0] as! Int32) - let unit = SK2SubscriptionPeriodUnitMessage(rawValue: list[1] as! Int)! - - return SK2SubscriptionPeriodMessage( - value: value, - unit: unit - ) - } - func toList() -> [Any?] { - return [ - value, - unit.rawValue, - ] - } -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2SubscriptionInfoMessage { - /// An array of all the promotional offers configured for this subscription. - /// This should be List but pigeon doesnt support - /// null-safe generics. https://github.com/flutter/flutter/issues/97848 - var promotionalOffers: [SK2SubscriptionOfferMessage?] - /// The group identifier for this subscription. - var subscriptionGroupID: String - /// The duration that this subscription lasts before auto-renewing. - var subscriptionPeriod: SK2SubscriptionPeriodMessage - - static func fromList(_ list: [Any?]) -> SK2SubscriptionInfoMessage? { - let promotionalOffers = list[0] as! [SK2SubscriptionOfferMessage?] - let subscriptionGroupID = list[1] as! String - let subscriptionPeriod = SK2SubscriptionPeriodMessage.fromList(list[2] as! [Any?])! - - return SK2SubscriptionInfoMessage( - promotionalOffers: promotionalOffers, - subscriptionGroupID: subscriptionGroupID, - subscriptionPeriod: subscriptionPeriod - ) - } - func toList() -> [Any?] { - return [ - promotionalOffers, - subscriptionGroupID, - subscriptionPeriod.toList(), - ] - } -} - -/// A Pigeon message class representing a Product -/// https://developer.apple.com/documentation/storekit/product -/// -/// Generated class from Pigeon that represents data sent in messages. -struct SK2ProductMessage { - /// The unique product identifier. - var id: String - /// The localized display name of the product, if it exists. - var displayName: String - /// The localized description of the product. - var description: String - /// The localized string representation of the product price, suitable for display. - var price: Double - /// The localized price of the product as a string. - var displayPrice: String - /// The types of in-app purchases. - var type: SK2ProductTypeMessage - /// The subscription information for an auto-renewable subscription. - var subscription: SK2SubscriptionInfoMessage? = nil - /// The currency and locale information for this product - var priceLocale: SK2PriceLocaleMessage - - static func fromList(_ list: [Any?]) -> SK2ProductMessage? { - let id = list[0] as! String - let displayName = list[1] as! String - let description = list[2] as! String - let price = list[3] as! Double - let displayPrice = list[4] as! String - let type = SK2ProductTypeMessage(rawValue: list[5] as! Int)! - var subscription: SK2SubscriptionInfoMessage? = nil - if let subscriptionList: [Any?] = nilOrValue(list[6]) { - subscription = SK2SubscriptionInfoMessage.fromList(subscriptionList) - } - let priceLocale = SK2PriceLocaleMessage.fromList(list[7] as! [Any?])! - - return SK2ProductMessage( - id: id, - displayName: displayName, - description: description, - price: price, - displayPrice: displayPrice, - type: type, - subscription: subscription, - priceLocale: priceLocale - ) - } - func toList() -> [Any?] { - return [ - id, - displayName, - description, - price, - displayPrice, - type.rawValue, - subscription?.toList(), - priceLocale.toList(), - ] - } -} - -/// Generated class from Pigeon that represents data sent in messages. -struct SK2PriceLocaleMessage { - var currencyCode: String - var currencySymbol: String - - static func fromList(_ list: [Any?]) -> SK2PriceLocaleMessage? { - let currencyCode = list[0] as! String - let currencySymbol = list[1] as! String - - return SK2PriceLocaleMessage( - currencyCode: currencyCode, - currencySymbol: currencySymbol - ) - } - func toList() -> [Any?] { - return [ - currencyCode, - currencySymbol, - ] - } -} - -private class InAppPurchase2APICodecReader: FlutterStandardReader { - override func readValue(ofType type: UInt8) -> Any? { - switch type { - case 128: - return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) - case 129: - return SK2ProductMessage.fromList(self.readValue() as! [Any?]) - case 130: - return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) - case 131: - return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) - case 132: - return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) - default: - return super.readValue(ofType: type) - } - } -} - -private class InAppPurchase2APICodecWriter: FlutterStandardWriter { - override func writeValue(_ value: Any) { - if let value = value as? SK2PriceLocaleMessage { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? SK2ProductMessage { - super.writeByte(129) - super.writeValue(value.toList()) - } else if let value = value as? SK2SubscriptionInfoMessage { - super.writeByte(130) - super.writeValue(value.toList()) - } else if let value = value as? SK2SubscriptionOfferMessage { - super.writeByte(131) - super.writeValue(value.toList()) - } else if let value = value as? SK2SubscriptionPeriodMessage { - super.writeByte(132) - super.writeValue(value.toList()) - } else { - super.writeValue(value) - } - } -} - -private class InAppPurchase2APICodecReaderWriter: FlutterStandardReaderWriter { - override func reader(with data: Data) -> FlutterStandardReader { - return InAppPurchase2APICodecReader(data: data) - } - - override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return InAppPurchase2APICodecWriter(data: data) - } -} - -class InAppPurchase2APICodec: FlutterStandardMessageCodec { - static let shared = InAppPurchase2APICodec(readerWriter: InAppPurchase2APICodecReaderWriter()) -} - -/// 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) -} - -/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. -class InAppPurchase2APISetup { - /// The codec used by InAppPurchase2API. - static var codec: FlutterStandardMessageCodec { InAppPurchase2APICodec.shared } - /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. - static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?) { - let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - canMakePaymentsChannel.setMessageHandler { _, reply in - do { - let result = try api.canMakePayments() - reply(wrapResult(result)) - } catch { - reply(wrapError(error)) - } - } - } else { - canMakePaymentsChannel.setMessageHandler(nil) - } - let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", binaryMessenger: binaryMessenger, codec: codec) - if let api = api { - productsChannel.setMessageHandler { message, reply in - let args = message as! [Any?] - let identifiersArg = args[0] as! [String] - api.products(identifiers: identifiersArg) { result in - switch result { - case .success(let res): - reply(wrapResult(res)) - case .failure(let error): - reply(wrapError(error)) - } - } - } - } else { - productsChannel.setMessageHandler(nil) - } - } -} From 2363635fddcab90cab8e45824a1854db0aee2552 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 23 Aug 2024 14:15:29 -0700 Subject: [PATCH 04/18] equality checks --- .../StoreKit2/StoreKit2Translators.swift | 36 +++++++++ .../Classes/StoreKit2/sk2_pigeon.g.swift | 11 ++- .../ios/Runner.xcodeproj/project.pbxproj | 8 +- .../InAppPurchase2PluginTests.swift | 73 +++++++++++++++++++ .../in_app_purchase_storekit_platform.dart | 2 +- .../lib/src/sk2_pigeon.g.dart | 43 +++++++---- .../sk2_appstore_wrapper.dart | 2 +- .../sk2_product_wrapper.dart | 6 +- .../pigeons/sk2_pigeon.dart | 17 ++--- .../test/fakes/fake_storekit_platform.dart | 16 ++++ .../test/sk2_test_api.g.dart | 63 ++++++++++------ 11 files changed, 221 insertions(+), 56 deletions(-) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift 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 d2daf8e7dd4a..af9ea7810eed 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 @@ -22,6 +22,15 @@ extension Product { } } +extension SK2ProductMessage: Equatable { + static func == (lhs: SK2ProductMessage, rhs: SK2ProductMessage) -> Bool { + return lhs.id == rhs.id && lhs.displayName == rhs.displayName + && lhs.description == rhs.description && lhs.price == rhs.price + && lhs.displayPrice == rhs.displayPrice && lhs.type == rhs.type + && lhs.subscription == rhs.subscription && lhs.priceLocale == rhs.priceLocale + } +} + @available(iOS 15.0, macOS 12.0, *) extension Product.ProductType { func convertToPigeon() -> SK2ProductTypeMessage { @@ -50,6 +59,14 @@ extension Product.SubscriptionInfo { } } +extension SK2SubscriptionInfoMessage: Equatable { + static func == (lhs: SK2SubscriptionInfoMessage, rhs: SK2SubscriptionInfoMessage) -> Bool { + return lhs.promotionalOffers == rhs.promotionalOffers + && lhs.subscriptionGroupID == rhs.subscriptionGroupID + && lhs.subscriptionPeriod == rhs.subscriptionPeriod + } +} + @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionOffer { func convertToPigeon() -> SK2SubscriptionOfferMessage { @@ -65,6 +82,14 @@ extension Product.SubscriptionOffer { } } +extension SK2SubscriptionOfferMessage: Equatable { + static func == (lhs: SK2SubscriptionOfferMessage, rhs: SK2SubscriptionOfferMessage) -> Bool { + return lhs.id == rhs.id && lhs.price == rhs.price && lhs.type == rhs.type + && lhs.period == rhs.period && lhs.periodCount == rhs.periodCount + && lhs.paymentMode == rhs.paymentMode + } +} + @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionOffer.OfferType { func convertToPigeon() -> SK2SubscriptionOfferTypeMessage { @@ -88,6 +113,12 @@ extension Product.SubscriptionPeriod { } } +extension SK2SubscriptionPeriodMessage: Equatable { + static func == (lhs: SK2SubscriptionPeriodMessage, rhs: SK2SubscriptionPeriodMessage) -> Bool { + return lhs.value == rhs.value && lhs.unit == rhs.unit + } +} + @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionPeriod.Unit { func convertToPigeon() -> SK2SubscriptionPeriodUnitMessage { @@ -131,3 +162,8 @@ extension Locale { } } +extension SK2PriceLocaleMessage: Equatable { + static func == (lhs: SK2PriceLocaleMessage, rhs: SK2PriceLocaleMessage) -> Bool { + 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 ab19a3381dad..0e113a244487 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 @@ -305,7 +305,8 @@ class InAppPurchase2APICodec: FlutterStandardMessageCodec { /// 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) } /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. @@ -314,7 +315,9 @@ class InAppPurchase2APISetup { static var codec: FlutterStandardMessageCodec { InAppPurchase2APICodec.shared } /// Sets up an instance of `InAppPurchase2API` to handle messages through the `binaryMessenger`. static func setUp(binaryMessenger: FlutterBinaryMessenger, api: InAppPurchase2API?) { - let canMakePaymentsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments", binaryMessenger: binaryMessenger, codec: codec) + let canMakePaymentsChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in do { @@ -327,7 +330,9 @@ class InAppPurchase2APISetup { } else { canMakePaymentsChannel.setMessageHandler(nil) } - let productsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", binaryMessenger: binaryMessenger, codec: codec) + let productsChannel = FlutterBasicMessageChannel( + name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", + binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in let args = message as! [Any?] 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 ce090286dd23..c9c880330a5b 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 @@ -22,6 +22,8 @@ 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 /* InAppPurchase2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.swift */; }; + F2858EE82C76A4230063A092 /* Configuration.storekit in Resources */ = {isa = PBXBuildFile; fileRef = F6E5D5F926131C4800C68BED /* Configuration.storekit */; }; F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD392C1256DD0067C78A /* Stubs.m */; }; F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */; }; F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD402C1256F50067C78A /* TranslatorTests.m */; }; @@ -79,6 +81,7 @@ 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 /* InAppPurchase2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchase2PluginTests.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 = ""; }; F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = ""; }; @@ -196,6 +199,7 @@ F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, + F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -304,6 +308,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + F2858EE82C76A4230063A092 /* Configuration.storekit in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, @@ -392,7 +397,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; 9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; @@ -435,6 +440,7 @@ files = ( F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, + F2858EE72C76A3B70063A092 /* InAppPurchase2PluginTests.swift in Sources */, F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift new file mode 100644 index 000000000000..c9217e5936ca --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -0,0 +1,73 @@ +// 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 { + var session: SKTestSession! + var plugin: InAppPurchasePlugin! + + 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)) + } + } + + 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 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(let error): + // Handle the error + print("Failed to fetch products: \(error.localizedDescription)") + } + } + await fulfillment(of: [expectation], timeout: 5) + + XCTAssert(fetchedProductMsg?.count == 0) + } +} 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 79a30df2cc3f..e9311720753c 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 @@ -144,7 +144,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { List productDetails; productDetails = products .map((SK2Product productWrapper) => - AppStoreProduct2Details.fromSK2Product(productWrapper)) + AppStoreProduct2Details.fromSK2Product(productWrapper)) .toList(); final ProductDetailsResponse response = ProductDetailsResponse( productDetails: productDetails, 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 1ec4a0831740..5213a97f167a 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, } @@ -98,7 +102,8 @@ class SK2SubscriptionOfferMessage { type: SK2SubscriptionOfferTypeMessage.values[result[2]! as int], period: SK2SubscriptionPeriodMessage.decode(result[3]! as List), periodCount: result[4]! as int, - paymentMode: SK2SubscriptionOfferPaymentModeMessage.values[result[5]! as int], + paymentMode: + SK2SubscriptionOfferPaymentModeMessage.values[result[5]! as int], ); } } @@ -160,9 +165,11 @@ 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: SK2SubscriptionPeriodMessage.decode(result[2]! as List), + subscriptionPeriod: + SK2SubscriptionPeriodMessage.decode(result[2]! as List), ); } } @@ -288,15 +295,15 @@ class _InAppPurchase2APICodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 128: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 129: + case 129: return SK2ProductMessage.decode(readValue(buffer)!); - case 130: + case 130: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 131: + case 131: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 132: + case 132: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -312,11 +319,14 @@ class InAppPurchase2API { : __pigeon_binaryMessenger = binaryMessenger; final BinaryMessenger? __pigeon_binaryMessenger; - static const MessageCodec pigeonChannelCodec = _InAppPurchase2APICodec(); + static const MessageCodec pigeonChannelCodec = + _InAppPurchase2APICodec(); Future canMakePayments() async { - const String __pigeon_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments'; - final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( __pigeon_channelName, pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, @@ -342,8 +352,10 @@ class InAppPurchase2API { } Future> products(List identifiers) async { - const String __pigeon_channelName = 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products'; - final BasicMessageChannel __pigeon_channel = BasicMessageChannel( + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( __pigeon_channelName, pigeonChannelCodec, binaryMessenger: __pigeon_binaryMessenger, @@ -364,7 +376,8 @@ class InAppPurchase2API { message: 'Host platform returned null value for non-null return value.', ); } else { - return (__pigeon_replyList[0] as List?)!.cast(); + return (__pigeon_replyList[0] as List?)! + .cast(); } } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart index e43f837452d8..4caef67c1d44 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart @@ -12,4 +12,4 @@ final class AppStore { Future canMakePayments() { return _hostApi.canMakePayments(); } -} \ No newline at end of file +} 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 51212210a36b..f8c8161ec10e 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 @@ -111,7 +111,7 @@ extension on SK2SubscriptionInfoMessage { promotionalOffers: promotionalOffers .whereType() .map((SK2SubscriptionOfferMessage offer) => - offer.convertFromPigeon()) + offer.convertFromPigeon()) .toList(), subscriptionPeriod: subscriptionPeriod.convertFromPigeon()); } @@ -252,7 +252,7 @@ class SK2Product { /// from the returned list. static Future> products(List identifiers) async { final List productsMsg = - await _hostApi.products(identifiers); + await _hostApi.products(identifiers); if (productsMsg.isEmpty && identifiers.isNotEmpty) { throw PlatformException( code: 'storekit_no_response', @@ -280,4 +280,4 @@ extension on SK2ProductMessage { subscription: subscription?.convertFromPigeon(), priceLocale: priceLocale.convertFromPigeon()); } -} \ No newline at end of file +} 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 9532814b24f3..941dd6dbb272 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/sk2_pigeon.dart @@ -1,4 +1,3 @@ - // 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. @@ -86,13 +85,13 @@ class SK2SubscriptionInfoMessage { class SK2ProductMessage { const SK2ProductMessage( {required this.id, - required this.displayName, - required this.displayPrice, - required this.description, - required this.price, - required this.type, - this.subscription, - required this.priceLocale}); + required this.displayName, + required this.displayPrice, + required this.description, + required this.price, + required this.type, + this.subscription, + required this.priceLocale}); /// The unique product identifier. final String id; @@ -139,4 +138,4 @@ abstract class InAppPurchase2API { // SK1 startProductRequest @async List products(List identifiers); -} \ No newline at end of file +} 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 082d62939d90..a6273d0989f5 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 @@ -6,8 +6,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; 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/store_kit_wrappers.dart'; +import '../sk2_test_api.g.dart'; import '../store_kit_wrappers/sk_test_stub_objects.dart'; import '../test_api.g.dart'; @@ -274,3 +276,17 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { queueIsActive = false; } } + +class FakeStoreKit2Platform implements TestInAppPurchase2Api { + @override + bool canMakePayments() { + return true; + } + + @override + Future> products(List identifiers) { + // TODO: implement products + 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 20f43b33b78b..5a8d9f2e6935 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 @@ -40,15 +40,15 @@ class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: + case 128: return SK2PriceLocaleMessage.decode(readValue(buffer)!); - case 129: + case 129: return SK2ProductMessage.decode(readValue(buffer)!); - case 130: + case 130: return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); - case 131: + case 131: return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); - case 132: + case 132: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -57,54 +57,71 @@ class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { } abstract class TestInAppPurchase2Api { - static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec pigeonChannelCodec = _TestInAppPurchase2ApiCodec(); + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => + TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec pigeonChannelCodec = + _TestInAppPurchase2ApiCodec(); bool canMakePayments(); Future> products(List identifiers); - static void setup(TestInAppPurchase2Api? api, {BinaryMessenger? binaryMessenger}) { + static void setup(TestInAppPurchase2Api? api, + {BinaryMessenger? binaryMessenger}) { { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments', pigeonChannelCodec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_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 __pigeon_channel = BasicMessageChannel( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products', pigeonChannelCodec, + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products', + pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, null); + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); } else { - _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(__pigeon_channel, (Object? message) async { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_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())); } }); } From 55fcfb13f1b671c1eb72f6c414eff7cca3fcb199 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Fri, 23 Aug 2024 14:27:55 -0700 Subject: [PATCH 05/18] boop --- .../test/fakes/fake_storekit_platform.dart | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) 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 a6273d0989f5..f776d90b65c0 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 @@ -7,6 +7,7 @@ import 'package:flutter_test/flutter_test.dart'; 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/store_kit_wrappers.dart'; import '../sk2_test_api.g.dart'; @@ -278,6 +279,10 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { } class FakeStoreKit2Platform implements TestInAppPurchase2Api { + + late Set validProductIDs; + late Map validProducts; + @override bool canMakePayments() { return true; @@ -285,8 +290,16 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future> products(List identifiers) { - // TODO: implement products - throw UnimplementedError(); + final List productIDS = identifiers; + final List invalidFound = []; + final List products = []; + for (final String? productID in productIDS) { + if (!validProductIDs.contains(productID)) { + invalidFound.add(productID!); + } else { + products.add(validProducts[productID]!); + } + } } } From bdbdc1ba75ee2bf0e455b4fe3e59148f28d01c1c Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 26 Aug 2024 20:59:13 -0700 Subject: [PATCH 06/18] tests done --- .../in_app_purchase_storekit_platform.dart | 16 ++++- .../sk2_product_wrapper.dart | 30 ++++++++ .../test/fakes/fake_storekit_platform.dart | 35 +++++++-- ...app_purchase_storekit_2_platform_test.dart | 71 +++++++++++++++++++ 4 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart 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 e9311720753c..283f73f86b0f 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 @@ -146,9 +146,17 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { .map((SK2Product productWrapper) => AppStoreProduct2Details.fromSK2Product(productWrapper)) .toList(); - final ProductDetailsResponse response = ProductDetailsResponse( - productDetails: productDetails, - notFoundIDs: invalidProductIdentifiers.toList()); + final ProductDetailsResponse response = ProductDetailsResponse( + productDetails: productDetails, + notFoundIDs: invalidProductIdentifiers.toList(), + error: exception == null + ? null + : IAPError( + source: kIAPSource, + code: exception.code, + message: exception.message ?? '', + details: exception.details), + ); return response; } final SKRequestMaker requestMaker = SKRequestMaker(); @@ -198,6 +206,8 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Use countryCode instead. @Deprecated('Use countryCode') Future getCountryCode() => countryCode(); + + } enum _TransactionRestoreState { 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 f8c8161ec10e..ed4906ee8860 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 @@ -40,6 +40,21 @@ extension on SK2ProductTypeMessage { } } +extension on SK2ProductType { + SK2ProductTypeMessage convertToPigeon() { + switch (this) { + case SK2ProductType.autoRenewable: + return SK2ProductTypeMessage.autoRenewable; + case SK2ProductType.consumable: + return SK2ProductTypeMessage.consumable; + case SK2ProductType.nonConsumable: + return SK2ProductTypeMessage.nonConsumable; + case SK2ProductType.nonRenewable: + return SK2ProductTypeMessage.nonRenewable; + } + } +} + enum SK2SubscriptionOfferType { introductory, promotional } extension on SK2SubscriptionOfferTypeMessage { @@ -196,6 +211,10 @@ class SK2PriceLocale { SK2PriceLocale({required this.currencyCode, required this.currencySymbol}); final String currencyCode; final String currencySymbol; + + SK2PriceLocaleMessage convertToPigeon() { + return SK2PriceLocaleMessage(currencyCode: currencyCode, currencySymbol: currencySymbol); + } } extension on SK2PriceLocaleMessage { @@ -265,6 +284,17 @@ class SK2Product { .map((SK2ProductMessage product) => product.convertFromPigeon()) .toList(); } + + SK2ProductMessage convertToPigeon() { + return SK2ProductMessage( + id: id, + displayName: displayName, + description: description, + price: price, + displayPrice: displayPrice, + type: type.convertToPigeon(), + priceLocale: priceLocale.convertToPigeon()); + } } // let me test what i wanted you to type 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 f776d90b65c0..baac4a528a09 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 @@ -282,6 +282,25 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { late Set validProductIDs; late Map validProducts; + PlatformException? queryProductException; + + void reset() { + validProductIDs = {'123', '456'}; + validProducts = {}; + for (final String validID in validProductIDs) { + final SK2Product product = SK2Product( + id: validID, + displayName: "test_product", + displayPrice: "0.99", + description: "description", + price: 0.99, + type: SK2ProductType.consumable, + priceLocale: SK2PriceLocale( + currencyCode: 'USD', + currencySymbol: r'$')); + validProducts[validID] = product; + } + } @override bool canMakePayments() { @@ -290,16 +309,22 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { @override Future> products(List identifiers) { + if (queryProductException != null) { + throw queryProductException!; + } final List productIDS = identifiers; - final List invalidFound = []; - final List products = []; + final List products = []; for (final String? productID in productIDS) { - if (!validProductIDs.contains(productID)) { - invalidFound.add(productID!); - } else { + if (validProductIDs.contains(productID)) { products.add(validProducts[productID]!); } } + final List result = []; + for (SK2Product p in products) { + result.add(p.convertToPigeon()); + } + + return Future>.value(result); } } 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 new file mode 100644 index 000000000000..36c2fce33c40 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart @@ -0,0 +1,71 @@ +// 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 '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 'fakes/fake_storekit_platform.dart'; +import 'sk2_test_api.g.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + final FakeStoreKit2Platform fakeStoreKit2Platform = FakeStoreKit2Platform(); + late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; + + setUpAll(() { + TestInAppPurchase2Api.setup(fakeStoreKit2Platform); + }); + + setUp(() { + InAppPurchaseStoreKitPlatform.registerPlatform(); + iapStoreKitPlatform = + InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; + fakeStoreKit2Platform.reset(); + }); + + tearDown(() => fakeStoreKit2Platform.reset()); + + group('isAvailable', () { + test('true', () async { + expect(await iapStoreKitPlatform.isAvailable(), isTrue); + }); + }); + + group('query product list', () { + test('should get product list and correct invalid identifiers', () async { + final InAppPurchaseStoreKitPlatform connection = + InAppPurchaseStoreKitPlatform(); + final ProductDetailsResponse response = + await connection.queryProductDetails({'123', '456', '789'}); + final List products = response.productDetails; + expect(products.first.id, '123'); + expect(products[1].id, '456'); + expect(response.notFoundIDs, ['789']); + expect(response.error, isNull); + expect(response.productDetails.first.currencySymbol, r'$'); + expect(response.productDetails[1].currencySymbol, r'$'); + }); + test( + 'if query products throws error, should get error object in the response', + () async { + fakeStoreKit2Platform.queryProductException = PlatformException( + code: 'error_code', + message: 'error_message', + details: {'info': 'error_info'}); + final InAppPurchaseStoreKitPlatform connection = + InAppPurchaseStoreKitPlatform(); + final ProductDetailsResponse response = + await connection.queryProductDetails({'123', '456', '789'}); + expect(response.productDetails, []); + expect(response.notFoundIDs, ['123', '456', '789']); + expect(response.error, isNotNull); + expect(response.error!.source, kIAPSource); + expect(response.error!.code, 'error_code'); + expect(response.error!.message, 'error_message'); + expect(response.error!.details, {'info': 'error_info'}); + }); +});} From b298f9ab1a63c38e5d410290d13d8d3850f7ff09 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Mon, 26 Aug 2024 20:59:34 -0700 Subject: [PATCH 07/18] format --- .../InAppPurchase2PluginTests.swift | 2 +- .../in_app_purchase_storekit_platform.dart | 12 +++--- .../sk2_product_wrapper.dart | 3 +- .../test/fakes/fake_storekit_platform.dart | 7 +--- ...app_purchase_storekit_2_platform_test.dart | 39 ++++++++++--------- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift index c9217e5936ca..160c85e6ad57 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -59,7 +59,7 @@ final class InAppPurchase2PluginTests: XCTestCase { plugin.products(identifiers: ["invalid_product"]) { result in switch result { case .success(let productMessages): - fetchedProductMsg = productMessages; + fetchedProductMsg = productMessages expectation.fulfill() case .failure(let error): // Handle the 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 283f73f86b0f..8cc448ab4348 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 @@ -146,16 +146,16 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { .map((SK2Product productWrapper) => AppStoreProduct2Details.fromSK2Product(productWrapper)) .toList(); - final ProductDetailsResponse response = ProductDetailsResponse( + final ProductDetailsResponse response = ProductDetailsResponse( productDetails: productDetails, notFoundIDs: invalidProductIdentifiers.toList(), error: exception == null ? null : IAPError( - source: kIAPSource, - code: exception.code, - message: exception.message ?? '', - details: exception.details), + source: kIAPSource, + code: exception.code, + message: exception.message ?? '', + details: exception.details), ); return response; } @@ -206,8 +206,6 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Use countryCode instead. @Deprecated('Use countryCode') Future getCountryCode() => countryCode(); - - } enum _TransactionRestoreState { 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 ed4906ee8860..39adb7530f74 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 @@ -213,7 +213,8 @@ class SK2PriceLocale { final String currencySymbol; SK2PriceLocaleMessage convertToPigeon() { - return SK2PriceLocaleMessage(currencyCode: currencyCode, currencySymbol: currencySymbol); + return SK2PriceLocaleMessage( + currencyCode: currencyCode, currencySymbol: currencySymbol); } } 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 baac4a528a09..1083ebd755ec 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 @@ -279,7 +279,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { } class FakeStoreKit2Platform implements TestInAppPurchase2Api { - late Set validProductIDs; late Map validProducts; PlatformException? queryProductException; @@ -295,9 +294,8 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { description: "description", price: 0.99, type: SK2ProductType.consumable, - priceLocale: SK2PriceLocale( - currencyCode: 'USD', - currencySymbol: r'$')); + priceLocale: + SK2PriceLocale(currencyCode: 'USD', currencySymbol: r'$')); validProducts[validID] = product; } } @@ -326,5 +324,4 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { return Future>.value(result); } - } 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 36c2fce33c40..da9840493f8f 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 @@ -23,7 +23,7 @@ void main() { setUp(() { InAppPurchaseStoreKitPlatform.registerPlatform(); iapStoreKitPlatform = - InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; + InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; fakeStoreKit2Platform.reset(); }); @@ -38,9 +38,9 @@ void main() { group('query product list', () { test('should get product list and correct invalid identifiers', () async { final InAppPurchaseStoreKitPlatform connection = - InAppPurchaseStoreKitPlatform(); + InAppPurchaseStoreKitPlatform(); final ProductDetailsResponse response = - await connection.queryProductDetails({'123', '456', '789'}); + await connection.queryProductDetails({'123', '456', '789'}); final List products = response.productDetails; expect(products.first.id, '123'); expect(products[1].id, '456'); @@ -51,21 +51,22 @@ void main() { }); test( 'if query products throws error, should get error object in the response', - () async { - fakeStoreKit2Platform.queryProductException = PlatformException( - code: 'error_code', - message: 'error_message', - details: {'info': 'error_info'}); - final InAppPurchaseStoreKitPlatform connection = + () async { + fakeStoreKit2Platform.queryProductException = PlatformException( + code: 'error_code', + message: 'error_message', + details: {'info': 'error_info'}); + final InAppPurchaseStoreKitPlatform connection = InAppPurchaseStoreKitPlatform(); - final ProductDetailsResponse response = + final ProductDetailsResponse response = await connection.queryProductDetails({'123', '456', '789'}); - expect(response.productDetails, []); - expect(response.notFoundIDs, ['123', '456', '789']); - expect(response.error, isNotNull); - expect(response.error!.source, kIAPSource); - expect(response.error!.code, 'error_code'); - expect(response.error!.message, 'error_message'); - expect(response.error!.details, {'info': 'error_info'}); - }); -});} + expect(response.productDetails, []); + expect(response.notFoundIDs, ['123', '456', '789']); + expect(response.error, isNotNull); + expect(response.error!.source, kIAPSource); + expect(response.error!.code, 'error_code'); + expect(response.error!.message, 'error_message'); + expect(response.error!.details, {'info': 'error_info'}); + }); + }); +} From 7e853a09bada964a02968a6ee177cb411a3f5bf7 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 27 Aug 2024 11:56:44 -0700 Subject: [PATCH 08/18] oopsies update pigeon by like 8 versions --- .../StoreKit2/InAppPurchasePlugin2.swift | 20 +- .../Classes/StoreKit2/sk2_pigeon.g.swift | 198 ++++++++++++------ .../lib/src/sk2_pigeon.g.dart | 177 +++++++++------- .../in_app_purchase_storekit/pubspec.yaml | 2 +- .../test/sk2_test_api.g.dart | 97 ++++++--- 5 files changed, 319 insertions(+), 175 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift index ca12635ec029..6a2cc356c833 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift @@ -4,31 +4,33 @@ @available(iOS 15.0, macOS 12.0, *) extension InAppPurchasePlugin: InAppPurchase2API { + // MARK: - Pigeon Functions + + // Wrapper method around StoreKit2's canMakePayments() method // https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments func canMakePayments() throws -> Bool { return AppStore.canMakePayments } - // Pigeon method + // Wrapper method around StoreKit2's products() method + // https://developer.apple.com/documentation/storekit/product/3851116-products func products( identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], any Error>) -> Void ) { Task { do { - let products = try await rawProducts(identifiers: identifiers) + let products = try await Product.products(for: identifiers) let productMessages = products.map { product in product.convertToPigeon() } completion(.success(productMessages)) } catch { - completion(.failure(error)) + throw PigeonError( + code: "storekit2_products_error", + message: error.localizedDescription, + details: error.localizedDescription + ) } } } - - // Raw storekit calls - - func rawProducts(identifiers: [String]) async throws -> [Product] { - return try await Product.products(for: identifiers) - } } 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 0e113a244487..119fb288f364 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/sk2_pigeon.g.swift @@ -1,7 +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. -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v22.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon import Foundation @@ -14,11 +14,36 @@ import Foundation #error("Unsupported platform.") #endif +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Any? + + init(code: String, message: String?, details: Any?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + private func wrapResult(_ result: Any?) -> [Any?] { return [result] } private func wrapError(_ error: Any) -> [Any?] { + if let pigeonError = error as? PigeonError { + return [ + pigeonError.code, + pigeonError.message, + pigeonError.details, + ] + } if let flutterError = error as? FlutterError { return [ flutterError.code, @@ -80,13 +105,15 @@ struct SK2SubscriptionOfferMessage { var periodCount: Int64 var paymentMode: SK2SubscriptionOfferPaymentModeMessage - static func fromList(_ list: [Any?]) -> SK2SubscriptionOfferMessage? { - let id: String? = nilOrValue(list[0]) - let price = list[1] as! Double - let type = SK2SubscriptionOfferTypeMessage(rawValue: list[2] as! Int)! - let period = SK2SubscriptionPeriodMessage.fromList(list[3] as! [Any?])! - let periodCount = list[4] is Int64 ? list[4] as! Int64 : Int64(list[4] as! Int32) - let paymentMode = SK2SubscriptionOfferPaymentModeMessage(rawValue: list[5] as! Int)! + // 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 paymentMode = pigeonVar_list[5] as! SK2SubscriptionOfferPaymentModeMessage return SK2SubscriptionOfferMessage( id: id, @@ -101,10 +128,10 @@ struct SK2SubscriptionOfferMessage { return [ id, price, - type.rawValue, - period.toList(), + type, + period, periodCount, - paymentMode.rawValue, + paymentMode, ] } } @@ -116,9 +143,11 @@ struct SK2SubscriptionPeriodMessage { /// The unit of time that this period represents. var unit: SK2SubscriptionPeriodUnitMessage - static func fromList(_ list: [Any?]) -> SK2SubscriptionPeriodMessage? { - let value = list[0] is Int64 ? list[0] as! Int64 : Int64(list[0] as! Int32) - let unit = SK2SubscriptionPeriodUnitMessage(rawValue: list[1] as! Int)! + // 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 unit = pigeonVar_list[1] as! SK2SubscriptionPeriodUnitMessage return SK2SubscriptionPeriodMessage( value: value, @@ -128,7 +157,7 @@ struct SK2SubscriptionPeriodMessage { func toList() -> [Any?] { return [ value, - unit.rawValue, + unit, ] } } @@ -144,10 +173,11 @@ struct SK2SubscriptionInfoMessage { /// The duration that this subscription lasts before auto-renewing. var subscriptionPeriod: SK2SubscriptionPeriodMessage - static func fromList(_ list: [Any?]) -> SK2SubscriptionInfoMessage? { - let promotionalOffers = list[0] as! [SK2SubscriptionOfferMessage?] - let subscriptionGroupID = list[1] as! String - let subscriptionPeriod = SK2SubscriptionPeriodMessage.fromList(list[2] as! [Any?])! + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> SK2SubscriptionInfoMessage? { + let promotionalOffers = pigeonVar_list[0] as! [SK2SubscriptionOfferMessage?] + let subscriptionGroupID = pigeonVar_list[1] as! String + let subscriptionPeriod = pigeonVar_list[2] as! SK2SubscriptionPeriodMessage return SK2SubscriptionInfoMessage( promotionalOffers: promotionalOffers, @@ -159,7 +189,7 @@ struct SK2SubscriptionInfoMessage { return [ promotionalOffers, subscriptionGroupID, - subscriptionPeriod.toList(), + subscriptionPeriod, ] } } @@ -186,18 +216,16 @@ struct SK2ProductMessage { /// The currency and locale information for this product var priceLocale: SK2PriceLocaleMessage - static func fromList(_ list: [Any?]) -> SK2ProductMessage? { - let id = list[0] as! String - let displayName = list[1] as! String - let description = list[2] as! String - let price = list[3] as! Double - let displayPrice = list[4] as! String - let type = SK2ProductTypeMessage(rawValue: list[5] as! Int)! - var subscription: SK2SubscriptionInfoMessage? = nil - if let subscriptionList: [Any?] = nilOrValue(list[6]) { - subscription = SK2SubscriptionInfoMessage.fromList(subscriptionList) - } - let priceLocale = SK2PriceLocaleMessage.fromList(list[7] as! [Any?])! + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> SK2ProductMessage? { + let id = pigeonVar_list[0] as! String + let displayName = pigeonVar_list[1] as! String + let description = pigeonVar_list[2] as! String + let price = pigeonVar_list[3] as! Double + let displayPrice = pigeonVar_list[4] as! String + let type = pigeonVar_list[5] as! SK2ProductTypeMessage + let subscription: SK2SubscriptionInfoMessage? = nilOrValue(pigeonVar_list[6]) + let priceLocale = pigeonVar_list[7] as! SK2PriceLocaleMessage return SK2ProductMessage( id: id, @@ -217,9 +245,9 @@ struct SK2ProductMessage { description, price, displayPrice, - type.rawValue, - subscription?.toList(), - priceLocale.toList(), + type, + subscription, + priceLocale, ] } } @@ -229,9 +257,10 @@ struct SK2PriceLocaleMessage { var currencyCode: String var currencySymbol: String - static func fromList(_ list: [Any?]) -> SK2PriceLocaleMessage? { - let currencyCode = list[0] as! String - let currencySymbol = list[1] as! String + // swift-format-ignore: AlwaysUseLowerCamelCase + static func fromList(_ pigeonVar_list: [Any?]) -> SK2PriceLocaleMessage? { + let currencyCode = pigeonVar_list[0] as! String + let currencySymbol = pigeonVar_list[1] as! String return SK2PriceLocaleMessage( currencyCode: currencyCode, @@ -246,41 +275,77 @@ struct SK2PriceLocaleMessage { } } -private class InAppPurchase2APICodecReader: FlutterStandardReader { +private class sk2_pigeonPigeonCodecReader: FlutterStandardReader { override func readValue(ofType type: UInt8) -> Any? { switch type { - case 128: - return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) case 129: - return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + return SK2ProductTypeMessage(rawValue: enumResultAsInt) + } + return nil case 130: - return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + return SK2SubscriptionOfferTypeMessage(rawValue: enumResultAsInt) + } + return nil case 131: - return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + return SK2SubscriptionOfferPaymentModeMessage(rawValue: enumResultAsInt) + } + return nil case 132: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as? Int) + if let enumResultAsInt = enumResultAsInt { + return SK2SubscriptionPeriodUnitMessage(rawValue: enumResultAsInt) + } + return nil + case 133: + return SK2SubscriptionOfferMessage.fromList(self.readValue() as! [Any?]) + case 134: return SK2SubscriptionPeriodMessage.fromList(self.readValue() as! [Any?]) + case 135: + return SK2SubscriptionInfoMessage.fromList(self.readValue() as! [Any?]) + case 136: + return SK2ProductMessage.fromList(self.readValue() as! [Any?]) + case 137: + return SK2PriceLocaleMessage.fromList(self.readValue() as! [Any?]) default: return super.readValue(ofType: type) } } } -private class InAppPurchase2APICodecWriter: FlutterStandardWriter { +private class sk2_pigeonPigeonCodecWriter: FlutterStandardWriter { override func writeValue(_ value: Any) { - if let value = value as? SK2PriceLocaleMessage { - super.writeByte(128) - super.writeValue(value.toList()) - } else if let value = value as? SK2ProductMessage { + if let value = value as? SK2ProductTypeMessage { super.writeByte(129) - super.writeValue(value.toList()) - } else if let value = value as? SK2SubscriptionInfoMessage { + super.writeValue(value.rawValue) + } else if let value = value as? SK2SubscriptionOfferTypeMessage { super.writeByte(130) - super.writeValue(value.toList()) - } else if let value = value as? SK2SubscriptionOfferMessage { + super.writeValue(value.rawValue) + } else if let value = value as? SK2SubscriptionOfferPaymentModeMessage { super.writeByte(131) + super.writeValue(value.rawValue) + } else if let value = value as? SK2SubscriptionPeriodUnitMessage { + super.writeByte(132) + super.writeValue(value.rawValue) + } else if let value = value as? SK2SubscriptionOfferMessage { + super.writeByte(133) super.writeValue(value.toList()) } else if let value = value as? SK2SubscriptionPeriodMessage { - super.writeByte(132) + super.writeByte(134) + super.writeValue(value.toList()) + } else if let value = value as? SK2SubscriptionInfoMessage { + super.writeByte(135) + super.writeValue(value.toList()) + } else if let value = value as? SK2ProductMessage { + super.writeByte(136) + super.writeValue(value.toList()) + } else if let value = value as? SK2PriceLocaleMessage { + super.writeByte(137) super.writeValue(value.toList()) } else { super.writeValue(value) @@ -288,18 +353,18 @@ private class InAppPurchase2APICodecWriter: FlutterStandardWriter { } } -private class InAppPurchase2APICodecReaderWriter: FlutterStandardReaderWriter { +private class sk2_pigeonPigeonCodecReaderWriter: FlutterStandardReaderWriter { override func reader(with data: Data) -> FlutterStandardReader { - return InAppPurchase2APICodecReader(data: data) + return sk2_pigeonPigeonCodecReader(data: data) } override func writer(with data: NSMutableData) -> FlutterStandardWriter { - return InAppPurchase2APICodecWriter(data: data) + return sk2_pigeonPigeonCodecWriter(data: data) } } -class InAppPurchase2APICodec: FlutterStandardMessageCodec { - static let shared = InAppPurchase2APICodec(readerWriter: InAppPurchase2APICodecReaderWriter()) +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. @@ -311,12 +376,16 @@ protocol InAppPurchase2API { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class InAppPurchase2APISetup { - /// The codec used by InAppPurchase2API. - static var codec: FlutterStandardMessageCodec { InAppPurchase2APICodec.shared } + 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?) { + 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", + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { canMakePaymentsChannel.setMessageHandler { _, reply in @@ -331,7 +400,8 @@ class InAppPurchase2APISetup { canMakePaymentsChannel.setMessageHandler(nil) } let productsChannel = FlutterBasicMessageChannel( - name: "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products", + name: + "dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { productsChannel.setMessageHandler { message, reply in 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 5213a97f167a..d45d16074ef3 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/sk2_pigeon.g.dart @@ -1,7 +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. -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v22.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -87,10 +87,10 @@ class SK2SubscriptionOfferMessage { return [ id, price, - type.index, - period.encode(), + type, + period, periodCount, - paymentMode.index, + paymentMode, ]; } @@ -99,11 +99,10 @@ class SK2SubscriptionOfferMessage { return SK2SubscriptionOfferMessage( id: result[0] as String?, price: result[1]! as double, - type: SK2SubscriptionOfferTypeMessage.values[result[2]! as int], - period: SK2SubscriptionPeriodMessage.decode(result[3]! as List), + type: result[2]! as SK2SubscriptionOfferTypeMessage, + period: result[3]! as SK2SubscriptionPeriodMessage, periodCount: result[4]! as int, - paymentMode: - SK2SubscriptionOfferPaymentModeMessage.values[result[5]! as int], + paymentMode: result[5]! as SK2SubscriptionOfferPaymentModeMessage, ); } } @@ -123,7 +122,7 @@ class SK2SubscriptionPeriodMessage { Object encode() { return [ value, - unit.index, + unit, ]; } @@ -131,7 +130,7 @@ class SK2SubscriptionPeriodMessage { result as List; return SK2SubscriptionPeriodMessage( value: result[0]! as int, - unit: SK2SubscriptionPeriodUnitMessage.values[result[1]! as int], + unit: result[1]! as SK2SubscriptionPeriodUnitMessage, ); } } @@ -158,7 +157,7 @@ class SK2SubscriptionInfoMessage { return [ promotionalOffers, subscriptionGroupID, - subscriptionPeriod.encode(), + subscriptionPeriod, ]; } @@ -168,8 +167,7 @@ class SK2SubscriptionInfoMessage { promotionalOffers: (result[0] as List?)!.cast(), subscriptionGroupID: result[1]! as String, - subscriptionPeriod: - SK2SubscriptionPeriodMessage.decode(result[2]! as List), + subscriptionPeriod: result[2]! as SK2SubscriptionPeriodMessage, ); } } @@ -219,9 +217,9 @@ class SK2ProductMessage { description, price, displayPrice, - type.index, - subscription?.encode(), - priceLocale.encode(), + type, + subscription, + priceLocale, ]; } @@ -233,11 +231,9 @@ class SK2ProductMessage { description: result[2]! as String, price: result[3]! as double, displayPrice: result[4]! as String, - type: SK2ProductTypeMessage.values[result[5]! as int], - subscription: result[6] != null - ? SK2SubscriptionInfoMessage.decode(result[6]! as List) - : null, - priceLocale: SK2PriceLocaleMessage.decode(result[7]! as List), + type: result[5]! as SK2ProductTypeMessage, + subscription: result[6] as SK2SubscriptionInfoMessage?, + priceLocale: result[7]! as SK2PriceLocaleMessage, ); } } @@ -268,24 +264,39 @@ class SK2PriceLocaleMessage { } } -class _InAppPurchase2APICodec extends StandardMessageCodec { - const _InAppPurchase2APICodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is SK2PriceLocaleMessage) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionOfferMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionPeriodUnitMessage) { + buffer.putUint8(132); + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferMessage) { + buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionPeriodMessage) { - buffer.putUint8(132); + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionInfoMessage) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is SK2ProductMessage) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is SK2PriceLocaleMessage) { + buffer.putUint8(137); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -295,16 +306,34 @@ class _InAppPurchase2APICodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return SK2PriceLocaleMessage.decode(readValue(buffer)!); case 129: - return SK2ProductMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : SK2ProductTypeMessage.values[value]; case 130: - return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; case 131: - return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + 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 SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + case 134: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); + case 135: + return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + case 136: + return SK2ProductMessage.decode(readValue(buffer)!); + case 137: + return SK2PriceLocaleMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -315,68 +344,72 @@ 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}) - : __pigeon_binaryMessenger = binaryMessenger; - final BinaryMessenger? __pigeon_binaryMessenger; + InAppPurchase2API( + {BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''}) + : pigeonVar_binaryMessenger = binaryMessenger, + pigeonVar_messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; + final BinaryMessenger? pigeonVar_binaryMessenger; + + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); - static const MessageCodec pigeonChannelCodec = - _InAppPurchase2APICodec(); + final String pigeonVar_messageChannelSuffix; Future canMakePayments() async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments'; - final BasicMessageChannel __pigeon_channel = + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - __pigeon_channelName, + pigeonVar_channelName, pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, + binaryMessenger: pigeonVar_binaryMessenger, ); - final List? __pigeon_replyList = - await __pigeon_channel.send(null) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { + 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: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], ); - } else if (__pigeon_replyList[0] == null) { + } else if (pigeonVar_replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (__pigeon_replyList[0] as bool?)!; + return (pigeonVar_replyList[0] as bool?)!; } } Future> products(List identifiers) async { - const String __pigeon_channelName = - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products'; - final BasicMessageChannel __pigeon_channel = + final String pigeonVar_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$pigeonVar_messageChannelSuffix'; + final BasicMessageChannel pigeonVar_channel = BasicMessageChannel( - __pigeon_channelName, + pigeonVar_channelName, pigeonChannelCodec, - binaryMessenger: __pigeon_binaryMessenger, + binaryMessenger: pigeonVar_binaryMessenger, ); - final List? __pigeon_replyList = - await __pigeon_channel.send([identifiers]) as List?; - if (__pigeon_replyList == null) { - throw _createConnectionError(__pigeon_channelName); - } else if (__pigeon_replyList.length > 1) { + final List? pigeonVar_replyList = + await pigeonVar_channel.send([identifiers]) as List?; + if (pigeonVar_replyList == null) { + throw _createConnectionError(pigeonVar_channelName); + } else if (pigeonVar_replyList.length > 1) { throw PlatformException( - code: __pigeon_replyList[0]! as String, - message: __pigeon_replyList[1] as String?, - details: __pigeon_replyList[2], + code: pigeonVar_replyList[0]! as String, + message: pigeonVar_replyList[1] as String?, + details: pigeonVar_replyList[2], ); - } else if (__pigeon_replyList[0] == null) { + } else if (pigeonVar_replyList[0] == null) { throw PlatformException( code: 'null-error', message: 'Host platform returned null value for non-null return value.', ); } else { - return (__pigeon_replyList[0] as List?)! + return (pigeonVar_replyList[0] as List?)! .cast(); } } 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 d9f70562270c..dd3a3af933da 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_storekit/pubspec.yaml @@ -31,7 +31,7 @@ dev_dependencies: flutter_test: sdk: flutter json_serializable: ^6.0.0 - pigeon: ^16.0.4 + pigeon: ^22.0.0 test: ^1.16.0 topics: 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 5a8d9f2e6935..21ad07890668 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/sk2_test_api.g.dart @@ -1,7 +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. -// Autogenerated from Pigeon (v16.0.5), do not edit directly. +// Autogenerated from Pigeon (v22.0.0), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import, no_leading_underscores_for_local_identifiers // ignore_for_file: avoid_relative_lib_imports @@ -13,24 +13,39 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/src/sk2_pigeon.g.dart'; -class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { - const _TestInAppPurchase2ApiCodec(); +class _PigeonCodec extends StandardMessageCodec { + const _PigeonCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is SK2PriceLocaleMessage) { - buffer.putUint8(128); - writeValue(buffer, value.encode()); - } else if (value is SK2ProductMessage) { + if (value is int) { + buffer.putUint8(4); + buffer.putInt64(value); + } else if (value is SK2ProductTypeMessage) { buffer.putUint8(129); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionInfoMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferTypeMessage) { buffer.putUint8(130); - writeValue(buffer, value.encode()); - } else if (value is SK2SubscriptionOfferMessage) { + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferPaymentModeMessage) { buffer.putUint8(131); + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionPeriodUnitMessage) { + buffer.putUint8(132); + writeValue(buffer, value.index); + } else if (value is SK2SubscriptionOfferMessage) { + buffer.putUint8(133); writeValue(buffer, value.encode()); } else if (value is SK2SubscriptionPeriodMessage) { - buffer.putUint8(132); + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is SK2SubscriptionInfoMessage) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is SK2ProductMessage) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is SK2PriceLocaleMessage) { + buffer.putUint8(137); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -40,16 +55,34 @@ class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { - case 128: - return SK2PriceLocaleMessage.decode(readValue(buffer)!); case 129: - return SK2ProductMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : SK2ProductTypeMessage.values[value]; case 130: - return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null + ? null + : SK2SubscriptionOfferTypeMessage.values[value]; case 131: - return SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + 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 SK2SubscriptionOfferMessage.decode(readValue(buffer)!); + case 134: return SK2SubscriptionPeriodMessage.decode(readValue(buffer)!); + case 135: + return SK2SubscriptionInfoMessage.decode(readValue(buffer)!); + case 136: + return SK2ProductMessage.decode(readValue(buffer)!); + case 137: + return SK2PriceLocaleMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -59,27 +92,32 @@ class _TestInAppPurchase2ApiCodec extends StandardMessageCodec { abstract class TestInAppPurchase2Api { static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; - static const MessageCodec pigeonChannelCodec = - _TestInAppPurchase2ApiCodec(); + static const MessageCodec pigeonChannelCodec = _PigeonCodec(); bool canMakePayments(); Future> products(List identifiers); - static void setup(TestInAppPurchase2Api? api, - {BinaryMessenger? binaryMessenger}) { + static void setUp( + TestInAppPurchase2Api? api, { + BinaryMessenger? binaryMessenger, + String messageChannelSuffix = '', + }) { + messageChannelSuffix = + messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : ''; { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments', + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.canMakePayments$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, + .setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { try { final bool output = api.canMakePayments(); @@ -94,17 +132,18 @@ abstract class TestInAppPurchase2Api { } } { - final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + final BasicMessageChannel< + Object?> pigeonVar_channel = BasicMessageChannel< Object?>( - 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products', + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products$messageChannelSuffix', pigeonChannelCodec, binaryMessenger: binaryMessenger); if (api == null) { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, null); + .setMockDecodedMessageHandler(pigeonVar_channel, null); } else { _testBinaryMessengerBinding!.defaultBinaryMessenger - .setMockDecodedMessageHandler(__pigeon_channel, + .setMockDecodedMessageHandler(pigeonVar_channel, (Object? message) async { assert(message != null, 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchase2API.products was null.'); From 37a57c123aff4ce39c2d3a951f3aac2236aa2a80 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 27 Aug 2024 15:41:19 -0700 Subject: [PATCH 09/18] =?UTF-8?q?added=2010=20million=20doc=20comments=20?= =?UTF-8?q?=F0=9F=98=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StoreKit2/InAppPurchasePlugin2.swift | 11 ++-- .../example/ios/Runner/Configuration.storekit | 5 +- .../InAppPurchase2PluginTests.swift | 27 ++++++++- .../in_app_purchase_storekit_platform.dart | 12 +++- .../sk2_appstore_wrapper.dart | 5 +- .../sk2_product_wrapper.dart | 60 +++++++++++++++---- .../test/fakes/fake_storekit_platform.dart | 8 +-- ...app_purchase_storekit_2_platform_test.dart | 2 +- 8 files changed, 101 insertions(+), 29 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift index 6a2cc356c833..ed913b18fbe5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift @@ -25,11 +25,12 @@ extension InAppPurchasePlugin: InAppPurchase2API { } completion(.success(productMessages)) } catch { - throw PigeonError( - code: "storekit2_products_error", - message: error.localizedDescription, - details: error.localizedDescription - ) + completion( + .failure( + PigeonError( + code: "storekit2_products_error", + message: error.localizedDescription, + details: error.localizedDescription))) } } } 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 58f3d4304fc6..d4484faac8d2 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 @@ -51,7 +51,10 @@ "_storefront" : "USA", "_storeKitErrors" : [ { - "current" : null, + "current" : { + "index" : 2, + "type" : "generic" + }, "enabled" : false, "name" : "Load Products" }, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift index 160c85e6ad57..7e2b1e1522bf 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -61,13 +61,34 @@ final class InAppPurchase2PluginTests: XCTestCase { case .success(let productMessages): fetchedProductMsg = productMessages expectation.fulfill() - case .failure(let error): - // Handle the error - print("Failed to fetch products: \(error.localizedDescription)") + case .failure(_): + XCTFail("Products should be successfully fetched") } } await fulfillment(of: [expectation], timeout: 5) XCTAssert(fetchedProductMsg?.count == 0) } + + @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.contains("This operation couldn't be completed.")) + } + } + await fulfillment(of: [expectation], timeout: 5) + + // Reset test session + try await session.setSimulatedError(nil, forAPI: .loadProducts) + } } 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 8cc448ab4348..b8a53eaddf42 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 @@ -18,7 +18,8 @@ const String kPurchaseErrorCode = 'purchase_error'; /// Indicates store front is Apple AppStore. const String kIAPSource = 'app_store'; -const bool useStoreKit2 = true; +/// Experimental flag for StoreKit2. +bool _useStoreKit2 = true; /// An [InAppPurchasePlatform] that wraps StoreKit. /// @@ -69,7 +70,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future isAvailable() { - if (useStoreKit2) { + if (_useStoreKit2) { return AppStore().canMakePayments(); } return SKPaymentQueueWrapper.canMakePayments(); @@ -127,7 +128,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @override Future queryProductDetails( Set identifiers) async { - if (useStoreKit2) { + if (_useStoreKit2) { List products = []; Set invalidProductIdentifiers; PlatformException? exception; @@ -206,6 +207,11 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { /// Use countryCode instead. @Deprecated('Use countryCode') Future getCountryCode() => countryCode(); + + /// Turns on StoreKit2. You cannot disable this after it is enabled. + void enableStoreKit2() { + _useStoreKit2 = true; + } } enum _TransactionRestoreState { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart index 4caef67c1d44..a57ad5611266 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_appstore_wrapper.dart @@ -7,8 +7,11 @@ import '../../store_kit_2_wrappers.dart'; InAppPurchase2API _hostApi = InAppPurchase2API(); /// Wrapper for StoreKit2's AppStore -/// (https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments) +/// (https://developer.apple.com/documentation/storekit/appstore) final class AppStore { + /// Dart wrapper for StoreKit2's canMakePayments() + /// Returns a bool that indicates whether the person can make purchases. + /// (https://developer.apple.com/documentation/storekit/appstore/3822277-canmakepayments) Future canMakePayments() { return _hostApi.canMakePayments(); } 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 39adb7530f74..9decfa433c9b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_2_wrappers/sk2_product_wrapper.dart @@ -3,11 +3,11 @@ // 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(); +/// A wrapper around StoreKit2's ProductType /// https://developer.apple.com/documentation/storekit/product/producttype /// The types of in-app purchases. enum SK2ProductType { @@ -55,7 +55,16 @@ extension on SK2ProductType { } } -enum SK2SubscriptionOfferType { introductory, promotional } +/// A wrapper around StoreKit2's SubscriptionOfferType +/// https://developer.apple.com/documentation/appstoreserverapi/offertype/ +/// The subscription offer types. +enum SK2SubscriptionOfferType { + /// An introductory offer. + introductory, + + /// A A promotional offer. + promotional +} extension on SK2SubscriptionOfferTypeMessage { SK2SubscriptionOfferType convertFromPigeon() { @@ -68,8 +77,12 @@ extension on SK2SubscriptionOfferTypeMessage { } } +/// A wrapper around StoreKit2's SubscriptionOffer +/// https://developer.apple.com/documentation/storekit/product/subscriptionoffer +/// Information about a subscription offer on a product. class SK2SubscriptionOffer { - const SK2SubscriptionOffer({ + /// Creates a new [SK2SubscriptionOffer] + SK2SubscriptionOffer({ this.id, required this.price, required this.type, @@ -77,11 +90,23 @@ class SK2SubscriptionOffer { required this.periodCount, required this.paymentMode, }); + + /// The subscription offer identifier. final String? id; + + /// The decimal representation of the discounted price of the subscription offer. final double price; + + /// The type of subscription offer, either introductory or promotional. final SK2SubscriptionOfferType type; + + /// The subscription period for the subscription offer. final SK2SubscriptionPeriod period; + + /// The number of periods that the subscription offer renews for. final int periodCount; + + /// The payment modes for subscription offers that apply to a transaction. final SK2SubscriptionOfferPaymentMode paymentMode; } @@ -97,10 +122,12 @@ extension on SK2SubscriptionOfferMessage { } } +/// A wrapper around StoreKit2's SubscriptionInfo /// https://developer.apple.com/documentation/storekit/product/subscriptioninfo -/// Information about an auto-renewable subscription, such as its status, period, subscription group, and subscription offer details. +/// Information about an auto-renewable subscription, +/// such as its status, period, subscription group, and subscription offer details. class SK2SubscriptionInfo { - /// Constructor + /// Creates a new instance of [SK2SubscriptionInfo] const SK2SubscriptionInfo({ required this.subscriptionGroupID, required this.promotionalOffers, @@ -122,7 +149,8 @@ extension on SK2SubscriptionInfoMessage { return SK2SubscriptionInfo( subscriptionGroupID: subscriptionGroupID, // Note that promotionalOffers should NOT be nullable, but is only declared - // so because of pigeon weirdness. There should be NO NULLS. + // so because of pigeon cannot handle non null lists. + // There should be NO NULLS. promotionalOffers: promotionalOffers .whereType() .map((SK2SubscriptionOfferMessage offer) => @@ -132,9 +160,11 @@ extension on SK2SubscriptionInfoMessage { } } -/// https://developer.apple.com/documentation/storekit/product/subscriptionperiod?changes=latest_minor +/// A wrapper around StoreKit2's SubscriptionPeriod +/// https://developer.apple.com/documentation/storekit/product/subscriptionperiod /// Values that represent the duration of time between subscription renewals. class SK2SubscriptionPeriod { + /// Creates a new instance of [SK2SubscriptionPeriod] const SK2SubscriptionPeriod({required this.value, required this.unit}); /// The number of units that the period represents. @@ -150,6 +180,7 @@ extension on SK2SubscriptionPeriodMessage { } } +/// A wrapper around StoreKit2's SubscriptionPeriodUnit /// https://developer.apple.com/documentation/storekit/product/subscriptionperiod/3749576-unit /// The increment of time for the subscription period. enum SK2SubscriptionPeriodUnit { @@ -181,7 +212,7 @@ extension on SK2SubscriptionPeriodUnitMessage { } } -/// https://developer.apple.com/documentation/storekit/product/subscriptionoffer/paymentmode +/// A wrapper around StoreKit2's [PaymentMode](https://developer.apple.com/documentation/storekit/product/subscriptionoffer/paymentmode) /// The payment modes for subscription offers that apply to a transaction. enum SK2SubscriptionOfferPaymentMode { /// A payment mode of a product discount that applies over a single billing period or multiple billing periods. @@ -207,11 +238,19 @@ extension on SK2SubscriptionOfferPaymentModeMessage { } } +/// A wrapper around StoreKit2's [Locale](https://developer.apple.com/documentation/foundation/locale) +/// The payment modes for subscription offers that apply to a transaction. class SK2PriceLocale { + /// Creates a new instance of [SK2PriceLocale] SK2PriceLocale({required this.currencyCode, required this.currencySymbol}); + + /// The currency code this format style uses. final String currencyCode; + + /// The currency symbol this format style uses. final String currencySymbol; + /// Convert this instance of [SK2PriceLocale] to [SK2PriceLocaleMessage] SK2PriceLocaleMessage convertToPigeon() { return SK2PriceLocaleMessage( currencyCode: currencyCode, currencySymbol: currencySymbol); @@ -225,8 +264,7 @@ extension on SK2PriceLocaleMessage { } } -/// Dart wrapper around StoreKit2's [Product](https://developer.apple.com/documentation/storekit/product). -/// +/// 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. class SK2Product { @@ -286,6 +324,7 @@ class SK2Product { .toList(); } + /// Converts this instance of [SK2Product] to it's pigeon representation [SK2ProductMessage] SK2ProductMessage convertToPigeon() { return SK2ProductMessage( id: id, @@ -298,7 +337,6 @@ class SK2Product { } } -// let me test what i wanted you to type extension on SK2ProductMessage { SK2Product convertFromPigeon() { return SK2Product( 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 1083ebd755ec..fa05e4fcbf5c 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 @@ -289,9 +289,9 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { for (final String validID in validProductIDs) { final SK2Product product = SK2Product( id: validID, - displayName: "test_product", - displayPrice: "0.99", - description: "description", + displayName: 'test_product', + displayPrice: '0.99', + description: 'description', price: 0.99, type: SK2ProductType.consumable, priceLocale: @@ -318,7 +318,7 @@ class FakeStoreKit2Platform implements TestInAppPurchase2Api { } } final List result = []; - for (SK2Product p in products) { + for (final SK2Product p in products) { result.add(p.convertToPigeon()); } 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 da9840493f8f..ff31756f7605 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 @@ -17,7 +17,7 @@ void main() { late InAppPurchaseStoreKitPlatform iapStoreKitPlatform; setUpAll(() { - TestInAppPurchase2Api.setup(fakeStoreKit2Platform); + TestInAppPurchase2Api.setUp(fakeStoreKit2Platform); }); setUp(() { From 93123c4a23a1a557e69246fe286a5074725f1f5b Mon Sep 17 00:00:00 2001 From: louisehsu Date: Tue, 27 Aug 2024 15:51:11 -0700 Subject: [PATCH 10/18] oops flag --- .../lib/src/in_app_purchase_storekit_platform.dart | 4 ++-- .../test/in_app_purchase_storekit_2_platform_test.dart | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) 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 b8a53eaddf42..2c30315ca280 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 @@ -19,7 +19,7 @@ const String kPurchaseErrorCode = 'purchase_error'; const String kIAPSource = 'app_store'; /// Experimental flag for StoreKit2. -bool _useStoreKit2 = true; +bool _useStoreKit2 = false; /// An [InAppPurchasePlatform] that wraps StoreKit. /// @@ -209,7 +209,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { Future getCountryCode() => countryCode(); /// Turns on StoreKit2. You cannot disable this after it is enabled. - void enableStoreKit2() { + static void enableStoreKit2() { _useStoreKit2 = true; } } 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 ff31756f7605..1b8635245567 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 @@ -22,6 +22,7 @@ void main() { setUp(() { InAppPurchaseStoreKitPlatform.registerPlatform(); + InAppPurchaseStoreKitPlatform.enableStoreKit2(); iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; fakeStoreKit2Platform.reset(); From 7f838656833398312426faffb947db774e0a4c45 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 28 Aug 2024 13:53:53 -0700 Subject: [PATCH 11/18] translatorTests --- .../ios/Runner.xcodeproj/project.pbxproj | 4 + .../StoreKit2TranslatorTests.swift | 82 +++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/StoreKit2TranslatorTests.swift 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 c9c880330a5b..72cc26490737 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,6 +24,7 @@ F27694172C49DBCA00277144 /* FIATransactionCacheTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F27694162C49DBCA00277144 /* FIATransactionCacheTests.swift */; }; F2858EE72C76A3B70063A092 /* InAppPurchase2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.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 */; }; F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */; }; F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD402C1256F50067C78A /* TranslatorTests.m */; }; @@ -82,6 +83,7 @@ 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 /* InAppPurchase2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchase2PluginTests.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 = ""; }; F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = ""; }; @@ -200,6 +202,7 @@ F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.swift */, + F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */, ); path = RunnerTests; sourceTree = ""; @@ -441,6 +444,7 @@ F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, F2858EE72C76A3B70063A092 /* InAppPurchase2PluginTests.swift in Sources */, + F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */, F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */, 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 100644 index 000000000000..37d42855391c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/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 XCTest +import StoreKitTest + +@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 30e858e692165dc9e13f41811c14cea5dcc3af32 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 28 Aug 2024 16:38:30 -0700 Subject: [PATCH 12/18] format, fix podspec --- .../darwin/Classes/InAppPurchasePlugin.swift | 2 +- .../InAppPurchase2PluginTests.swift | 2 +- .../StoreKit2TranslatorTests.swift | 50 +++++++++---------- 3 files changed, 27 insertions(+), 27 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 f24e88ae7745..a591739eb693 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.swift @@ -41,7 +41,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { registrar.addMethodCallDelegate(instance, channel: channel) registrar.addApplicationDelegate(instance) SetUpInAppPurchaseAPI(messenger, instance) - if #available(iOS 15.0, *) { + if #available(iOS 15.0, macOS 12.0, *) { InAppPurchase2APISetup.setUp(binaryMessenger: messenger, api: instance) } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift index 7e2b1e1522bf..637c524509df 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -83,7 +83,7 @@ final class InAppPurchase2PluginTests: XCTestCase { XCTFail("This `products` call should not succeed") case .failure(let error): expectation.fulfill() - XCTAssert(error.localizedDescription.contains("This operation couldn't be completed.")) + XCTAssert(error.localizedDescription.contains("The operation couldn't be completed.")) } } await fulfillment(of: [expectation], timeout: 5) 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 index 37d42855391c..108641ef4a94 100644 --- 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 @@ -3,8 +3,8 @@ // found in the LICENSE file. import Foundation -import XCTest import StoreKitTest +import XCTest @testable import in_app_purchase_storekit @@ -16,20 +16,20 @@ class StoreKit2TranslatorTests: XCTestCase { // 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: "$")) + 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() @@ -40,43 +40,43 @@ class StoreKit2TranslatorTests: XCTestCase { plugin = InAppPurchasePluginStub(receiptManager: receiptManagerStub) { request in DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: request)) } - product = try await Product.products(for: ["subscription_silver"]).first!; + product = try await Product.products(for: ["subscription_silver"]).first! } func testPigeonConversionForProduct() async throws { - XCTAssertNotNil(product); + XCTAssertNotNil(product) let pigeonMessage = product.convertToPigeon() - XCTAssertEqual(pigeonMessage, productMessage); + XCTAssertEqual(pigeonMessage, productMessage) } func testPigeonConversionForSubscriptionInfo() async throws { guard let subscription = product.subscription else { XCTFail("SubscriptionInfo should not be nil") - return; + return } let pigeonMessage = subscription.convertToPigeon() - XCTAssertEqual(pigeonMessage, productMessage.subscription); + XCTAssertEqual(pigeonMessage, productMessage.subscription) } func testPigeonConversionForProductType() async throws { let type = product.type let pigeonMessage = type.convertToPigeon() - XCTAssertEqual(pigeonMessage, productMessage.type); + XCTAssertEqual(pigeonMessage, productMessage.type) } func testPigeonConversionForSubscriptionPeriod() async throws { guard let period = product.subscription?.subscriptionPeriod else { XCTFail("SubscriptionPeriod should not be nil") - return; + return } let pigeonMessage = period.convertToPigeon() - XCTAssertEqual(pigeonMessage, productMessage.subscription?.subscriptionPeriod); + XCTAssertEqual(pigeonMessage, productMessage.subscription?.subscriptionPeriod) } func testPigeonConversionForPriceLocale() async throws { - let locale = product.priceFormatStyle.locale; + let locale = product.priceFormatStyle.locale let pigeonMessage = locale.convertToPigeon() - XCTAssertEqual(pigeonMessage, productMessage.priceLocale); + XCTAssertEqual(pigeonMessage, productMessage.priceLocale) } } From 8455690b586db0c3bdcec6e37f06cf9281bd6165 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Wed, 28 Aug 2024 16:42:07 -0700 Subject: [PATCH 13/18] version --- .../in_app_purchase/in_app_purchase_storekit/CHANGELOG.md | 4 ++++ .../in_app_purchase/in_app_purchase_storekit/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) 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 982f192746a2..d3360b829140 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+1 + +* Adds support for StoreKit2's `canMakePayments` and `products` + ## 0.3.17+2 * Converts FIAPPaymentQueueDeleteTests to swift. 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 dd3a3af933da..53ddbb138aae 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.17+2 +version: 0.3.18+1 environment: sdk: ^3.2.3 From d8d485d49af45610a8fbd05f79381439830b2a3e Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 29 Aug 2024 11:49:01 -0700 Subject: [PATCH 14/18] remove swift 5.6 language feature --- .../darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift | 2 +- .../example/ios/RunnerTests/InAppPurchase2PluginTests.swift | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift index ed913b18fbe5..cd5fb71ce72d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift @@ -15,7 +15,7 @@ extension InAppPurchasePlugin: InAppPurchase2API { // Wrapper method around StoreKit2's products() method // https://developer.apple.com/documentation/storekit/product/3851116-products func products( - identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], any Error>) -> Void + identifiers: [String], completion: @escaping (Result<[SK2ProductMessage], Error>) -> Void ) { Task { do { diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift index 637c524509df..b51cfde6df9d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -83,7 +83,8 @@ final class InAppPurchase2PluginTests: XCTestCase { XCTFail("This `products` call should not succeed") case .failure(let error): expectation.fulfill() - XCTAssert(error.localizedDescription.contains("The operation couldn't be completed.")) + print(error.localizedDescription) + XCTAssert(error.localizedDescription == "The operation couldn’t be completed. (in_app_purchase_storekit.PigeonError error 1.)") } } await fulfillment(of: [expectation], timeout: 5) From 3a0645dcea74519a3a6cb824926f2847274290d2 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 29 Aug 2024 11:53:38 -0700 Subject: [PATCH 15/18] format --- .../example/ios/RunnerTests/InAppPurchase2PluginTests.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift index b51cfde6df9d..33dc49369e65 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift @@ -84,7 +84,10 @@ final class InAppPurchase2PluginTests: XCTestCase { 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.)") + XCTAssert( + error.localizedDescription + == "The operation couldn’t be completed. (in_app_purchase_storekit.PigeonError error 1.)" + ) } } await fulfillment(of: [expectation], timeout: 5) From 6b3cb7d04cb021bc077bb727fcf38683702b117f Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 29 Aug 2024 14:00:07 -0700 Subject: [PATCH 16/18] . --- ...in2.swift => InAppPurchaseStoreKit2.swift} | 4 +-- .../StoreKit2/StoreKit2Translators.swift | 36 +++++++++---------- .../ios/Runner.xcodeproj/project.pbxproj | 13 ++++--- ...> InAppPurchaseStoreKit2PluginTests.swift} | 7 ++-- .../StoreKit2TranslatorTests.swift | 2 +- .../in_app_purchase_storekit_platform.dart | 8 ++--- ...app_purchase_storekit_2_platform_test.dart | 2 +- 7 files changed, 36 insertions(+), 36 deletions(-) rename packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/{InAppPurchasePlugin2.swift => InAppPurchaseStoreKit2.swift} (92%) rename packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/{InAppPurchase2PluginTests.swift => InAppPurchaseStoreKit2PluginTests.swift} (94%) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift similarity index 92% rename from packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift rename to packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift index cd5fb71ce72d..7ab0405ae674 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchasePlugin2.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/InAppPurchaseStoreKit2.swift @@ -20,8 +20,8 @@ extension InAppPurchasePlugin: InAppPurchase2API { Task { do { let products = try await Product.products(for: identifiers) - let productMessages = products.map { product in - product.convertToPigeon() + let productMessages = products.map { + $0.convertToPigeon } completion(.success(productMessages)) } catch { 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 af9ea7810eed..aed0059733e0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/StoreKit2/StoreKit2Translators.swift @@ -7,7 +7,7 @@ import StoreKit @available(iOS 15.0, macOS 12.0, *) extension Product { - func convertToPigeon() -> SK2ProductMessage { + var convertToPigeon: SK2ProductMessage { return SK2ProductMessage( id: id, @@ -15,9 +15,9 @@ extension Product { description: description, price: NSDecimalNumber(decimal: price).doubleValue, displayPrice: displayPrice, - type: type.convertToPigeon(), - subscription: subscription?.convertToPigeon(), - priceLocale: priceFormatStyle.locale.convertToPigeon() + type: type.convertToPigeon, + subscription: subscription?.convertToPigeon, + priceLocale: priceFormatStyle.locale.convertToPigeon ) } } @@ -33,7 +33,7 @@ extension SK2ProductMessage: Equatable { @available(iOS 15.0, macOS 12.0, *) extension Product.ProductType { - func convertToPigeon() -> SK2ProductTypeMessage { + var convertToPigeon: SK2ProductTypeMessage { switch self { case Product.ProductType.autoRenewable: return SK2ProductTypeMessage.autoRenewable @@ -51,11 +51,11 @@ extension Product.ProductType { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionInfo { - func convertToPigeon() -> SK2SubscriptionInfoMessage { + var convertToPigeon: SK2SubscriptionInfoMessage { return SK2SubscriptionInfoMessage( - promotionalOffers: promotionalOffers.map({ $0.convertToPigeon() }), + promotionalOffers: promotionalOffers.map({ $0.convertToPigeon }), subscriptionGroupID: subscriptionGroupID, - subscriptionPeriod: subscriptionPeriod.convertToPigeon()) + subscriptionPeriod: subscriptionPeriod.convertToPigeon) } } @@ -69,15 +69,15 @@ extension SK2SubscriptionInfoMessage: Equatable { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionOffer { - func convertToPigeon() -> SK2SubscriptionOfferMessage { + var convertToPigeon: SK2SubscriptionOfferMessage { return SK2SubscriptionOfferMessage( /// ID is always `nil` for introductory offers and never `nil` for other offer types. id: id, price: NSDecimalNumber(decimal: price).doubleValue, - type: type.convertToPigeon(), - period: period.convertToPigeon(), + type: type.convertToPigeon, + period: period.convertToPigeon, periodCount: Int64(periodCount), - paymentMode: paymentMode.convertToPigeon() + paymentMode: paymentMode.convertToPigeon ) } } @@ -92,7 +92,7 @@ extension SK2SubscriptionOfferMessage: Equatable { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionOffer.OfferType { - func convertToPigeon() -> SK2SubscriptionOfferTypeMessage { + var convertToPigeon: SK2SubscriptionOfferTypeMessage { switch self { case .introductory: return SK2SubscriptionOfferTypeMessage.introductory @@ -106,10 +106,10 @@ extension Product.SubscriptionOffer.OfferType { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionPeriod { - func convertToPigeon() -> SK2SubscriptionPeriodMessage { + var convertToPigeon: SK2SubscriptionPeriodMessage { return SK2SubscriptionPeriodMessage( value: Int64(value), - unit: unit.convertToPigeon()) + unit: unit.convertToPigeon) } } @@ -121,7 +121,7 @@ extension SK2SubscriptionPeriodMessage: Equatable { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionPeriod.Unit { - func convertToPigeon() -> SK2SubscriptionPeriodUnitMessage { + var convertToPigeon: SK2SubscriptionPeriodUnitMessage { switch self { case .day: return SK2SubscriptionPeriodUnitMessage.day @@ -139,7 +139,7 @@ extension Product.SubscriptionPeriod.Unit { @available(iOS 15.0, macOS 12.0, *) extension Product.SubscriptionOffer.PaymentMode { - func convertToPigeon() -> SK2SubscriptionOfferPaymentModeMessage { + var convertToPigeon: SK2SubscriptionOfferPaymentModeMessage { switch self { case .freeTrial: return SK2SubscriptionOfferPaymentModeMessage.freeTrial @@ -154,7 +154,7 @@ extension Product.SubscriptionOffer.PaymentMode { } extension Locale { - func convertToPigeon() -> SK2PriceLocaleMessage { + var convertToPigeon: SK2PriceLocaleMessage { return SK2PriceLocaleMessage( currencyCode: currencyCode ?? "", currencySymbol: currencySymbol ?? "" 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 052ed9b947e5..caf8c62f6e80 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Runner.xcodeproj/project.pbxproj @@ -22,12 +22,12 @@ 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 /* InAppPurchase2PluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.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 */; }; - F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D527292C583C4A00C137C7 /* TranslatorTests.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 */ /* Begin PBXContainerItemProxy section */ @@ -82,12 +82,12 @@ 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 /* InAppPurchase2PluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppPurchase2PluginTests.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 = ""; }; - F2D527292C583C4A00C137C7 /* TranslatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TranslatorTests.swift; path = ../../shared/RunnerTests/TranslatorTests.swift; sourceTree = ""; }; 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 = ""; }; /* End PBXFileReference section */ @@ -201,7 +201,7 @@ F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, - F2858EE62C76A3B70063A092 /* InAppPurchase2PluginTests.swift */, + F2858EE62C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift */, F2858EEF2C7F98E70063A092 /* StoreKit2TranslatorTests.swift */, ); path = RunnerTests; @@ -444,9 +444,8 @@ F2D5271A2C50627500C137C7 /* PaymentQueueTests.swift in Sources */, F24C45E22C409D42000C6C72 /* InAppPurchasePluginTests.swift in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, - F2858EE72C76A3B70063A092 /* InAppPurchase2PluginTests.swift in Sources */, + F2858EE72C76A3B70063A092 /* InAppPurchaseStoreKit2PluginTests.swift in Sources */, F2858EF02C7F98E70063A092 /* StoreKit2TranslatorTests.swift in Sources */, - F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */, F276940B2C47268700277144 /* ProductRequestHandlerTests.swift in Sources */, F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, F2D5272A2C583C4A00C137C7 /* TranslatorTests.swift in Sources */, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift similarity index 94% rename from packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift rename to packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift index 33dc49369e65..20a8bf8621b5 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchase2PluginTests.swift +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/RunnerTests/InAppPurchaseStoreKit2PluginTests.swift @@ -9,8 +9,8 @@ import XCTest @available(iOS 15.0, *) final class InAppPurchase2PluginTests: XCTestCase { - var session: SKTestSession! - var plugin: InAppPurchasePlugin! + private var session: SKTestSession! + private var plugin: InAppPurchasePlugin! override func setUp() async throws { try await super.setUp() @@ -46,7 +46,7 @@ final class InAppPurchase2PluginTests: XCTestCase { let testProduct = try await Product.products(for: ["subscription_silver"]).first - let testProductMsg = testProduct?.convertToPigeon() + let testProductMsg = testProduct?.convertToPigeon XCTAssertNotNil(fetchedProductMsg) XCTAssertEqual(testProductMsg, fetchedProductMsg) @@ -70,6 +70,7 @@ final class InAppPurchase2PluginTests: XCTestCase { XCTAssert(fetchedProductMsg?.count == 0) } + //TODO(louisehsu): Add testing for lower versions. @available(iOS 17.0, *) func testGetProductsWithStoreKitError() async throws { try await session.setSimulatedError( 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 index 108641ef4a94..a4d0799b8ec5 100644 --- 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 @@ -46,7 +46,7 @@ class StoreKit2TranslatorTests: XCTestCase { func testPigeonConversionForProduct() async throws { XCTAssertNotNil(product) - let pigeonMessage = product.convertToPigeon() + let pigeonMessage = product.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage) } 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 2c30315ca280..4bbd863b8447 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/in_app_purchase_storekit_platform.dart @@ -18,9 +18,6 @@ const String kPurchaseErrorCode = 'purchase_error'; /// Indicates store front is Apple AppStore. const String kIAPSource = 'app_store'; -/// Experimental flag for StoreKit2. -bool _useStoreKit2 = false; - /// An [InAppPurchasePlatform] that wraps StoreKit. /// /// This translates various `StoreKit` calls and responses into the @@ -33,6 +30,9 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { @visibleForTesting InAppPurchaseStoreKitPlatform(); + /// Experimental flag for StoreKit2. + static bool _useStoreKit2 = false; + static late SKPaymentQueueWrapper _skPaymentQueueWrapper; static late _TransactionObserver _observer; @@ -209,7 +209,7 @@ class InAppPurchaseStoreKitPlatform extends InAppPurchasePlatform { Future getCountryCode() => countryCode(); /// Turns on StoreKit2. You cannot disable this after it is enabled. - static void enableStoreKit2() { + void enableStoreKit2() { _useStoreKit2 = true; } } 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 1b8635245567..afc08cf71b9e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/in_app_purchase_storekit_2_platform_test.dart @@ -22,9 +22,9 @@ void main() { setUp(() { InAppPurchaseStoreKitPlatform.registerPlatform(); - InAppPurchaseStoreKitPlatform.enableStoreKit2(); iapStoreKitPlatform = InAppPurchasePlatform.instance as InAppPurchaseStoreKitPlatform; + iapStoreKitPlatform.enableStoreKit2(); fakeStoreKit2Platform.reset(); }); From 4bee70f0d32cdc240729e578dae4b5baae118a89 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 29 Aug 2024 14:08:47 -0700 Subject: [PATCH 17/18] . --- .../ios/RunnerTests/StoreKit2TranslatorTests.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index a4d0799b8ec5..44a64132dd8a 100644 --- 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 @@ -55,13 +55,13 @@ class StoreKit2TranslatorTests: XCTestCase { XCTFail("SubscriptionInfo should not be nil") return } - let pigeonMessage = subscription.convertToPigeon() + let pigeonMessage = subscription.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage.subscription) } func testPigeonConversionForProductType() async throws { let type = product.type - let pigeonMessage = type.convertToPigeon() + let pigeonMessage = type.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage.type) } @@ -70,13 +70,13 @@ class StoreKit2TranslatorTests: XCTestCase { XCTFail("SubscriptionPeriod should not be nil") return } - let pigeonMessage = period.convertToPigeon() + let pigeonMessage = period.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage.subscription?.subscriptionPeriod) } func testPigeonConversionForPriceLocale() async throws { let locale = product.priceFormatStyle.locale - let pigeonMessage = locale.convertToPigeon() + let pigeonMessage = locale.convertToPigeon XCTAssertEqual(pigeonMessage, productMessage.priceLocale) } } From c8ccd5eb694301828f7c4fe9258423bf935aabf9 Mon Sep 17 00:00:00 2001 From: louisehsu Date: Thu, 29 Aug 2024 14:16:34 -0700 Subject: [PATCH 18/18] . --- .../lib/src/types/app_store_product_details.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart index c36a2d94ded8..f0ab7257b375 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/types/app_store_product_details.dart @@ -44,7 +44,7 @@ class AppStoreProductDetails extends ProductDetails { final SKProductWrapper skProduct; } -/// The class represents the information of a product as registered in the Apple +/// The class represents the information of a StoreKit2 product as registered in the Apple /// AppStore. class AppStoreProduct2Details extends ProductDetails { /// Creates a new AppStore specific product details object with the provided