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 070f0753e8a07..18072ee2a3ee4 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,6 +1,10 @@ +## 0.3.17 + +* Removes OCMock from tests. + ## 0.3.16 -* Converts main plugin class to Swift +* Converts main plugin class to Swift. ## 0.3.15 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h index 4347846f54caa..12ef96bee5c22 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.h @@ -9,13 +9,14 @@ #endif #import #import +#import "FLTMethodChannelProtocol.h" NS_ASSUME_NONNULL_BEGIN API_AVAILABLE(ios(13)) API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegate : NSObject -- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel; +- (id)initWithMethodChannel:(id)methodChannel; @end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m index cb18d9b86d663..d0efb06861a9a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPPaymentQueueDelegate.m @@ -7,13 +7,14 @@ @interface FIAPPaymentQueueDelegate () -@property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel; +// The designated Flutter method channel that handles if a transaction should be continued +@property(nonatomic, strong, readonly) id callbackChannel; @end @implementation FIAPPaymentQueueDelegate -- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel { +- (id)initWithMethodChannel:(id)methodChannel { self = [super init]; if (self) { _callbackChannel = methodChannel; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m index 22a3973b7fca0..503eb0fcf3e27 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPReceiptManager.m @@ -14,13 +14,15 @@ @interface FIAPReceiptManager () // Gets the receipt file data from the location of the url. Can be nil if // there is an error. This interface is defined so it can be stubbed for testing. - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error; - +// Gets the app store receipt url. Can be nil if +// there is an error. This property is defined so it can be stubbed for testing. +@property(nonatomic, readonly) NSURL *receiptURL; @end @implementation FIAPReceiptManager - (NSString *)retrieveReceiptWithError:(FlutterError **)flutterError { - NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; + NSURL *receiptURL = self.receiptURL; if (!receiptURL) { return nil; } @@ -43,4 +45,8 @@ - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { return [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:error]; } +- (NSURL *)receiptURL { + return [[NSBundle mainBundle] appStoreReceiptURL]; +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h index cbf21d6e161f2..ea6e2b736afe9 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.h @@ -2,19 +2,23 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#import #import #import NS_ASSUME_NONNULL_BEGIN -typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response, - NSError *_Nullable errror); - -@interface FIAPRequestHandler : NSObject +@interface FIAPRequestHandler : NSObject - (instancetype)initWithRequest:(SKRequest *)request; - (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion; @end +// The default request handler that wraps FIAPRequestHandler +@interface DefaultRequestHandler : NSObject + +// Initialize this wrapper with an instance of FIAPRequestHandler +- (instancetype)initWithRequestHandler:(FIAPRequestHandler *)handler; +@end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m index 8767265d8544c..d2ef6829eecc6 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPRequestHandler.m @@ -9,8 +9,8 @@ @interface FIAPRequestHandler () -@property(copy, nonatomic) ProductRequestCompletion completion; -@property(strong, nonatomic) SKRequest *request; +@property(nonatomic, copy) ProductRequestCompletion completion; +@property(nonatomic, strong) SKRequest *request; @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h index 356940cd39ba4..68ddcaa765159 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.h @@ -5,130 +5,16 @@ #import #import #import "FIATransactionCache.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTTransactionCacheProtocol.h" @class SKPaymentTransaction; NS_ASSUME_NONNULL_BEGIN -typedef void (^TransactionsUpdated)(NSArray *transactions); -typedef void (^TransactionsRemoved)(NSArray *transactions); -typedef void (^RestoreTransactionFailed)(NSError *error); -typedef void (^RestoreCompletedTransactionsFinished)(void); -typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product); -typedef void (^UpdatedDownloads)(NSArray *downloads); - -@interface FIAPaymentQueueHandler : NSObject - -@property(NS_NONATOMIC_IOSONLY, weak, nullable) id delegate API_AVAILABLE( - ios(13.0), macos(10.15), watchos(6.2)); -@property(nonatomic, readonly, nullable) - SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2)); - -/// Creates a new FIAPaymentQueueHandler initialized with an empty -/// FIATransactionCache. -/// -/// @param queue The SKPaymentQueue instance connected to the App Store and -/// responsible for processing transactions. -/// @param transactionsUpdated Callback method that is called each time the App -/// Store indicates transactions are updated. -/// @param transactionsRemoved Callback method that is called each time the App -/// Store indicates transactions are removed. -/// @param restoreTransactionFailed Callback method that is called each time -/// the App Store indicates transactions failed -/// to restore. -/// @param restoreCompletedTransactionsFinished Callback method that is called -/// each time the App Store -/// indicates restoring of -/// transactions has finished. -/// @param shouldAddStorePayment Callback method that is called each time an -/// in-app purchase has been initiated from the -/// App Store. -/// @param updatedDownloads Callback method that is called each time the App -/// Store indicates downloads are updated. -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue - transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated - transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved - restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed - restoreCompletedTransactionsFinished: - (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished - shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment - updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - DEPRECATED_MSG_ATTRIBUTE( - "Use the " - "'initWithQueue:transactionsUpdated:transactionsRemoved:restoreTransactionsFinished:" - "shouldAddStorePayment:updatedDownloads:transactionCache:' message instead."); - -/// Creates a new FIAPaymentQueueHandler. -/// -/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" -/// callbacks are only called while actively observing transactions. To start -/// observing transactions send the "startObservingPaymentQueue" message. -/// Sending the "stopObservingPaymentQueue" message will stop actively -/// observing transactions. When transactions are not observed they are cached -/// to the "transactionCache" and will be delivered via the -/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" -/// callbacks as soon as the "startObservingPaymentQueue" message arrives. -/// -/// Note: cached transactions that are not processed when the application is -/// killed will be delivered again by the App Store as soon as the application -/// starts again. -/// -/// @param queue The SKPaymentQueue instance connected to the App Store and -/// responsible for processing transactions. -/// @param transactionsUpdated Callback method that is called each time the App -/// Store indicates transactions are updated. -/// @param transactionsRemoved Callback method that is called each time the App -/// Store indicates transactions are removed. -/// @param restoreTransactionFailed Callback method that is called each time -/// the App Store indicates transactions failed -/// to restore. -/// @param restoreCompletedTransactionsFinished Callback method that is called -/// each time the App Store -/// indicates restoring of -/// transactions has finished. -/// @param shouldAddStorePayment Callback method that is called each time an -/// in-app purchase has been initiated from the -/// App Store. -/// @param updatedDownloads Callback method that is called each time the App -/// Store indicates downloads are updated. -/// @param transactionCache An empty [FIATransactionCache] instance that is -/// responsible for keeping track of transactions that -/// arrive when not actively observing transactions. -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue - transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated - transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved - restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed - restoreCompletedTransactionsFinished: - (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished - shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment - updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - transactionCache:(nonnull FIATransactionCache *)transactionCache; -// Can throw exceptions if the transaction type is purchasing, should always used in a @try block. -- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; -- (void)restoreTransactions:(nullable NSString *)applicationName; -- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos); -- (NSArray *)getUnfinishedTransactions; - -// This method needs to be called before any other methods. -- (void)startObservingPaymentQueue; -// Call this method when the Flutter app is no longer listening -- (void)stopObservingPaymentQueue; - -// Appends a payment to the SKPaymentQueue. -// -// @param payment Payment object to be added to the payment queue. -// @return whether "addPayment" was successful. -- (BOOL)addPayment:(SKPayment *)payment; - -// Displays the price consent sheet. -// -// The price consent sheet is only displayed when the following -// is true: -// - You have increased the price of the subscription in App Store Connect. -// - The subscriber has not yet responded to a price consent query. -// Otherwise the method has no effect. -- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos); - +@interface FIAPaymentQueueHandler + : NSObject @end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m index 600565750897f..e221ce0695c7b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAPaymentQueueHandler.m @@ -10,34 +10,34 @@ @interface FIAPaymentQueueHandler () /// The SKPaymentQueue instance connected to the App Store and responsible for processing /// transactions. -@property(strong, nonatomic) SKPaymentQueue *queue; +@property(nonatomic, strong) SKPaymentQueue *queue; /// Callback method that is called each time the App Store indicates transactions are updated. -@property(nullable, copy, nonatomic) TransactionsUpdated transactionsUpdated; +@property(nonatomic, nullable, copy) TransactionsUpdated transactionsUpdated; /// Callback method that is called each time the App Store indicates transactions are removed. -@property(nullable, copy, nonatomic) TransactionsRemoved transactionsRemoved; +@property(nonatomic, nullable, copy) TransactionsRemoved transactionsRemoved; /// Callback method that is called each time the App Store indicates transactions failed to restore. -@property(nullable, copy, nonatomic) RestoreTransactionFailed restoreTransactionFailed; +@property(nonatomic, nullable, copy) RestoreTransactionFailed restoreTransactionFailed; /// Callback method that is called each time the App Store indicates restoring of transactions has /// finished. -@property(nullable, copy, nonatomic) +@property(nonatomic, nullable, copy) RestoreCompletedTransactionsFinished paymentQueueRestoreCompletedTransactionsFinished; /// Callback method that is called each time an in-app purchase has been initiated from the App /// Store. -@property(nullable, copy, nonatomic) ShouldAddStorePayment shouldAddStorePayment; +@property(nonatomic, nullable, copy) ShouldAddStorePayment shouldAddStorePayment; /// Callback method that is called each time the App Store indicates downloads are updated. -@property(nullable, copy, nonatomic) UpdatedDownloads updatedDownloads; +@property(nonatomic, nullable, copy) UpdatedDownloads updatedDownloads; /// The transaction cache responsible for caching transactions. /// /// Keeps track of transactions that arrive when the Flutter client is not /// actively observing for transactions. -@property(strong, nonatomic, nonnull) FIATransactionCache *transactionCache; +@property(nonatomic, strong, nonnull) FIATransactionCache *transactionCache; /// Indicates if the Flutter client is observing transactions. /// @@ -51,7 +51,9 @@ @interface FIAPaymentQueueHandler () @implementation FIAPaymentQueueHandler -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue +@synthesize delegate; + +- (instancetype)initWithQueue:(nonnull id)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed @@ -66,10 +68,10 @@ - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue restoreCompletedTransactionsFinished:restoreCompletedTransactionsFinished shouldAddStorePayment:shouldAddStorePayment updatedDownloads:updatedDownloads - transactionCache:[[FIATransactionCache alloc] init]]; + transactionCache:[[DefaultTransactionCache alloc] init]]; } -- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue +- (instancetype)initWithQueue:(nonnull id)queue transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed @@ -77,7 +79,7 @@ - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment updatedDownloads:(nullable UpdatedDownloads)updatedDownloads - transactionCache:(nonnull FIATransactionCache *)transactionCache { + transactionCache:(nonnull id)transactionCache { self = [super init]; if (self) { _queue = queue; @@ -170,7 +172,7 @@ - (void)presentCodeRedemptionSheet { #endif #if TARGET_OS_IOS -- (void)showPriceConsentIfNeeded { +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4)) { [self.queue showPriceConsentIfNeeded]; } #endif @@ -233,7 +235,7 @@ - (BOOL)paymentQueue:(SKPaymentQueue *)queue return self.queue.transactions; } -- (SKStorefront *)storefront { +- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) { return self.queue.storefront; } 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 511fc264c0a52..4ebd882818429 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 @@ -18,17 +18,19 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // note - the type should be FIAPPaymentQueueDelegate, but this is only available >= iOS 13, // FIAPPaymentQueueDelegate only gets set/used in registerPaymentQueueDelegateWithError or removePaymentQueueDelegateWithError, which both are ios13+ only private var paymentQueueDelegate: Any? - private var requestHandlers = Set() - private var handlerFactory: ((SKRequest) -> FIAPRequestHandler) + // Swift sets do not accept protocols, only concrete implementations + // TODO(louisehsu): Change it back to a set when removing obj-c dependancies from this file via type erasure + private var requestHandlers = NSHashTable() + private var handlerFactory: ((SKRequest) -> FLTRequestHandlerProtocol) // TODO(louisehsu): Once tests are migrated to swift, we can use @testable import, and make theses vars private again and remove all instances of @objc @objc public var registrar: FlutterPluginRegistrar? // This property is optional, as it requires self to exist to be initialized. @objc - public var paymentQueueHandler: FIAPaymentQueueHandler? + public var paymentQueueHandler: FLTPaymentQueueHandlerProtocol? // This property is optional, as it needs to be set during plugin registration, and can't be directly initialized. @objc - public var transactionObserverCallbackChannel: FlutterMethodChannel? + public var transactionObserverCallbackChannel: FLTMethodChannelProtocol? public static func register(with registrar: FlutterPluginRegistrar) { #if os(iOS) @@ -50,8 +52,8 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { // This init is used for tests public init( receiptManager: FIAPReceiptManager, - handlerFactory: @escaping (SKRequest) -> FIAPRequestHandler = { - FIAPRequestHandler(request: $0) + handlerFactory: @escaping (SKRequest) -> FLTRequestHandlerProtocol = { + DefaultRequestHandler(requestHandler: FIAPRequestHandler(request: $0)) } ) { self.receiptManager = receiptManager @@ -65,7 +67,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { self.registrar = registrar self.paymentQueueHandler = FIAPaymentQueueHandler( - queue: SKPaymentQueue.default(), + queue: DefaultPaymentQueue(queue: SKPaymentQueue.default()), transactionsUpdated: { [weak self] transactions in self?.handleTransactionsUpdated(transactions) }, @@ -84,15 +86,18 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { updatedDownloads: { [weak self] _ in self?.updatedDownloads() }, - transactionCache: FIATransactionCache()) + transactionCache: DefaultTransactionCache(cache: FIATransactionCache())) #if os(iOS) let messenger = registrar.messenger() #endif #if os(macOS) let messenger = registrar.messenger #endif - transactionObserverCallbackChannel = FlutterMethodChannel( - name: "plugins.flutter.io/in_app_purchase", binaryMessenger: messenger) + transactionObserverCallbackChannel = DefaultMethodChannel( + channel: FlutterMethodChannel( + name: "plugins.flutter.io/in_app_purchase", + binaryMessenger: messenger) + ) } // MARK: - Pigeon Functions @@ -128,7 +133,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { ) { let request = getProductRequest(withIdentifiers: Set(productIdentifiers)) let handler = handlerFactory(request) - requestHandlers.insert(handler) + requestHandlers.add(handler) handler.startProductRequest { [weak self] response, startProductRequestError in guard let self = self else { return } @@ -282,7 +287,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { let properties = receiptProperties?.compactMapValues { $0 } ?? [:] let request = getRefreshReceiptRequest(properties: properties.isEmpty ? nil : properties) let handler = handlerFactory(request) - requestHandlers.insert(handler) + requestHandlers.add(handler) handler.startProductRequest { [weak self] response, error in if let error = error { let requestError = FlutterError( @@ -322,10 +327,10 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { binaryMessenger: messenger) guard let unwrappedChannel = paymentQueueDelegateCallbackChannel else { - fatalError("registrar.messenger can not be nil.") + fatalError("paymentQueueDelegateCallbackChannel can not be nil.") } paymentQueueDelegate = FIAPPaymentQueueDelegate( - methodChannel: unwrappedChannel) + methodChannel: DefaultMethodChannel(channel: unwrappedChannel)) getPaymentQueueHandler().delegate = paymentQueueDelegate as? SKPaymentQueueDelegate } @@ -419,7 +424,7 @@ public class InAppPurchasePlugin: NSObject, FlutterPlugin, InAppPurchaseAPI { return value is NSNull ? nil : value } - private func getPaymentQueueHandler() -> FIAPaymentQueueHandler { + private func getPaymentQueueHandler() -> FLTPaymentQueueHandlerProtocol { guard let paymentQueueHandler = self.paymentQueueHandler else { fatalError( "paymentQueueHandler can't be nil. Please ensure you're using init(registrar: FlutterPluginRegistrar)" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h new file mode 100644 index 0000000000000..1285643520863 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.h @@ -0,0 +1,33 @@ +// 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. + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN +/// A protocol that wraps FlutterMethodChannel. +@protocol FLTMethodChannelProtocol + +/// Invokes the specified Flutter method with the specified arguments, expecting +/// an asynchronous result. +- (void)invokeMethod:(NSString *)method arguments:(id _Nullable)arguments; + +/// Invokes the specified Flutter method with the specified arguments and specified callback +- (void)invokeMethod:(NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback; + +@end + +/// The default method channel that wraps FlutterMethodChannel +@interface DefaultMethodChannel : NSObject + +/// Initialize this wrapper with a FlutterMethodChannel +- (instancetype)initWithChannel:(FlutterMethodChannel *)channel; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m new file mode 100644 index 0000000000000..17e0e0803fca9 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTMethodChannelProtocol.m @@ -0,0 +1,32 @@ +// 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 "FLTMethodChannelProtocol.h" + +@interface DefaultMethodChannel () +/// The wrapped FlutterMethodChannel +@property(nonatomic, strong) FlutterMethodChannel *channel; +@end + +@implementation DefaultMethodChannel + +- (instancetype)initWithChannel:(nonnull FlutterMethodChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments { + [self.channel invokeMethod:method arguments:arguments]; +} + +- (void)invokeMethod:(nonnull NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback { + [self.channel invokeMethod:method arguments:arguments result:callback]; +} + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h new file mode 100644 index 0000000000000..f11b1a09ce49a --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueHandlerProtocol.h @@ -0,0 +1,107 @@ +// 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 +#import "FIATransactionCache.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTTransactionCacheProtocol.h" + +NS_ASSUME_NONNULL_BEGIN +typedef void (^TransactionsUpdated)(NSArray *transactions); +typedef void (^TransactionsRemoved)(NSArray *transactions); +typedef void (^RestoreTransactionFailed)(NSError *error); +typedef void (^RestoreCompletedTransactionsFinished)(void); +typedef BOOL (^ShouldAddStorePayment)(SKPayment *payment, SKProduct *product); +typedef void (^UpdatedDownloads)(NSArray *downloads); + +/// A protocol that conforms to SKPaymentTransactionObserver and handles SKPaymentQueue methods +@protocol FLTPaymentQueueHandlerProtocol +/// An object that provides information needed to complete transactions. +@property(nonatomic, weak, nullable) id delegate API_AVAILABLE( + ios(13.0), macos(10.15), watchos(6.2)); +/// An object containing the location and unique identifier of an Apple App Store storefront. +@property(nonatomic, readonly, nullable) + SKStorefront *storefront API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2)); + +/// Creates a new FIAPaymentQueueHandler. +/// +/// The "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" +/// callbacks are only called while actively observing transactions. To start +/// observing transactions send the "startObservingPaymentQueue" message. +/// Sending the "stopObservingPaymentQueue" message will stop actively +/// observing transactions. When transactions are not observed they are cached +/// to the "transactionCache" and will be delivered via the +/// "transactionsUpdated", "transactionsRemoved" and "updatedDownloads" +/// callbacks as soon as the "startObservingPaymentQueue" message arrives. +/// +/// Note: cached transactions that are not processed when the application is +/// killed will be delivered again by the App Store as soon as the application +/// starts again. +/// +/// @param queue The SKPaymentQueue instance connected to the App Store and +/// responsible for processing transactions. +/// @param transactionsUpdated Callback method that is called each time the App +/// Store indicates transactions are updated. +/// @param transactionsRemoved Callback method that is called each time the App +/// Store indicates transactions are removed. +/// @param restoreTransactionFailed Callback method that is called each time +/// the App Store indicates transactions failed +/// to restore. +/// @param restoreCompletedTransactionsFinished Callback method that is called +/// each time the App Store +/// indicates restoring of +/// transactions has finished. +/// @param shouldAddStorePayment Callback method that is called each time an +/// in-app purchase has been initiated from the +/// App Store. +/// @param updatedDownloads Callback method that is called each time the App +/// Store indicates downloads are updated. +/// @param transactionCache An empty [FIATransactionCache] instance that is +/// responsible for keeping track of transactions that +/// arrive when not actively observing transactions. +- (instancetype)initWithQueue:(id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads + transactionCache:(nonnull id)transactionCache; + +/// Can throw exceptions if the transaction type is purchasing, should always used in a @try block. +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; + +/// Attempt to restore transactions. Require app store receipt url. +- (void)restoreTransactions:(nullable NSString *)applicationName; + +/// Displays a sheet that enables users to redeem subscription offer codes. +- (void)presentCodeRedemptionSheet API_UNAVAILABLE(tvos, macos, watchos); + +/// Return all transactions that are not marked as complete. +- (NSArray *)getUnfinishedTransactions; + +/// This method needs to be called before any other methods. +- (void)startObservingPaymentQueue; + +/// Call this method when the Flutter app is no longer listening +- (void)stopObservingPaymentQueue; + +/// Appends a payment to the SKPaymentQueue. +/// +/// @param payment Payment object to be added to the payment queue. +/// @return whether "addPayment" was successful. +- (BOOL)addPayment:(SKPayment *)payment; + +/// Displays the price consent sheet. +/// +/// The price consent sheet is only displayed when the following +/// is true: +/// - You have increased the price of the subscription in App Store Connect. +/// - The subscriber has not yet responded to a price consent query. +/// Otherwise the method has no effect. +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4))API_UNAVAILABLE(tvos, macos, watchos); + +@end +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h new file mode 100644 index 0000000000000..4047647120020 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.h @@ -0,0 +1,69 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol that wraps SKPaymentQueue +@protocol FLTPaymentQueueProtocol + +/// An object containing the location and unique identifier of an Apple App Store storefront. +@property(nonatomic, strong) SKStorefront *storefront API_AVAILABLE(ios(13.0)); + +/// A list of SKPaymentTransactions, which each represents a single transaction +@property(nonatomic, strong) NSArray *transactions API_AVAILABLE( + ios(3.0), macos(10.7), watchos(6.2), visionos(1.0)); + +/// An object that provides information needed to complete transactions. +@property(nonatomic, weak, nullable) id delegate API_AVAILABLE( + ios(13.0), macos(10.15), watchos(6.2), visionos(1.0)); + +/// Remove a finished (i.e. failed or completed) transaction from the queue. Attempting to finish a +/// purchasing transaction will throw an exception. +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction; + +/// Observers are not retained. The transactions array will only be synchronized with the server +/// while the queue has observers. This may require that the user authenticate. +- (void)addTransactionObserver:(id)observer; + +/// Add a payment to the server queue. The payment is copied to add an SKPaymentTransaction to the +/// transactions array. The same payment can be added multiple times to create multiple +/// transactions. +- (void)addPayment:(SKPayment *_Nonnull)payment; + +/// Will add completed transactions for the current user back to the queue to be re-completed. +- (void)restoreCompletedTransactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2), + visionos(1.0)); + +/// Will add completed transactions for the current user back to the queue to be re-completed. This +/// version requires an identifier to the user's account. +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username + API_AVAILABLE(ios(7.0), macos(10.9), watchos(6.2), visionos(1.0)); + +/// Call this method to have StoreKit present a sheet enabling the user to redeem codes provided by +/// your app. Only for iOS. +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos); + +/// If StoreKit has called your SKPaymentQueueDelegate's "paymentQueueShouldShowPriceConsent:" +/// method and you returned NO, you can use this method to show the price consent UI at a later time +/// that is more appropriate for your app. If there is no pending price consent, this method will do +/// nothing. +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos); + +@end + +/// The default PaymentQueue that wraps SKPaymentQueue +@interface DefaultPaymentQueue : NSObject + +/// Initialize this wrapper with an SKPaymentQueue +- (instancetype)initWithQueue:(SKPaymentQueue *)queue NS_DESIGNATED_INITIALIZER; + +/// The default initializer is unavailable, as it this must be initlai +- (instancetype)init NS_UNAVAILABLE; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m new file mode 100644 index 0000000000000..fd97794bae858 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTPaymentQueueProtocol.m @@ -0,0 +1,74 @@ +// 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 "FLTPaymentQueueProtocol.h" + +@interface DefaultPaymentQueue () +/// The wrapped SKPaymentQueue +@property(nonatomic, strong) SKPaymentQueue *queue; +@end + +@implementation DefaultPaymentQueue + +@synthesize storefront; +@synthesize delegate; +@synthesize transactions; + +- (instancetype)initWithQueue:(SKPaymentQueue *)queue { + self = [super init]; + if (self) { + _queue = queue; + } + return self; +} + +- (void)addPayment:(SKPayment *_Nonnull)payment { + [self.queue addPayment:payment]; +} + +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction { + [self.queue finishTransaction:transaction]; +} + +- (void)addTransactionObserver:(nonnull id)observer { + [self.queue addTransactionObserver:observer]; +} + +- (void)restoreCompletedTransactions { + [self.queue restoreCompletedTransactions]; +} + +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username { + [self.queue restoreCompletedTransactionsWithApplicationUsername:username]; +} + +- (id)delegate API_AVAILABLE(ios(13.0), macos(10.15), watchos(6.2), + visionos(1.0)) { + return self.queue.delegate; +} + +- (NSArray *)transactions API_AVAILABLE(ios(3.0), macos(10.7), watchos(6.2), + visionos(1.0)) { + return self.queue.transactions; +} + +- (SKStorefront *)storefront API_AVAILABLE(ios(13.0)) { + return self.queue.storefront; +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet API_AVAILABLE(ios(14.0), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos) { + [self.queue presentCodeRedemptionSheet]; +} +#endif + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4), visionos(1.0)) + API_UNAVAILABLE(tvos, macos, watchos) { + [self.queue showPriceConsentIfNeeded]; +} +#endif + +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h new file mode 100644 index 0000000000000..d2359d4283eb6 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.h @@ -0,0 +1,17 @@ +// 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 + +NS_ASSUME_NONNULL_BEGIN +typedef void (^ProductRequestCompletion)(SKProductsResponse *_Nullable response, + NSError *_Nullable errror); +/// A protocol that wraps SKRequest. +@protocol FLTRequestHandlerProtocol + +/// Wrapper for SKRequest's start +/// https://developer.apple.com/documentation/storekit/skrequest/1385534-start +- (void)startProductRequestWithCompletionHandler:(ProductRequestCompletion)completion; +@end +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m new file mode 100644 index 0000000000000..e171467348978 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTRequestHandlerProtocol.m @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTRequestHandlerProtocol.h" +#import +#import "FIAPRequestHandler.h" + +@interface DefaultRequestHandler () +/// The wrapped FIAPRequestHandler +@property(nonatomic, strong) FIAPRequestHandler *handler; +@end + +@implementation DefaultRequestHandler + +- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion { + [self.handler startProductRequestWithCompletionHandler:completion]; +} + +- (nonnull instancetype)initWithRequestHandler:(nonnull FIAPRequestHandler *)handler { + self = [super init]; + if (self) { + _handler = handler; + } + return self; +} +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h new file mode 100644 index 0000000000000..f7a58b3f2e8af --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.h @@ -0,0 +1,39 @@ +// 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 "FIATransactionCache.h" +#if TARGET_OS_OSX +#import +#else +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +/// A protocol that defines a cache of all transactions, both completed and in progress. +@protocol FLTTransactionCacheProtocol + +/// Adds objects to the transaction cache. +/// +/// If the cache already contains an array of objects on the specified key, the supplied +/// array will be appended to the existing array. +- (void)addObjects:(NSArray *)objects forKey:(TransactionCacheKey)key; + +/// Gets the array of objects stored at the given key. +/// +/// If there are no objects associated with the given key nil is returned. +- (NSArray *)getObjectsForKey:(TransactionCacheKey)key; + +/// Removes all objects from the transaction cache. +- (void)clear; +@end + +/// The default method channel that wraps FIATransactionCache +@interface DefaultTransactionCache : NSObject + +/// Initialize this wrapper with an FIATransactionCache +- (instancetype)initWithCache:(FIATransactionCache *)cache; +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m new file mode 100644 index 0000000000000..3ed268e337cbf --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/Protocols/FLTTransactionCacheProtocol.m @@ -0,0 +1,33 @@ +// 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 "FLTTransactionCacheProtocol.h" + +@interface DefaultTransactionCache () +/// The wrapped FIATransactionCache +@property(nonatomic, strong) FIATransactionCache *cache; +@end + +@implementation DefaultTransactionCache + +- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key { + [self.cache addObjects:objects forKey:key]; +} + +- (void)clear { + [self.cache clear]; +} + +- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key { + return [self.cache getObjectsForKey:key]; +} + +- (nonnull instancetype)initWithCache:(nonnull FIATransactionCache *)cache { + self = [super init]; + if (self) { + _cache = cache; + } + return self; +} +@end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h index f57780d9516f7..e44e5cf4384f7 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/in_app_purchase_storekit-Bridging-Header.h @@ -8,4 +8,9 @@ #import "FIAPRequestHandler.h" #import "FIAPaymentQueueHandler.h" #import "FIATransactionCache.h" +#import "FLTMethodChannelProtocol.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTRequestHandlerProtocol.h" +#import "FLTTransactionCacheProtocol.h" #import "messages.g.h" diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile index 035842459e693..bb776acdd2c8c 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/ios/Podfile @@ -33,8 +33,6 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths - # Matches in_app_purchase test_spec dependency. - pod 'OCMock', '~> 3.6' end end 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 5b7ba61497975..7d99ab6109b11 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 @@ -7,24 +7,24 @@ objects = { /* Begin PBXBuildFile section */ - 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1630769A874F9381BC761FE1 /* libPods-Runner.a */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688DE35021F2A5A100EA2684 /* TranslatorTests.m */; }; - 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */; }; - 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34B21EEB4B800D37AEF /* Stubs.m */; }; - 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; }; - A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; }; + C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */; }; + E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */; }; F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */; }; - F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; }; - F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */; }; - F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; }; + F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD392C1256DD0067C78A /* Stubs.m */; }; + F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */; }; + F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */; }; + F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */; }; + F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */; }; + F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */; }; + F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F295AD402C1256F50067C78A /* TranslatorTests.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -51,20 +51,16 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1630769A874F9381BC761FE1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 688DE35021F2A5A100EA2684 /* TranslatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TranslatorTests.m; sourceTree = ""; }; - 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductRequestHandlerTests.m; sourceTree = ""; }; - 6896B34A21EEB4B800D37AEF /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stubs.h; sourceTree = ""; }; - 6896B34B21EEB4B800D37AEF /* Stubs.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Stubs.m; sourceTree = ""; }; + 6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -73,18 +69,21 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; A59001A421E69658004A3E5E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = ""; }; - A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RunnerTests-Bridging-Header.h"; sourceTree = ""; }; F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStubs.swift; sourceTree = ""; }; - F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; - F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIATransactionCacheTests.m; 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 = ""; }; + F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIAPPaymentQueueDeleteTests.m; path = ../../shared/RunnerTests/FIAPPaymentQueueDeleteTests.m; sourceTree = ""; }; + F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = InAppPurchasePluginTests.m; path = ../../shared/RunnerTests/InAppPurchasePluginTests.m; sourceTree = ""; }; + F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProductRequestHandlerTests.m; path = ../../shared/RunnerTests/ProductRequestHandlerTests.m; sourceTree = ""; }; + F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FIATransactionCacheTests.m; path = ../../shared/RunnerTests/FIATransactionCacheTests.m; sourceTree = ""; }; + F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = PaymentQueueTests.m; path = ../../shared/RunnerTests/PaymentQueueTests.m; sourceTree = ""; }; + F295AD402C1256F50067C78A /* TranslatorTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TranslatorTests.m; path = ../../shared/RunnerTests/TranslatorTests.m; sourceTree = ""; }; F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = ""; }; - F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -93,7 +92,7 @@ buildActionMask = 2147483647; files = ( A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */, - 0FFCF66105590202CD84C7AA /* libPods-Runner.a in Frameworks */, + E680BD031412EB2D02C9190B /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -101,7 +100,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */, + C4667AA10A6BC70CE9A5007C /* libPods-RunnerTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -111,10 +110,10 @@ 0B4403AC68C3196AECF5EF89 /* Pods */ = { isa = PBXGroup; children = ( - E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */, - 2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */, - 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */, - 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */, + 6458340B2CE3497379F6B389 /* Pods-Runner.debug.xcconfig */, + CC9E5595B2B9B9B90632DA75 /* Pods-Runner.release.xcconfig */, + 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */, + 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -187,15 +186,14 @@ A59001A521E69658004A3E5E /* RunnerTests */ = { isa = PBXGroup; children = ( - A59001A821E69658004A3E5E /* Info.plist */, - 6896B34A21EEB4B800D37AEF /* Stubs.h */, - 6896B34B21EEB4B800D37AEF /* Stubs.m */, - A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */, - 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */, - F78AF3132342BC89008449C7 /* PaymentQueueTests.m */, - 688DE35021F2A5A100EA2684 /* TranslatorTests.m */, - F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */, - F6995BDC27CF73000050EA78 /* FIATransactionCacheTests.m */, + F295AD3B2C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m */, + F295AD3E2C1256F50067C78A /* FIATransactionCacheTests.m */, + F295AD3C2C1256F50067C78A /* InAppPurchasePluginTests.m */, + F295AD3F2C1256F50067C78A /* PaymentQueueTests.m */, + F295AD3D2C1256F50067C78A /* ProductRequestHandlerTests.m */, + F295AD402C1256F50067C78A /* TranslatorTests.m */, + F295AD392C1256DD0067C78A /* Stubs.m */, + F295AD362C1251300067C78A /* Stubs.h */, F22BF91B2BC9B40B00713878 /* SwiftStubs.swift */, F22BF91A2BC9B40B00713878 /* RunnerTests-Bridging-Header.h */, ); @@ -206,8 +204,8 @@ isa = PBXGroup; children = ( A5279297219369C600FF69E6 /* StoreKit.framework */, - 1630769A874F9381BC761FE1 /* libPods-Runner.a */, - 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */, + 21CE6E615CF661FC0E18FB0A /* libPods-Runner.a */, + AB9CD9DD098BDAB3D5053EE5 /* libPods-RunnerTests.a */, ); name = Frameworks; sourceTree = ""; @@ -219,14 +217,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */, + 9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */, + 325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -241,7 +239,7 @@ isa = PBXNativeTarget; buildConfigurationList = A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */, + 39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */, A59001A021E69658004A3E5E /* Sources */, A59001A121E69658004A3E5E /* Frameworks */, A59001A221E69658004A3E5E /* Resources */, @@ -323,23 +321,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; - }; - 67CBAA37FA50343E43E988F6 /* [CP] Copy Pods Resources */ = { + 325E900B3895C722B0E09318 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -347,17 +329,19 @@ inputPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh", "${PODS_CONFIGURATION_BUILD_DIR}/in_app_purchase_storekit/in_app_purchase_storekit_privacy.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/shared_preferences_foundation/shared_preferences_foundation_privacy.bundle", ); name = "[CP] Copy Pods Resources"; outputPaths = ( "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/in_app_purchase_storekit_privacy.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/shared_preferences_foundation_privacy.bundle", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; - 95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = { + 39A4BCA317070A14A6C5C70F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -379,6 +363,22 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -394,7 +394,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - EDD921296E29F853F7B69716 /* [CP] Check Pods Manifest.lock */ = { + 9AF65E7BDC9361CB3944EE9C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -433,14 +433,14 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + F295AD432C1256F50067C78A /* ProductRequestHandlerTests.m in Sources */, F22BF91C2BC9B40B00713878 /* SwiftStubs.swift in Sources */, - F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */, - F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */, - 6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */, - 688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */, - F6995BDD27CF73000050EA78 /* FIATransactionCacheTests.m in Sources */, - A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */, - 6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */, + F295AD412C1256F50067C78A /* FIAPPaymentQueueDeleteTests.m in Sources */, + F295AD452C1256F50067C78A /* PaymentQueueTests.m in Sources */, + F295AD442C1256F50067C78A /* FIATransactionCacheTests.m in Sources */, + F295AD462C1256F50067C78A /* TranslatorTests.m in Sources */, + F295AD422C1256F50067C78A /* InAppPurchasePluginTests.m in Sources */, + F295AD3A2C1256DD0067C78A /* Stubs.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -636,7 +636,7 @@ }; A59001AB21E69658004A3E5E /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 7AB7758EFACBE3E1E7BDE0C6 /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -667,7 +667,7 @@ }; A59001AC21E69658004A3E5E /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8B97B58DB1E9CF900A4617A3 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 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 29ebbebf553d3..58f3d4304fc6f 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 @@ -1,4 +1,14 @@ { + "appPolicies" : { + "eula" : "", + "policies" : [ + { + "locale" : "en_US", + "policyText" : "", + "policyURL" : "" + } + ] + }, "identifier" : "6073E9A3", "nonRenewingSubscriptions" : [ @@ -118,7 +128,10 @@ "recurringSubscriptionPeriod" : "P1W", "referenceName" : "subscription_silver", "subscriptionGroupID" : "D0FEE8D8", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] }, { "adHocOffers" : [ @@ -143,13 +156,16 @@ "recurringSubscriptionPeriod" : "P1M", "referenceName" : "subscription_gold", "subscriptionGroupID" : "D0FEE8D8", - "type" : "RecurringSubscription" + "type" : "RecurringSubscription", + "winbackOffers" : [ + + ] } ] } ], "version" : { - "major" : 3, + "major" : 4, "minor" : 0 } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile index 04238b6a5f2cd..0fcebfff85803 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Podfile @@ -34,8 +34,6 @@ target 'Runner' do target 'RunnerTests' do inherit! :search_paths - - pod 'OCMock', '~> 3.6' end end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj index e4eb1fae60058..86bb21175b19b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/project.pbxproj @@ -265,7 +265,6 @@ F700DCFE28E652A10004836B /* Sources */, F700DCFF28E652A10004836B /* Frameworks */, F700DD0028E652A10004836B /* Resources */, - E318947BE753B7BBEFC3782A /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -425,23 +424,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - E318947BE753B7BBEFC3782A /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-RunnerTests/Pods-RunnerTests-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; F83C62E1BF4D0A86747FA7CF /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 53f3a37403a84..5eb222feadb66 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -38,8 +38,7 @@ + skipped = "NO"> + + + + diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m index 187cc6e37bf6b..1056c343b173e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/FIAPPaymentQueueDeleteTests.m @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "FIAObjectTranslator.h" #import "FIAPaymentQueueHandler.h" @@ -14,17 +13,14 @@ API_UNAVAILABLE(tvos, macos, watchos) @interface FIAPPaymentQueueDelegateTests : XCTestCase -@property(strong, nonatomic) FlutterMethodChannel *channel; -@property(strong, nonatomic) SKPaymentTransaction *transaction; -@property(strong, nonatomic) SKStorefront *storefront; +@property(nonatomic, strong) SKPaymentTransaction *transaction; +@property(nonatomic, strong) SKStorefront *storefront; @end @implementation FIAPPaymentQueueDelegateTests - (void)setUp { - self.channel = OCMClassMock(FlutterMethodChannel.class); - NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @@ -45,21 +41,24 @@ - (void)setUp { } - (void)tearDown { - self.channel = nil; } - (void)testShouldContinueTransaction { if (@available(iOS 13.0, *)) { - FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldContinueTransaction"); + XCTAssertEqualObjects(arguments, + [FIAObjectTranslator getMapFromSKStorefront:self.storefront + andSKPaymentTransaction:self.transaction]); + result(@NO); + }; - OCMStub([self.channel - invokeMethod:@"shouldContinueTransaction" - arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront - andSKPaymentTransaction:self.transaction] - result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); + FIAPPaymentQueueDelegate *delegate = + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) + BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init] shouldContinueTransaction:self.transaction inStorefront:self.storefront]; @@ -69,15 +68,19 @@ - (void)testShouldContinueTransaction { - (void)testShouldContinueTransaction_should_default_to_yes { if (@available(iOS 13.0, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel invokeMethod:@"shouldContinueTransaction" - arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront - andSKPaymentTransaction:self.transaction] - result:[OCMArg any]]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldContinueTransaction"); + XCTAssertEqualObjects(arguments, + [FIAObjectTranslator getMapFromSKStorefront:self.storefront + andSKPaymentTransaction:self.transaction]); + }; - BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class) + BOOL shouldContinue = [delegate paymentQueue:[[SKPaymentQueueStub alloc] init] shouldContinueTransaction:self.transaction inStorefront:self.storefront]; @@ -88,16 +91,19 @@ - (void)testShouldContinueTransaction_should_default_to_yes { #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded { if (@available(iOS 13.4, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel - invokeMethod:@"shouldShowPriceConsent" - arguments:nil - result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldShowPriceConsent"); + XCTAssertNil(arguments); + result(@NO); + }; BOOL shouldShow = - [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; + [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]]; XCTAssertFalse(shouldShow); } @@ -107,15 +113,18 @@ - (void)testShouldShowPriceConsentIfNeeded { #if TARGET_OS_IOS - (void)testShouldShowPriceConsentIfNeeded_should_default_to_yes { if (@available(iOS 13.4, *)) { + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; FIAPPaymentQueueDelegate *delegate = - [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel]; + [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:channelStub]; - OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent" - arguments:nil - result:[OCMArg any]]); + channelStub.invokeMethodChannelWithResultsStub = + ^(NSString *_Nonnull method, id _Nonnull arguments, FlutterResult _Nullable result) { + XCTAssertEqualObjects(method, @"shouldShowPriceConsent"); + XCTAssertNil(arguments); + }; BOOL shouldShow = - [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)]; + [delegate paymentQueueShouldShowPriceConsent:[[SKPaymentQueueStub alloc] init]]; XCTAssertTrue(shouldShow); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m index 8839bd2940230..1820ff8f65a49 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/InAppPurchasePluginTests.m @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "FIAPaymentQueueHandler.h" #import "RunnerTests-Swift.h" @@ -12,8 +11,8 @@ @interface InAppPurchasePluginTest : XCTestCase -@property(strong, nonatomic) FIAPReceiptManagerStub *receiptManagerStub; -@property(strong, nonatomic) InAppPurchasePlugin *plugin; +@property(nonatomic, strong) FIAPReceiptManagerStub *receiptManagerStub; +@property(nonatomic, strong) InAppPurchasePlugin *plugin; @end @@ -23,8 +22,9 @@ - (void)setUp { self.receiptManagerStub = [FIAPReceiptManagerStub new]; self.plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; } @@ -40,23 +40,23 @@ - (void)testCanMakePayments { - (void)testPaymentQueueStorefront { if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); NSDictionary *storefrontMap = @{ @"countryCode" : @"USA", @"identifier" : @"unique_identifier", }; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; - OCMStub(mockQueue.storefront).andReturn([[SKStorefrontStub alloc] initWithMap:storefrontMap]); + queueStub.storefront = [[SKStorefrontStub alloc] initWithMap:storefrontMap]; - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; SKStorefrontMessage *result = [self.plugin storefrontWithError:&error]; @@ -71,19 +71,17 @@ - (void)testPaymentQueueStorefront { - (void)testPaymentQueueStorefrontReturnsNil { if (@available(iOS 13, macOS 10.15, *)) { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); - - OCMStub(mockQueue.storefront).andReturn(nil); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; + + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; SKStorefrontMessage *resultMap = [self.plugin storefrontWithError:&error]; @@ -129,14 +127,23 @@ - (void)testFinishTransactionSucceeds { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - SKPaymentTransactionStub *paymentTransaction = + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = @[ paymentTransaction ]; + NSArray *array = @[ paymentTransactionStub ]; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(array); + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; + queue.transactions = array; - self.plugin.paymentQueueHandler = mockHandler; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; + + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; FlutterError *error; [self.plugin finishTransactionFinishMap:args error:&error]; @@ -167,13 +174,23 @@ - (void)testFinishTransactionSucceedsWithNilTransaction { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), }; - SKPaymentTransactionStub *paymentTransaction = + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler getUnfinishedTransactions]).andReturn(@[ paymentTransaction ]); + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + queueStub.transactions = @[ paymentTransactionStub ]; - self.plugin.paymentQueueHandler = mockHandler; + TransactionCacheStub *cache = [[TransactionCacheStub alloc] init]; + + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cache]; + ; FlutterError *error; [self.plugin finishTransactionFinishMap:args error:&error]; @@ -186,20 +203,21 @@ - (void)testGetProductResponseWithRequestError { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:_receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *error = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], error, - nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, error); + }; [plugin startProductRequestProductIdentifiers:argument @@ -220,28 +238,23 @@ - (void)testGetProductResponseWithNoResponse { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); - + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:_receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; - NSError *error = [NSError errorWithDomain:@"errorDomain" - code:0 - userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - [NSNull null], nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, nil); + }; [plugin startProductRequestProductIdentifiers:argument completion:^(SKProductsResponseMessage *_Nullable response, FlutterError *_Nullable startProductRequestError) { [expectation fulfill]; - XCTAssertNotNil(error); XCTAssertNotNil(startProductRequestError); XCTAssertEqualObjects(startProductRequestError.code, @"storekit_platform_no_response"); @@ -256,15 +269,20 @@ - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { @"simulatesAskToBuyInSandbox" : @YES, }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(NO); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + addPaymentInvokeCount += 1; + return NO; + }; + [self.plugin addPaymentPaymentMap:argument error:&error]; - OCMVerify(times(1), [mockHandler addPayment:[OCMArg any]]); + XCTAssertEqual(addPaymentInvokeCount, 1); XCTAssertEqualObjects(@"storekit_duplicate_product_object", error.code); XCTAssertEqualObjects(@"There is a pending transaction for the same product identifier. " @"Please either wait for it to be finished or finish it manually " @@ -301,21 +319,24 @@ - (void)testAddPaymentSuccessWithoutPaymentDiscount { @"simulatesAskToBuyInSandbox" : @YES, }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + XCTAssert(payment != nil); + XCTAssertEqual(payment.productIdentifier, @"123"); + XCTAssert(payment.quantity == 1); + addPaymentInvokeCount++; + return YES; + }; + FlutterError *error; [self.plugin addPaymentPaymentMap:argument error:&error]; XCTAssertNil(error); - OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - XCTAssert(payment != nil); - XCTAssertEqual(payment.productIdentifier, @"123"); - XCTAssert(payment.quantity == 1); - return YES; - }]]); + XCTAssertEqual(addPaymentInvokeCount, 1); } - (void)testAddPaymentSuccessWithPaymentDiscount { @@ -332,43 +353,42 @@ - (void)testAddPaymentSuccessWithPaymentDiscount { } }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + if (@available(iOS 12.2, *)) { + SKPaymentDiscount *discount = payment.paymentDiscount; + XCTAssertEqual(discount.identifier, @"test_identifier"); + XCTAssertEqual(discount.keyIdentifier, @"test_key_identifier"); + XCTAssertEqualObjects( + discount.nonce, + [[NSUUID alloc] initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]); + XCTAssertEqual(discount.signature, @"test_signature"); + addPaymentInvokeCount++; + return YES; + } + addPaymentInvokeCount++; + return YES; + }; FlutterError *error; [self.plugin addPaymentPaymentMap:argument error:&error]; + XCTAssertEqual(addPaymentInvokeCount, 1); XCTAssertNil(error); - OCMVerify( - times(1), - [mockHandler - addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - if (@available(iOS 12.2, *)) { - SKPaymentDiscount *discount = payment.paymentDiscount; - - return [discount.identifier isEqual:@"test_identifier"] && - [discount.keyIdentifier isEqual:@"test_key_identifier"] && - [discount.nonce - isEqual:[[NSUUID alloc] - initWithUUIDString:@"4a11a9cc-3bc3-11ec-8d3d-0242ac130003"]] && - [discount.signature isEqual:@"test_signature"] && - [discount.timestamp isEqual:@(1635847102)]; - } - - return YES; - }]]); } - (void)testAddPaymentFailureWithInvalidPaymentDiscount { // Support for payment discount is only available on iOS 12.2 and higher. if (@available(iOS 12.2, *)) { - NSDictionary *argument = @{ + NSDictionary *invalidDiscount = @{ @"productIdentifier" : @"123", @"quantity" : @(1), @"simulatesAskToBuyInSandbox" : @YES, @"paymentDiscount" : @{ + /// This payment discount is missing the field `identifier`, and is thus malformed @"keyIdentifier" : @"test_key_identifier", @"nonce" : @"4a11a9cc-3bc3-11ec-8d3d-0242ac130003", @"signature" : @"test_signature", @@ -376,25 +396,26 @@ - (void)testAddPaymentFailureWithInvalidPaymentDiscount { } }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - id translator = OCMClassMock(FIAObjectTranslator.class); + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + + __block NSInteger addPaymentCount = 0; + handlerStub.addPaymentStub = ^BOOL(SKPayment *_Nonnull payment) { + addPaymentCount++; + return YES; + }; - NSString *errorMsg = @"Some error occurred"; - OCMStub(ClassMethod([translator - getSKPaymentDiscountFromMap:[OCMArg any] - withError:(NSString __autoreleasing **)[OCMArg setTo:errorMsg]])) - .andReturn(nil); - self.plugin.paymentQueueHandler = mockHandler; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; - [self.plugin addPaymentPaymentMap:argument error:&error]; + [self.plugin addPaymentPaymentMap:invalidDiscount error:&error]; XCTAssertEqualObjects(@"storekit_invalid_payment_discount_object", error.code); XCTAssertEqualObjects(@"You have requested a payment and specified a " - @"payment discount with invalid properties. Some error occurred", + @"payment discount with invalid properties. When specifying a payment " + @"discount the 'identifier' field is mandatory.", error.message); - XCTAssertEqualObjects(argument, error.details); - OCMVerify(never(), [mockHandler addPayment:[OCMArg any]]); + XCTAssertEqualObjects(invalidDiscount, error.details); + XCTAssertEqual(0, addPaymentCount); } } @@ -405,27 +426,30 @@ - (void)testAddPaymentWithNullSandboxArgument { @"simulatesAskToBuyInSandbox" : [NSNull null], }; - FIAPaymentQueueHandler *mockHandler = OCMClassMock(FIAPaymentQueueHandler.class); - OCMStub([mockHandler addPayment:[OCMArg any]]).andReturn(YES); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; FlutterError *error; + __block NSInteger addPaymentInvokeCount = 0; + handlerStub.addPaymentStub = ^(SKPayment *payment) { + XCTAssertEqual(payment.simulatesAskToBuyInSandbox, false); + addPaymentInvokeCount++; + return YES; + }; + [self.plugin addPaymentPaymentMap:argument error:&error]; - OCMVerify(times(1), [mockHandler addPayment:[OCMArg checkWithBlock:^BOOL(id obj) { - SKPayment *payment = obj; - return !payment.simulatesAskToBuyInSandbox; - }]]); + XCTAssertEqual(addPaymentInvokeCount, 1); } - (void)testRestoreTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"result successfully restore transactions"]; - SKPaymentQueueStub *queue = [SKPaymentQueueStub new]; - queue.testState = SKPaymentTransactionStatePurchased; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; __block BOOL callbackInvoked = NO; - self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub transactionsUpdated:^(NSArray *_Nonnull transactions) { } transactionRemoved:nil @@ -436,8 +460,8 @@ - (void)testRestoreTransactions { } shouldAddStorePayment:nil updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - [queue addTransactionObserver:self.plugin.paymentQueueHandler]; + transactionCache:cacheStub]; + [queueStub addTransactionObserver:self.plugin.paymentQueueHandler]; FlutterError *error; [self.plugin restoreTransactionsApplicationUserName:nil error:&error]; @@ -454,8 +478,8 @@ - (void)testRetrieveReceiptDataSuccess { } - (void)testRetrieveReceiptDataNil { - NSBundle *mockBundle = OCMPartialMock([NSBundle mainBundle]); - OCMStub(mockBundle.appStoreReceiptURL).andReturn(nil); + self.receiptManagerStub.returnNilURL = YES; + FlutterError *error; NSString *result = [self.plugin retrieveReceiptDataWithError:&error]; XCTAssertNil(result); @@ -480,20 +504,21 @@ - (void)testRefreshReceiptRequest { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:_receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *recieptError = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; [plugin refreshReceiptReceiptProperties:nil completion:^(FlutterError *_Nullable error) { @@ -512,20 +537,21 @@ - (void)testRefreshReceiptRequestWithParams { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:_receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *recieptError = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; [plugin refreshReceiptReceiptProperties:properties completion:^(FlutterError *_Nullable error) { @@ -544,20 +570,21 @@ - (void)testRefreshReceiptRequestWithError { XCTestExpectation *expectation = [self expectationWithDescription:@"completion handler successfully called"]; - id mockHandler = OCMClassMock([FIAPRequestHandler class]); + RequestHandlerStub *handlerStub = [[RequestHandlerStub alloc] init]; InAppPurchasePlugin *plugin = [[InAppPurchasePlugin alloc] initWithReceiptManager:_receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return mockHandler; + handlerFactory:^RequestHandlerStub *(SKRequest *request) { + return handlerStub; }]; NSError *recieptError = [NSError errorWithDomain:@"errorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"description"}]; - OCMStub([mockHandler - startProductRequestWithCompletionHandler:([OCMArg invokeBlockWithArgs:[NSNull null], - recieptError, nil])]); + handlerStub.startProductRequestWithCompletionHandlerStub = + ^(ProductRequestCompletion _Nonnull completion) { + completion(nil, recieptError); + }; [plugin refreshReceiptReceiptProperties:properties completion:^(FlutterError *_Nullable error) { @@ -572,18 +599,24 @@ - (void)testRefreshReceiptRequestWithError { /// presentCodeRedemptionSheetWithError:error is only available on iOS #if TARGET_OS_IOS - (void)testPresentCodeRedemptionSheet { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger presentCodeRedemptionSheetCount = 0; + handlerStub.presentCodeRedemptionSheetStub = ^{ + presentCodeRedemptionSheetCount++; + }; FlutterError *error; [self.plugin presentCodeRedemptionSheetWithError:&error]; - OCMVerify(times(1), [mockHandler presentCodeRedemptionSheet]); + XCTAssertEqual(1, presentCodeRedemptionSheetCount); } #endif - (void)testGetPendingTransactions { - SKPaymentQueue *mockQueue = OCMClassMock(SKPaymentQueue.class); + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; NSDictionary *transactionMap = @{ @"transactionIdentifier" : [NSNull null], @"transactionState" : @(SKPaymentTransactionStatePurchasing), @@ -594,18 +627,15 @@ - (void)testGetPendingTransactions { @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970), @"originalTransaction" : [NSNull null], }; - OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc] - initWithMap:transactionMap] ]); - - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + queueStub.transactions = @[ [[SKPaymentTransactionStub alloc] initWithMap:transactionMap] ]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; FlutterError *error; SKPaymentTransactionStub *original = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; @@ -619,45 +649,50 @@ - (void)testGetPendingTransactions { } - (void)testStartObservingPaymentQueue { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger startObservingCount = 0; + handlerStub.startObservingPaymentQueueStub = ^{ + startObservingCount++; + }; FlutterError *error; [self.plugin startObservingPaymentQueueWithError:&error]; - OCMVerify(times(1), [mockHandler startObservingPaymentQueue]); + XCTAssertEqual(1, startObservingCount); } - (void)testStopObservingPaymentQueue { - FIAPaymentQueueHandler *mockHandler = OCMClassMock([FIAPaymentQueueHandler class]); - self.plugin.paymentQueueHandler = mockHandler; + PaymentQueueHandlerStub *handlerStub = [[PaymentQueueHandlerStub alloc] init]; + self.plugin.paymentQueueHandler = handlerStub; + + __block NSInteger stopObservingCount = 0; + handlerStub.stopObservingPaymentQueueStub = ^{ + stopObservingCount++; + }; FlutterError *error; [self.plugin stopObservingPaymentQueueWithError:&error]; - OCMVerify(times(1), [mockHandler stopObservingPaymentQueue]); + XCTAssertEqual(1, stopObservingCount); } #if TARGET_OS_IOS - (void)testRegisterPaymentQueueDelegate { + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; if (@available(iOS 13, *)) { - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - - self.plugin.registrar = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; - id registrarMock = OCMProtocolMock(@protocol(FlutterPluginRegistrar)); - self.plugin.registrar = registrarMock; - - id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); - OCMStub([registrarMock messenger]).andReturn(binaryMessengerMock); + self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init]; // Verify the delegate is nil before we register one. XCTAssertNil(self.plugin.paymentQueueHandler.delegate); @@ -672,21 +707,28 @@ - (void)testRegisterPaymentQueueDelegate { - (void)testRemovePaymentQueueDelegate { if (@available(iOS 13, *)) { - self.plugin.paymentQueueHandler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new] - transactionsUpdated:nil - transactionRemoved:nil - restoreTransactionFailed:nil - restoreCompletedTransactionsFinished:nil - shouldAddStorePayment:nil - updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; - self.plugin.paymentQueueHandler.delegate = OCMProtocolMock(@protocol(SKPaymentQueueDelegate)); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; + + self.plugin.registrar = [[FlutterPluginRegistrarStub alloc] init]; + + // Verify the delegate is nil before we register one. + XCTAssertNil(self.plugin.paymentQueueHandler.delegate); + + FlutterError *error; + [self.plugin registerPaymentQueueDelegateWithError:&error]; // Verify the delegate is not nil before removing it. XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate); - FlutterError *error; [self.plugin removePaymentQueueDelegateWithError:&error]; // Verify the delegate is nill after removing it. @@ -708,21 +750,30 @@ - (void)testHandleTransactionsUpdated { InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; - SKPaymentTransactionStub *paymentTransaction = + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"updatedTransactions", method); + XCTAssertNotNil(arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; + + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil]; NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]]; [plugin handleTransactionsUpdated:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"updatedTransactions" arguments:[OCMArg any]]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testHandleTransactionsRemoved { @@ -738,52 +789,76 @@ - (void)testHandleTransactionsRemoved { InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - - SKPaymentTransactionStub *paymentTransaction = + SKPaymentTransactionStub *paymentTransactionStub = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap]; - NSArray *array = [NSArray arrayWithObjects:paymentTransaction, nil]; + NSArray *array = [NSArray arrayWithObjects:paymentTransactionStub, nil]; NSMutableArray *maps = [NSMutableArray new]; - [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransaction]]; + [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:paymentTransactionStub]]; + + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; + + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"removedTransactions", method); + XCTAssertEqualObjects(maps, arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; [plugin handleTransactionsRemoved:array]; - OCMVerify(times(1), [mockChannel invokeMethod:@"removedTransactions" arguments:maps]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testHandleTransactionRestoreFailed { InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); - + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; NSError *error = [NSError errorWithDomain:@"error" code:0 userInfo:nil]; + + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"restoreCompletedTransactionsFailed", method); + XCTAssertEqualObjects([FIAObjectTranslator getMapFromNSError:error], arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; + [plugin handleTransactionRestoreFailed:error]; - OCMVerify(times(1), [mockChannel invokeMethod:@"restoreCompletedTransactionsFailed" - arguments:[FIAObjectTranslator getMapFromNSError:error]]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testRestoreCompletedTransactionsFinished { InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + __block NSInteger invokeMethodCount = 0; + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"paymentQueueRestoreCompletedTransactionsFinished", method); + XCTAssertNil(arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; [plugin restoreCompletedTransactionsFinished]; - OCMVerify(times(1), [mockChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished" - arguments:nil]); + XCTAssertEqual(invokeMethodCount, 1); } - (void)testShouldAddStorePayment { @@ -804,41 +879,65 @@ - (void)testShouldAddStorePayment { }; SKMutablePayment *payment = [FIAObjectTranslator getSKMutablePaymentFromMap:paymentMap]; - SKProductStub *product = [[SKProductStub alloc] initWithMap:productMap]; + SKProductStub *productStub = [[SKProductStub alloc] initWithMap:productMap]; InAppPurchasePlugin *plugin = [[InAppPurchasePluginStub alloc] initWithReceiptManager:self.receiptManagerStub - handlerFactory:^FIAPRequestHandler *(SKRequest *request) { - return [[FIAPRequestHandler alloc] initWithRequest:request]; + handlerFactory:^DefaultRequestHandler *(SKRequest *request) { + return [[DefaultRequestHandler alloc] + initWithRequestHandler:[[FIAPRequestHandler alloc] initWithRequest:request]]; }]; - FlutterMethodChannel *mockChannel = OCMClassMock([FlutterMethodChannel class]); - plugin.transactionObserverCallbackChannel = mockChannel; - OCMStub([mockChannel invokeMethod:[OCMArg any] arguments:[OCMArg any]]); NSDictionary *args = @{ @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment], - @"product" : [FIAObjectTranslator getMapFromSKProduct:product] + @"product" : [FIAObjectTranslator getMapFromSKProduct:productStub] }; - BOOL result = [plugin shouldAddStorePaymentWithPayment:payment product:product]; + MethodChannelStub *channelStub = [[MethodChannelStub alloc] init]; + + __block NSInteger invokeMethodCount = 0; + channelStub.invokeMethodChannelStub = ^(NSString *_Nonnull method, id _Nonnull arguments) { + XCTAssertEqualObjects(@"shouldAddStorePayment", method); + XCTAssertEqualObjects(args, arguments); + invokeMethodCount++; + }; + + // (TODO: louisehsu) Change this to inject the channel, like requestHandler + plugin.transactionObserverCallbackChannel = channelStub; + + BOOL result = [plugin shouldAddStorePaymentWithPayment:payment product:productStub]; XCTAssertEqual(result, NO); - OCMVerify(times(1), [mockChannel invokeMethod:@"shouldAddStorePayment" arguments:args]); + XCTAssertEqual(invokeMethodCount, 1); } #if TARGET_OS_IOS - (void)testShowPriceConsentIfNeeded { - FIAPaymentQueueHandler *mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class); - self.plugin.paymentQueueHandler = mockQueueHandler; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; + PaymentQueueStub *queueStub = [[PaymentQueueStub alloc] init]; + self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queueStub + transactionsUpdated:nil + transactionRemoved:nil + restoreTransactionFailed:nil + restoreCompletedTransactionsFinished:nil + shouldAddStorePayment:nil + updatedDownloads:nil + transactionCache:cacheStub]; FlutterError *error; + __block NSInteger showPriceConsentIfNeededCount = 0; + + queueStub.showPriceConsentIfNeededStub = ^(void) { + showPriceConsentIfNeededCount++; + }; + [self.plugin showPriceConsentIfNeededWithError:&error]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpartial-availability" if (@available(iOS 13.4, *)) { - OCMVerify(times(1), [mockQueueHandler showPriceConsentIfNeeded]); + XCTAssertEqual(showPriceConsentIfNeededCount, 1); } else { - OCMVerify(never(), [mockQueueHandler showPriceConsentIfNeeded]); + XCTAssertEqual(showPriceConsentIfNeededCount, 0); } #pragma clang diagnostic pop } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m index 2f8d5857c8d85..0c6d51a3b75db 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/PaymentQueueTests.m @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import #import #import "Stubs.h" @@ -10,10 +9,10 @@ @interface PaymentQueueTest : XCTestCase -@property(strong, nonatomic) NSDictionary *periodMap; -@property(strong, nonatomic) NSDictionary *discountMap; -@property(strong, nonatomic) NSDictionary *productMap; -@property(strong, nonatomic) NSDictionary *productResponseMap; +@property(nonatomic, strong) NSDictionary *periodMap; +@property(nonatomic, strong) NSDictionary *discountMap; +@property(nonatomic, strong) NSDictionary *productMap; +@property(nonatomic, strong) NSDictionary *productResponseMap; @end @@ -45,7 +44,7 @@ - (void)setUp { - (void)testTransactionPurchased { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchased transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -61,20 +60,20 @@ - (void)testTransactionPurchased { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStatePurchased); - XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); + XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionFailed { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get failed transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateFailed; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -90,7 +89,7 @@ - (void)testTransactionFailed { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -104,7 +103,7 @@ - (void)testTransactionFailed { - (void)testTransactionRestored { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get restored transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateRestored; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -120,7 +119,7 @@ - (void)testTransactionRestored { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -128,13 +127,13 @@ - (void)testTransactionRestored { [handler addPayment:payment]; [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertEqual(tran.transactionState, SKPaymentTransactionStateRestored); - XCTAssertEqual(tran.transactionIdentifier, @"fakeID"); + XCTAssertEqualObjects(tran.transactionIdentifier, @"fakeID"); } - (void)testTransactionPurchasing { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get purchasing transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchasing; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -150,7 +149,7 @@ - (void)testTransactionPurchasing { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; @@ -164,7 +163,7 @@ - (void)testTransactionPurchasing { - (void)testTransactionDeferred { XCTestExpectation *expectation = [self expectationWithDescription:@"expect to get deffered transcation."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block SKPaymentTransactionStub *tran; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue @@ -180,7 +179,7 @@ - (void)testTransactionDeferred { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; @@ -193,7 +192,7 @@ - (void)testTransactionDeferred { - (void)testFinishTransaction { XCTestExpectation *expectation = [self expectationWithDescription:@"handler.transactions should be empty."]; - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStateDeferred; __block FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { @@ -211,7 +210,7 @@ - (void)testFinishTransaction { return YES; } updatedDownloads:nil - transactionCache:OCMClassMock(FIATransactionCache.class)]; + transactionCache:[[TransactionCacheStub alloc] init]]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; [handler startObservingPaymentQueue]; @@ -220,9 +219,9 @@ - (void)testFinishTransaction { } - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmpty { - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } @@ -237,20 +236,41 @@ - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmp updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + return nil; + }; [handler startObservingPaymentQueue]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void) testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheContainsEmptyTransactionArrays { - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); } @@ -265,17 +285,36 @@ - (void)testStartObservingPaymentQueueShouldNotProcessTransactionsWhenCacheIsEmp updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; - - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[]); + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + return @[]; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + return @[]; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + return @[]; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; [handler startObservingPaymentQueue]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { @@ -288,17 +327,17 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; - SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); - SKDownload *mockDownload = OCMClassMock(SKDownload.class); - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init]; + SKDownload *downloadStub = [[SKDownload alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = - [[FIAPaymentQueueHandler alloc] initWithQueue:[[SKPaymentQueueStub alloc] init] + [[FIAPaymentQueueHandler alloc] initWithQueue:[[PaymentQueueStub alloc] init] transactionsUpdated:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil @@ -307,20 +346,38 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { - XCTAssertEqualObjects(downloads, @[ mockDownload ]); + XCTAssertEqualObjects(downloads, @[ downloadStub ]); [updateDownloadsExpectation fulfill]; } - transactionCache:mockCache]; - - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]).andReturn(@[ - mockTransaction - ]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]).andReturn(@[ - mockDownload - ]); - OCMStub([mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]).andReturn(@[ - mockTransaction - ]); + transactionCache:cacheStub]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.getObjectsForKeyStub = ^NSArray *_Nonnull(TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + return @[ transactionStub ]; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + return @[ downloadStub ]; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + return @[ transactionStub ]; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + + __block NSInteger clearInvokedCount = 0; + cacheStub.clearStub = ^{ + clearInvokedCount++; + }; [handler startObservingPaymentQueue]; @@ -328,15 +385,16 @@ - (void)testStartObservingPaymentQueueShouldProcessTransactionsForItemsInCache { updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(times(1), [mockCache getObjectsForKey:TransactionCacheKeyRemovedTransactions]); - OCMVerify(times(1), [mockCache clear]); + + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(1, TransactionCacheKeyRemovedTransactionsInvokedCount); + XCTAssertEqual(1, clearInvokedCount); } - (void)testTransactionsShouldBeCachedWhenNotObserving { - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { XCTFail("transactionsUpdated callback should not be called when cache is empty."); @@ -352,18 +410,36 @@ - (void)testTransactionsShouldBeCachedWhenNotObserving { updatedDownloads:^(NSArray *_Nonnull downloads) { XCTFail("updatedDownloads callback should not be called when cache is empty."); } - transactionCache:mockCache]; + transactionCache:cacheStub]; SKPayment *payment = [SKPayment paymentWithProduct:[[SKProductStub alloc] initWithMap:self.productResponseMap]]; + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + [handler addPayment:payment]; - OCMVerify(times(1), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyRemovedTransactions]); + XCTAssertEqual(1, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount); } - (void)testTransactionsShouldNotBeCachedWhenObserving { @@ -376,18 +452,18 @@ - (void)testTransactionsShouldNotBeCachedWhenObserving { XCTestExpectation *updateDownloadsExpectation = [self expectationWithDescription: @"downloadsUpdated callback should be called with one transaction."]; - SKPaymentTransaction *mockTransaction = OCMClassMock(SKPaymentTransaction.class); - SKDownload *mockDownload = OCMClassMock(SKDownload.class); - SKPaymentQueueStub *queue = [[SKPaymentQueueStub alloc] init]; + SKPaymentTransaction *transactionStub = [[SKPaymentTransactionStub alloc] init]; + SKDownload *downloadStub = [[SKDownload alloc] init]; + PaymentQueueStub *queue = [[PaymentQueueStub alloc] init]; queue.testState = SKPaymentTransactionStatePurchased; - FIATransactionCache *mockCache = OCMClassMock(FIATransactionCache.class); + TransactionCacheStub *cacheStub = [[TransactionCacheStub alloc] init]; FIAPaymentQueueHandler *handler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [updateTransactionsExpectation fulfill]; } transactionRemoved:^(NSArray *_Nonnull transactions) { - XCTAssertEqualObjects(transactions, @[ mockTransaction ]); + XCTAssertEqualObjects(transactions, @[ transactionStub ]); [removeTransactionsExpectation fulfill]; } restoreTransactionFailed:nil @@ -396,25 +472,44 @@ - (void)testTransactionsShouldNotBeCachedWhenObserving { return YES; } updatedDownloads:^(NSArray *_Nonnull downloads) { - XCTAssertEqualObjects(downloads, @[ mockDownload ]); + XCTAssertEqualObjects(downloads, @[ downloadStub ]); [updateDownloadsExpectation fulfill]; } - transactionCache:mockCache]; + transactionCache:cacheStub]; + + SKPaymentQueueStub *paymentQueueStub = [[SKPaymentQueueStub alloc] init]; [handler startObservingPaymentQueue]; - [handler paymentQueue:queue updatedTransactions:@[ mockTransaction ]]; - [handler paymentQueue:queue removedTransactions:@[ mockTransaction ]]; - [handler paymentQueue:queue updatedDownloads:@[ mockDownload ]]; + [handler paymentQueue:paymentQueueStub updatedTransactions:@[ transactionStub ]]; + [handler paymentQueue:paymentQueueStub removedTransactions:@[ transactionStub ]]; + [handler paymentQueue:paymentQueueStub updatedDownloads:@[ downloadStub ]]; [self waitForExpectations:@[ updateTransactionsExpectation, removeTransactionsExpectation, updateDownloadsExpectation ] timeout:5]; - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedTransactions]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyUpdatedDownloads]); - OCMVerify(never(), [mockCache addObjects:[OCMArg any] - forKey:TransactionCacheKeyRemovedTransactions]); + + __block NSInteger TransactionCacheKeyUpdatedTransactionsInvokedCount = 0; + __block NSInteger TransactionCacheKeyUpdatedDownloadsInvokedCount = 0; + __block NSInteger TransactionCacheKeyRemovedTransactionsInvokedCount = 0; + + cacheStub.addObjectsStub = ^(NSArray *_Nonnull objects, TransactionCacheKey key) { + switch (key) { + case TransactionCacheKeyUpdatedTransactions: + TransactionCacheKeyUpdatedTransactionsInvokedCount++; + break; + case TransactionCacheKeyUpdatedDownloads: + TransactionCacheKeyUpdatedDownloadsInvokedCount++; + break; + case TransactionCacheKeyRemovedTransactions: + TransactionCacheKeyRemovedTransactionsInvokedCount++; + break; + default: + XCTFail("Invalid transaction state was invoked."); + } + }; + XCTAssertEqual(0, TransactionCacheKeyUpdatedTransactionsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyUpdatedDownloadsInvokedCount); + XCTAssertEqual(0, TransactionCacheKeyRemovedTransactionsInvokedCount); } @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h index daad506e79cdc..8e7769c5b5a13 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.h @@ -4,6 +4,12 @@ #import #import +#import "FIATransactionCache.h" +#import "FLTMethodChannelProtocol.h" +#import "FLTPaymentQueueHandlerProtocol.h" +#import "FLTPaymentQueueProtocol.h" +#import "FLTRequestHandlerProtocol.h" +#import "FLTTransactionCacheProtocol.h" @import in_app_purchase_storekit; @@ -24,7 +30,7 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @end @interface SKProductRequestStub : SKProductsRequest -@property(assign, nonatomic) BOOL returnError; +@property(nonatomic, assign) BOOL returnError; - (instancetype)initWithProductIdentifiers:(NSSet *)productIdentifiers; - (instancetype)initWithFailureError:(NSError *)error; @end @@ -33,12 +39,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) - (instancetype)initWithMap:(NSDictionary *)map; @end -@interface SKRequestStub : SKRequest -@end - @interface SKPaymentQueueStub : SKPaymentQueue -@property(assign, nonatomic) SKPaymentTransactionState testState; -@property(strong, nonatomic, nullable) id observer; +@property(nonatomic, assign) SKPaymentTransactionState testState; +@property(nonatomic, strong, nullable) id observer; @end @interface SKPaymentTransactionStub : SKPaymentTransaction @@ -58,7 +61,9 @@ API_AVAILABLE(ios(11.2), macos(10.13.2)) @interface FIAPReceiptManagerStub : FIAPReceiptManager // Indicates whether getReceiptData of this stub is going to return an error. // Setting this to true will let getReceiptData give a basic NSError and return nil. -@property(assign, nonatomic) BOOL returnError; +@property(nonatomic, assign) BOOL returnError; +// Indicates whether the receipt url will be nil. +@property(nonatomic, assign) BOOL returnNilURL; @end @interface SKReceiptRefreshRequestStub : SKReceiptRefreshRequest @@ -70,4 +75,116 @@ API_AVAILABLE(ios(13.0), macos(10.15)) - (instancetype)initWithMap:(NSDictionary *)map; @end +// An interface representing a stubbed DefaultPaymentQueue +@interface PaymentQueueStub : NSObject + +// FLTPaymentQueueProtocol properties +@property(nonatomic, assign) SKPaymentTransactionState paymentState; +@property(nonatomic, strong, nullable) id observer; +@property(nonatomic, strong, readwrite) SKStorefront *storefront API_AVAILABLE(ios(13.0)); +@property(nonatomic, strong, readwrite) NSArray *transactions API_AVAILABLE( + ios(3.0), macos(10.7), watchos(6.2), visionos(1.0)); + +// Test Properties +@property(nonatomic, assign) + SKPaymentTransactionState testState; // Set this property to set a test Transaction state, then + // call addPayment to add it to the queue. +@property(nonatomic, strong, nonnull) + SKPaymentQueue *realQueue; // This is a reference to the real SKPaymentQueue + +// Stubs +@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void); +@property(nonatomic, copy, nullable) void (^restoreTransactionsStub)(NSString *); +@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void); +@property(nonatomic, copy, nullable) + NSArray * (^getUnfinishedTransactionsStub)(void); + +@end + +// An interface representing a stubbed DefaultTransactionCache +@interface TransactionCacheStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) NSArray * (^getObjectsForKeyStub)(TransactionCacheKey key); +@property(nonatomic, copy, nullable) void (^clearStub)(void); +@property(nonatomic, copy, nullable) void (^addObjectsStub)(NSArray *, TransactionCacheKey); + +@end + +// An interface representing a stubbed DefaultMethodChannel +@interface MethodChannelStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^invokeMethodChannelStub)(NSString *method, id arguments) + ; +@property(nonatomic, copy, nullable) void (^invokeMethodChannelWithResultsStub) + (NSString *method, id arguments, FlutterResult _Nullable); + +@end + +// An interface representing a stubbed DefaultPaymentQueueHandler +@interface PaymentQueueHandlerStub + : NSObject + +// Stubs +@property(nonatomic, copy, nullable) BOOL (^addPaymentStub)(SKPayment *payment); +@property(nonatomic, copy, nullable) void (^showPriceConsentIfNeededStub)(void); +@property(nonatomic, copy, nullable) void (^stopObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^startObservingPaymentQueueStub)(void); +@property(nonatomic, copy, nullable) void (^presentCodeRedemptionSheetStub)(void); +@property(nonatomic, copy, nullable) void (^restoreTransactions)(NSString *); +@property(nonatomic, copy, nullable) + NSArray * (^getUnfinishedTransactionsStub)(void); +@property(nonatomic, copy, nullable) void (^finishTransactionStub)(SKPaymentTransaction *); +@property(nonatomic, copy, nullable) void (^paymentQueueUpdatedTransactionsStub) + (SKPaymentQueue *, NSArray *); + +@end + +// An interface representing a stubbed DefaultRequestHandler +@interface RequestHandlerStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^startProductRequestWithCompletionHandlerStub) + (ProductRequestCompletion); + +@end + +#if TARGET_OS_IOS +@interface FlutterPluginRegistrarStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^addApplicationDelegateStub)(NSObject *); +@property(nonatomic, copy, nullable) void (^addMethodCallDelegateStub) + (NSObject *, FlutterMethodChannel *); +@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetStub)(NSString *); +@property(nonatomic, copy, nullable) NSString * (^lookupKeyForAssetFromPackageStub) + (NSString *, NSString *); +@property(nonatomic, copy, nullable) NSObject * (^messengerStub)(void); +@property(nonatomic, copy, nullable) void (^publishStub)(NSObject *); +@property(nonatomic, copy, nullable) void (^registerViewFactoryStub) + (NSObject *, NSString *); +@property(nonatomic, copy, nullable) NSObject * (^texturesStub)(void); +@property(nonatomic, copy, nullable) + void (^registerViewFactoryWithGestureRecognizersBlockingPolicyStub) + (NSObject *, NSString *, + FlutterPlatformViewGestureRecognizersBlockingPolicy); +@end +#endif + +@interface FlutterBinaryMessengerStub : NSObject + +// Stubs +@property(nonatomic, copy, nullable) void (^cleanUpConnectionStub)(FlutterBinaryMessengerConnection) + ; +@property(nonatomic, copy, nullable) void (^sendOnChannelMessageStub)(NSString *, NSData *); +@property(nonatomic, copy, nullable) void (^sendOnChannelMessageBinaryReplyStub) + (NSString *, NSData *, FlutterBinaryReply); +@property(nonatomic, copy, nullable) + FlutterBinaryMessengerConnection (^setMessageHandlerOnChannelBinaryMessageHandlerStub) + (NSString *, FlutterBinaryMessageHandler); +@end + NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m index 8c8b2973ebc6d..6c7d246cfede2 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/Stubs.m @@ -3,6 +3,14 @@ // found in the LICENSE file. #import "Stubs.h" +#import +#import + +#if TARGET_OS_OSX +#import +#else +#import +#endif @implementation SKProductSubscriptionPeriodStub @@ -87,8 +95,8 @@ - (instancetype)initWithProductID:(NSString *)productIdentifier { @interface SKProductRequestStub () -@property(strong, nonatomic) NSSet *identifers; -@property(strong, nonatomic) NSError *error; +@property(nonatomic, strong) NSSet *identifers; +@property(nonatomic, strong) NSError *error; @end @@ -271,6 +279,14 @@ - (NSData *)getReceiptData:(NSURL *)url error:(NSError **)error { return [[NSData alloc] initWithBase64EncodedString:originalString options:kNilOptions]; } +- (NSURL *)receiptURL { + if (self.returnNilURL) { + return nil; + } else { + return [[NSBundle mainBundle] appStoreReceiptURL]; + } +} + @end @implementation SKReceiptRefreshRequestStub { @@ -309,5 +325,325 @@ - (instancetype)initWithMap:(NSDictionary *)map { } return self; } +@end + +@implementation PaymentQueueStub + +@synthesize transactions; +@synthesize delegate; + +- (void)finishTransaction:(SKPaymentTransaction *)transaction { + [self.observer paymentQueue:self.realQueue removedTransactions:@[ transaction ]]; +} + +- (void)addPayment:(SKPayment *_Nonnull)payment { + SKPaymentTransactionStub *transaction = + [[SKPaymentTransactionStub alloc] initWithState:self.testState payment:payment]; + [self.observer paymentQueue:self.realQueue updatedTransactions:@[ transaction ]]; +} + +- (void)addTransactionObserver:(nonnull id)observer { + self.observer = observer; +} + +- (void)restoreCompletedTransactions { + [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self]; +} + +- (void)restoreCompletedTransactionsWithApplicationUsername:(nullable NSString *)username { + [self.observer paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)self]; +} + +- (NSArray *_Nonnull)getUnfinishedTransactions { + if (self.getUnfinishedTransactionsStub) { + return self.getUnfinishedTransactionsStub(); + } else { + return @[]; + } +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet { + if (self.presentCodeRedemptionSheetStub) { + self.presentCodeRedemptionSheetStub(); + } +} +#endif + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded { + if (self.showPriceConsentIfNeededStub) { + self.showPriceConsentIfNeededStub(); + } +} +#endif + +- (void)restoreTransactions:(nullable NSString *)applicationName { + if (self.restoreTransactionsStub) { + self.restoreTransactionsStub(applicationName); + } +} + +- (void)startObservingPaymentQueue { + if (self.startObservingPaymentQueueStub) { + self.startObservingPaymentQueueStub(); + } +} + +- (void)stopObservingPaymentQueue { + if (self.stopObservingPaymentQueueStub) { + self.stopObservingPaymentQueueStub(); + } +} + +- (void)removeTransactionObserver:(id)observer { + self.observer = nil; +} +@end + +@implementation MethodChannelStub +- (void)invokeMethod:(nonnull NSString *)method arguments:(id _Nullable)arguments { + if (self.invokeMethodChannelStub) { + self.invokeMethodChannelStub(method, arguments); + } +} + +- (void)invokeMethod:(nonnull NSString *)method + arguments:(id _Nullable)arguments + result:(FlutterResult _Nullable)callback { + if (self.invokeMethodChannelWithResultsStub) { + self.invokeMethodChannelWithResultsStub(method, arguments, callback); + } +} + +@end + +@implementation TransactionCacheStub +- (void)addObjects:(nonnull NSArray *)objects forKey:(TransactionCacheKey)key { + if (self.addObjectsStub) { + self.addObjectsStub(objects, key); + } +} + +- (void)clear { + if (self.clearStub) { + self.clearStub(); + } +} +- (nonnull NSArray *)getObjectsForKey:(TransactionCacheKey)key { + if (self.getObjectsForKeyStub) { + return self.getObjectsForKeyStub(key); + } + return @[]; +} @end + +@implementation PaymentQueueHandlerStub + +@synthesize storefront; +@synthesize delegate; + +- (void)paymentQueue:(nonnull SKPaymentQueue *)queue + updatedTransactions:(nonnull NSArray *)transactions { + if (self.paymentQueueUpdatedTransactionsStub) { + self.paymentQueueUpdatedTransactionsStub(queue, transactions); + } +} + +#if TARGET_OS_IOS +- (void)showPriceConsentIfNeeded { + if (self.showPriceConsentIfNeededStub) { + self.showPriceConsentIfNeededStub(); + } +} +#endif + +- (BOOL)addPayment:(nonnull SKPayment *)payment { + if (self.addPaymentStub) { + return self.addPaymentStub(payment); + } else { + return NO; + } +} + +- (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction { + if (self.finishTransactionStub) { + self.finishTransactionStub(transaction); + } +} + +- (nonnull NSArray *)getUnfinishedTransactions { + if (self.getUnfinishedTransactionsStub) { + return self.getUnfinishedTransactionsStub(); + } else { + return @[]; + } +} + +- (nonnull instancetype)initWithQueue:(nonnull id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads + transactionCache:(nonnull id)transactionCache { + return [[PaymentQueueHandlerStub alloc] init]; +} + +#if TARGET_OS_IOS +- (void)presentCodeRedemptionSheet { + if (self.presentCodeRedemptionSheetStub) { + self.presentCodeRedemptionSheetStub(); + } +} +#endif + +- (void)restoreTransactions:(nullable NSString *)applicationName { + if (self.restoreTransactions) { + self.restoreTransactions(applicationName); + } +} + +- (void)startObservingPaymentQueue { + if (self.startObservingPaymentQueueStub) { + self.startObservingPaymentQueueStub(); + } +} + +- (void)stopObservingPaymentQueue { + if (self.stopObservingPaymentQueueStub) { + self.stopObservingPaymentQueueStub(); + } +} + +- (nonnull instancetype)initWithQueue:(nonnull id)queue + transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated + transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved + restoreTransactionFailed:(nullable RestoreTransactionFailed)restoreTransactionFailed + restoreCompletedTransactionsFinished: + (nullable RestoreCompletedTransactionsFinished)restoreCompletedTransactionsFinished + shouldAddStorePayment:(nullable ShouldAddStorePayment)shouldAddStorePayment + updatedDownloads:(nullable UpdatedDownloads)updatedDownloads { + return [[PaymentQueueHandlerStub alloc] init]; +} + +@end + +@implementation RequestHandlerStub + +- (void)startProductRequestWithCompletionHandler:(nonnull ProductRequestCompletion)completion { + if (self.startProductRequestWithCompletionHandlerStub) { + self.startProductRequestWithCompletionHandlerStub(completion); + } +} +@end + +/// This mock is only used in iOS tests +#if TARGET_OS_IOS + +// This FlutterPluginRegistrar is a protocol, so to make a stub it has to be implemented. +@implementation FlutterPluginRegistrarStub + +- (void)addApplicationDelegate:(nonnull NSObject *)delegate { + if (self.addApplicationDelegateStub) { + self.addApplicationDelegateStub(delegate); + } +} + +- (void)addMethodCallDelegate:(nonnull NSObject *)delegate + channel:(nonnull FlutterMethodChannel *)channel { + if (self.addMethodCallDelegateStub) { + self.addMethodCallDelegateStub(delegate, channel); + } +} + +- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset { + if (self.lookupKeyForAssetStub) { + return self.lookupKeyForAssetStub(asset); + } + return nil; +} + +- (nonnull NSString *)lookupKeyForAsset:(nonnull NSString *)asset + fromPackage:(nonnull NSString *)package { + if (self.lookupKeyForAssetFromPackageStub) { + return self.lookupKeyForAssetFromPackageStub(asset, package); + } + return nil; +} + +- (nonnull NSObject *)messenger { + if (self.messengerStub) { + return self.messengerStub(); + } + return [[FlutterBinaryMessengerStub alloc] init]; // Or default behavior +} + +- (void)publish:(nonnull NSObject *)value { + if (self.publishStub) { + self.publishStub(value); + } +} + +- (void)registerViewFactory:(nonnull NSObject *)factory + withId:(nonnull NSString *)factoryId { + if (self.registerViewFactoryStub) { + self.registerViewFactoryStub(factory, factoryId); + } +} + +- (nonnull NSObject *)textures { + if (self.texturesStub) { + return self.texturesStub(); + } + return nil; +} + +- (void)registerViewFactory:(nonnull NSObject *)factory + withId:(nonnull NSString *)factoryId + gestureRecognizersBlockingPolicy: + (FlutterPlatformViewGestureRecognizersBlockingPolicy)gestureRecognizersBlockingPolicy { + if (self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub) { + self.registerViewFactoryWithGestureRecognizersBlockingPolicyStub( + factory, factoryId, gestureRecognizersBlockingPolicy); + } +} + +@end + +// This FlutterBinaryMessenger is a protocol, so to make a stub it has to be implemented. +@implementation FlutterBinaryMessengerStub +- (void)cleanUpConnection:(FlutterBinaryMessengerConnection)connection { + if (self.cleanUpConnectionStub) { + self.cleanUpConnectionStub(connection); + } +} + +- (void)sendOnChannel:(nonnull NSString *)channel message:(NSData *_Nullable)message { + if (self.sendOnChannelMessageStub) { + self.sendOnChannelMessageStub(channel, message); + } +} + +- (void)sendOnChannel:(nonnull NSString *)channel + message:(NSData *_Nullable)message + binaryReply:(FlutterBinaryReply _Nullable)callback { + if (self.sendOnChannelMessageBinaryReplyStub) { + self.sendOnChannelMessageBinaryReplyStub(channel, message, callback); + } +} + +- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(nonnull NSString *)channel + binaryMessageHandler: + (FlutterBinaryMessageHandler _Nullable)handler { + if (self.setMessageHandlerOnChannelBinaryMessageHandlerStub) { + return self.setMessageHandlerOnChannelBinaryMessageHandlerStub(channel, handler); + } + return 0; +} +@end + +#endif 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 b960bebd082cb..bcb280781d087 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.16 +version: 0.3.17 environment: sdk: ^3.2.3