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 044a1fef4fc5..67ff21186e7b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_storekit/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.10 + +* Converts `startProductRequest()`, `finishTransaction()`, `restoreTransactions()`, `presentCodeRedemptionSheet()` to pigeon. + ## 0.3.9 * Converts `storefront()`, `transactions()`, `addPayment()`, `canMakePayment` to pigeon. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h index bf32cc128fc1..ffb0f1fe9588 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.h @@ -69,7 +69,22 @@ NS_ASSUME_NONNULL_BEGIN + (nullable SKPaymentMessage *)convertPaymentToPigeon:(nullable SKPayment *)payment API_AVAILABLE(ios(12.2)); -+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(NSError *)error; ++ (nullable SKErrorMessage *)convertSKErrorToPigeon:(nullable NSError *)error; + ++ (nullable SKProductsResponseMessage *)convertProductsResponseToPigeon: + (nullable SKProductsResponse *)payment; + ++ (nullable SKProductMessage *)convertProductToPigeon:(nullable SKProduct *)product + API_AVAILABLE(ios(12.2)); + ++ (nullable SKProductDiscountMessage *)convertProductDiscountToPigeon: + (nullable SKProductDiscount *)productDiscount API_AVAILABLE(ios(12.2)); + ++ (nullable SKPriceLocaleMessage *)convertNSLocaleToPigeon:(nullable NSLocale *)locale + API_AVAILABLE(ios(12.2)); + ++ (nullable SKProductSubscriptionPeriodMessage *)convertSKProductSubscriptionPeriodToPigeon: + (nullable SKProductSubscriptionPeriod *)period API_AVAILABLE(ios(12.2)); @end NS_ASSUME_NONNULL_END diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m index bfc5542af5ec..b8c6a269b8ce 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/FIAObjectTranslator.m @@ -306,7 +306,11 @@ + (nullable SKPaymentTransactionMessage *)convertTransactionToPigeon: return msg; } -+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(NSError *)error { ++ (nullable SKErrorMessage *)convertSKErrorToPigeon:(nullable NSError *)error { + if (!error) { + return nil; + } + NSMutableDictionary *userInfo = [NSMutableDictionary new]; for (NSErrorUserInfoKey key in error.userInfo) { id value = error.userInfo[key]; @@ -376,4 +380,134 @@ + (nullable SKStorefrontMessage *)convertStorefrontToPigeon:(nullable SKStorefro return msg; } ++ (nullable SKProductSubscriptionPeriodMessage *)convertSKProductSubscriptionPeriodToPigeon: + (nullable SKProductSubscriptionPeriod *)period API_AVAILABLE(ios(12.2)) { + if (!period) { + return nil; + } + + SKSubscriptionPeriodUnitMessage unit; + switch (period.unit) { + case SKProductPeriodUnitDay: + unit = SKSubscriptionPeriodUnitMessageDay; + break; + case SKProductPeriodUnitWeek: + unit = SKSubscriptionPeriodUnitMessageWeek; + break; + case SKProductPeriodUnitMonth: + unit = SKSubscriptionPeriodUnitMessageMonth; + break; + case SKProductPeriodUnitYear: + unit = SKSubscriptionPeriodUnitMessageYear; + break; + } + + SKProductSubscriptionPeriodMessage *msg = + [SKProductSubscriptionPeriodMessage makeWithNumberOfUnits:period.numberOfUnits unit:unit]; + + return msg; +} + ++ (nullable SKProductDiscountMessage *)convertProductDiscountToPigeon: + (nullable SKProductDiscount *)productDiscount API_AVAILABLE(ios(12.2)) { + if (!productDiscount) { + return nil; + } + + SKProductDiscountPaymentModeMessage paymentMode; + switch (productDiscount.paymentMode) { + case SKProductDiscountPaymentModeFreeTrial: + paymentMode = SKProductDiscountPaymentModeMessageFreeTrial; + break; + case SKProductDiscountPaymentModePayAsYouGo: + paymentMode = SKProductDiscountPaymentModeMessagePayAsYouGo; + break; + case SKProductDiscountPaymentModePayUpFront: + paymentMode = SKProductDiscountPaymentModeMessagePayUpFront; + break; + } + + SKProductDiscountTypeMessage type; + switch (productDiscount.type) { + case SKProductDiscountTypeIntroductory: + type = SKProductDiscountTypeMessageIntroductory; + break; + case SKProductDiscountTypeSubscription: + type = SKProductDiscountTypeMessageSubscription; + break; + } + + SKProductDiscountMessage *msg = [SKProductDiscountMessage + makeWithPrice:productDiscount.price.description + priceLocale:[self convertNSLocaleToPigeon:productDiscount.priceLocale] + numberOfPeriods:productDiscount.numberOfPeriods + paymentMode:paymentMode + subscriptionPeriod:[self convertSKProductSubscriptionPeriodToPigeon:productDiscount + .subscriptionPeriod] + identifier:productDiscount.identifier + type:type]; + + return msg; +} + ++ (nullable SKPriceLocaleMessage *)convertNSLocaleToPigeon:(nullable NSLocale *)locale + API_AVAILABLE(ios(12.2)) { + if (!locale) { + return nil; + } + SKPriceLocaleMessage *msg = [SKPriceLocaleMessage makeWithCurrencySymbol:locale.currencySymbol + currencyCode:locale.currencyCode + countryCode:locale.countryCode]; + + return msg; +} + ++ (nullable SKProductMessage *)convertProductToPigeon:(nullable SKProduct *)product + API_AVAILABLE(ios(12.2)) { + if (!product) { + return nil; + } + + NSArray *skProductDiscounts = product.discounts; + NSMutableArray *pigeonProductDiscounts = + [NSMutableArray arrayWithCapacity:skProductDiscounts.count]; + + for (SKProductDiscount *productDiscount in skProductDiscounts) { + [pigeonProductDiscounts addObject:[self convertProductDiscountToPigeon:productDiscount]]; + }; + + SKProductMessage *msg = [SKProductMessage + makeWithProductIdentifier:product.productIdentifier + localizedTitle:product.localizedTitle + localizedDescription:product.localizedDescription + priceLocale:[self convertNSLocaleToPigeon:product.priceLocale] + subscriptionGroupIdentifier:product.subscriptionGroupIdentifier + price:product.price.description + subscriptionPeriod: + [self convertSKProductSubscriptionPeriodToPigeon:product.subscriptionPeriod] + introductoryPrice:[self convertProductDiscountToPigeon:product.introductoryPrice] + discounts:pigeonProductDiscounts]; + + return msg; +} + ++ (nullable SKProductsResponseMessage *)convertProductsResponseToPigeon: + (nullable SKProductsResponse *)productsResponse API_AVAILABLE(ios(12.2)) { + if (!productsResponse) { + return nil; + } + NSArray *skProducts = productsResponse.products; + NSMutableArray *pigeonProducts = + [NSMutableArray arrayWithCapacity:skProducts.count]; + + for (SKProduct *product in skProducts) { + [pigeonProducts addObject:[self convertProductToPigeon:product]]; + }; + + SKProductsResponseMessage *msg = + [SKProductsResponseMessage makeWithProducts:pigeonProducts + invalidProductIdentifiers:productsResponse.invalidProductIdentifiers]; + return msg; +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m index 88cc4e8a5d03..82366537404b 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/InAppPurchasePlugin.m @@ -88,18 +88,7 @@ - (instancetype)initWithRegistrar:(NSObject *)registrar } - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) { - [self handleProductRequestMethodCall:call result:result]; - } else if ([@"-[InAppPurchasePlugin finishTransaction:result:]" isEqualToString:call.method]) { - [self finishTransaction:call result:result]; - } else if ([@"-[InAppPurchasePlugin restoreTransactions:result:]" isEqualToString:call.method]) { - [self restoreTransactions:call result:result]; -#if TARGET_OS_IOS - } else if ([@"-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]" - isEqualToString:call.method]) { - [self presentCodeRedemptionSheet:call result:result]; -#endif - } else if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) { + if ([@"-[InAppPurchasePlugin retrieveReceiptData:result:]" isEqualToString:call.method]) { [self retrieveReceiptData:call result:result]; } else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) { [self refreshReceipt:call result:result]; @@ -147,38 +136,38 @@ - (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable * return [FIAObjectTranslator convertStorefrontToPigeon:storefront]; } -- (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - if (![call.arguments isKindOfClass:[NSArray class]]) { - result([FlutterError errorWithCode:@"storekit_invalid_argument" - message:@"Argument type of startRequest is not array" - details:call.arguments]); - return; - } - NSArray *productIdentifiers = (NSArray *)call.arguments; +- (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers + completion:(void (^)(SKProductsResponseMessage *_Nullable, + FlutterError *_Nullable))completion { SKProductsRequest *request = [self getProductRequestWithIdentifiers:[NSSet setWithArray:productIdentifiers]]; FIAPRequestHandler *handler = [[FIAPRequestHandler alloc] initWithRequest:request]; [self.requestHandlers addObject:handler]; __weak typeof(self) weakSelf = self; + [handler startProductRequestWithCompletionHandler:^(SKProductsResponse *_Nullable response, - NSError *_Nullable error) { - if (error) { - result([FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" - message:error.localizedDescription - details:error.description]); + NSError *_Nullable startProductRequestError) { + FlutterError *error = nil; + if (startProductRequestError != nil) { + error = [FlutterError errorWithCode:@"storekit_getproductrequest_platform_error" + message:startProductRequestError.localizedDescription + details:startProductRequestError.description]; + completion(nil, error); return; } if (!response) { - result([FlutterError errorWithCode:@"storekit_platform_no_response" - message:@"Failed to get SKProductResponse in startRequest " - @"call. Error occured on iOS platform" - details:call.arguments]); + error = [FlutterError errorWithCode:@"storekit_platform_no_response" + message:@"Failed to get SKProductResponse in startRequest " + @"call. Error occured on iOS platform" + details:productIdentifiers]; + completion(nil, error); return; } for (SKProduct *product in response.products) { [self.productsCache setObject:product forKey:product.productIdentifier]; } - result([FIAObjectTranslator getMapFromSKProductsResponse:response]); + + completion([FIAObjectTranslator convertProductsResponseToPigeon:response], error); [weakSelf.requestHandlers removeObject:handler]; }]; } @@ -240,16 +229,10 @@ - (void)addPaymentPaymentMap:(nonnull NSDictionary *)paymentMap } } -- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result { - if (![call.arguments isKindOfClass:[NSDictionary class]]) { - result([FlutterError errorWithCode:@"storekit_invalid_argument" - message:@"Argument type of finishTransaction is not a Dictionary" - details:call.arguments]); - return; - } - NSDictionary *paymentMap = (NSDictionary *)call.arguments; - NSString *transactionIdentifier = [paymentMap objectForKey:@"transactionIdentifier"]; - NSString *productIdentifier = [paymentMap objectForKey:@"productIdentifier"]; +- (void)finishTransactionFinishMap:(nonnull NSDictionary *)finishMap + error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error { + NSString *transactionIdentifier = [finishMap objectForKey:@"transactionIdentifier"]; + NSString *productIdentifier = [finishMap objectForKey:@"productIdentifier"]; NSArray *pendingTransactions = [self.paymentQueueHandler getUnfinishedTransactions]; @@ -265,35 +248,27 @@ - (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result @try { [self.paymentQueueHandler finishTransaction:transaction]; } @catch (NSException *e) { - result([FlutterError errorWithCode:@"storekit_finish_transaction_exception" - message:e.name - details:e.description]); + *error = [FlutterError errorWithCode:@"storekit_finish_transaction_exception" + message:e.name + details:e.description]; return; } } } - - result(nil); } -- (void)restoreTransactions:(FlutterMethodCall *)call result:(FlutterResult)result { - if (call.arguments && ![call.arguments isKindOfClass:[NSString class]]) { - result([FlutterError - errorWithCode:@"storekit_invalid_argument" - message:@"Argument is not nil and the type of finishTransaction is not a string." - details:call.arguments]); - return; - } - [self.paymentQueueHandler restoreTransactions:call.arguments]; - result(nil); +- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName + error:(FlutterError *_Nullable __autoreleasing *_Nonnull) + error { + [self.paymentQueueHandler restoreTransactions:applicationUserName]; } +- (void)presentCodeRedemptionSheetWithError: + (FlutterError *_Nullable __autoreleasing *_Nonnull)error { #if TARGET_OS_IOS -- (void)presentCodeRedemptionSheet:(FlutterMethodCall *)call result:(FlutterResult)result { [self.paymentQueueHandler presentCodeRedemptionSheet]; - result(nil); -} #endif +} - (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result { FlutterError *error = nil; diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h index 3874b7b20a88..87a197212504 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.h @@ -52,11 +52,60 @@ typedef NS_ENUM(NSUInteger, SKPaymentTransactionStateMessage) { - (instancetype)initWithValue:(SKPaymentTransactionStateMessage)value; @end +typedef NS_ENUM(NSUInteger, SKProductDiscountTypeMessage) { + /// A constant indicating the discount type is an introductory offer. + SKProductDiscountTypeMessageIntroductory = 0, + /// A constant indicating the discount type is a promotional offer. + SKProductDiscountTypeMessageSubscription = 1, +}; + +/// Wrapper for SKProductDiscountTypeMessage to allow for nullability. +@interface SKProductDiscountTypeMessageBox : NSObject +@property(nonatomic, assign) SKProductDiscountTypeMessage value; +- (instancetype)initWithValue:(SKProductDiscountTypeMessage)value; +@end + +typedef NS_ENUM(NSUInteger, SKProductDiscountPaymentModeMessage) { + /// Allows user to pay the discounted price at each payment period. + SKProductDiscountPaymentModeMessagePayAsYouGo = 0, + /// Allows user to pay the discounted price upfront and receive the product for the rest of time + /// that was paid for. + SKProductDiscountPaymentModeMessagePayUpFront = 1, + /// User pays nothing during the discounted period. + SKProductDiscountPaymentModeMessageFreeTrial = 2, + /// Unspecified mode. + SKProductDiscountPaymentModeMessageUnspecified = 3, +}; + +/// Wrapper for SKProductDiscountPaymentModeMessage to allow for nullability. +@interface SKProductDiscountPaymentModeMessageBox : NSObject +@property(nonatomic, assign) SKProductDiscountPaymentModeMessage value; +- (instancetype)initWithValue:(SKProductDiscountPaymentModeMessage)value; +@end + +typedef NS_ENUM(NSUInteger, SKSubscriptionPeriodUnitMessage) { + SKSubscriptionPeriodUnitMessageDay = 0, + SKSubscriptionPeriodUnitMessageWeek = 1, + SKSubscriptionPeriodUnitMessageMonth = 2, + SKSubscriptionPeriodUnitMessageYear = 3, +}; + +/// Wrapper for SKSubscriptionPeriodUnitMessage to allow for nullability. +@interface SKSubscriptionPeriodUnitMessageBox : NSObject +@property(nonatomic, assign) SKSubscriptionPeriodUnitMessage value; +- (instancetype)initWithValue:(SKSubscriptionPeriodUnitMessage)value; +@end + @class SKPaymentTransactionMessage; @class SKPaymentMessage; @class SKErrorMessage; @class SKPaymentDiscountMessage; @class SKStorefrontMessage; +@class SKProductsResponseMessage; +@class SKProductMessage; +@class SKPriceLocaleMessage; +@class SKProductDiscountMessage; +@class SKProductSubscriptionPeriodMessage; @interface SKPaymentTransactionMessage : NSObject /// `init` unavailable to enforce nonnull fields, see the `make` class method. @@ -126,6 +175,79 @@ typedef NS_ENUM(NSUInteger, SKPaymentTransactionStateMessage) { @property(nonatomic, copy) NSString *identifier; @end +@interface SKProductsResponseMessage : NSObject ++ (instancetype)makeWithProducts:(nullable NSArray *)products + invalidProductIdentifiers:(nullable NSArray *)invalidProductIdentifiers; +@property(nonatomic, copy, nullable) NSArray *products; +@property(nonatomic, copy, nullable) NSArray *invalidProductIdentifiers; +@end + +@interface SKProductMessage : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithProductIdentifier:(NSString *)productIdentifier + localizedTitle:(NSString *)localizedTitle + localizedDescription:(NSString *)localizedDescription + priceLocale:(SKPriceLocaleMessage *)priceLocale + subscriptionGroupIdentifier:(nullable NSString *)subscriptionGroupIdentifier + price:(NSString *)price + subscriptionPeriod: + (nullable SKProductSubscriptionPeriodMessage *)subscriptionPeriod + introductoryPrice:(nullable SKProductDiscountMessage *)introductoryPrice + discounts:(nullable NSArray *)discounts; +@property(nonatomic, copy) NSString *productIdentifier; +@property(nonatomic, copy) NSString *localizedTitle; +@property(nonatomic, copy) NSString *localizedDescription; +@property(nonatomic, strong) SKPriceLocaleMessage *priceLocale; +@property(nonatomic, copy, nullable) NSString *subscriptionGroupIdentifier; +@property(nonatomic, copy) NSString *price; +@property(nonatomic, strong, nullable) SKProductSubscriptionPeriodMessage *subscriptionPeriod; +@property(nonatomic, strong, nullable) SKProductDiscountMessage *introductoryPrice; +@property(nonatomic, copy, nullable) NSArray *discounts; +@end + +@interface SKPriceLocaleMessage : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithCurrencySymbol:(NSString *)currencySymbol + currencyCode:(NSString *)currencyCode + countryCode:(NSString *)countryCode; +/// The currency symbol for the locale, e.g. $ for US locale. +@property(nonatomic, copy) NSString *currencySymbol; +/// The currency code for the locale, e.g. USD for US locale. +@property(nonatomic, copy) NSString *currencyCode; +/// The country code for the locale, e.g. US for US locale. +@property(nonatomic, copy) NSString *countryCode; +@end + +@interface SKProductDiscountMessage : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithPrice:(NSString *)price + priceLocale:(SKPriceLocaleMessage *)priceLocale + numberOfPeriods:(NSInteger)numberOfPeriods + paymentMode:(SKProductDiscountPaymentModeMessage)paymentMode + subscriptionPeriod:(SKProductSubscriptionPeriodMessage *)subscriptionPeriod + identifier:(nullable NSString *)identifier + type:(SKProductDiscountTypeMessage)type; +@property(nonatomic, copy) NSString *price; +@property(nonatomic, strong) SKPriceLocaleMessage *priceLocale; +@property(nonatomic, assign) NSInteger numberOfPeriods; +@property(nonatomic, assign) SKProductDiscountPaymentModeMessage paymentMode; +@property(nonatomic, strong) SKProductSubscriptionPeriodMessage *subscriptionPeriod; +@property(nonatomic, copy, nullable) NSString *identifier; +@property(nonatomic, assign) SKProductDiscountTypeMessage type; +@end + +@interface SKProductSubscriptionPeriodMessage : NSObject +/// `init` unavailable to enforce nonnull fields, see the `make` class method. +- (instancetype)init NS_UNAVAILABLE; ++ (instancetype)makeWithNumberOfUnits:(NSInteger)numberOfUnits + unit:(SKSubscriptionPeriodUnitMessage)unit; +@property(nonatomic, assign) NSInteger numberOfUnits; +@property(nonatomic, assign) SKSubscriptionPeriodUnitMessage unit; +@end + /// The codec used by InAppPurchaseAPI. NSObject *InAppPurchaseAPIGetCodec(void); @@ -141,6 +263,14 @@ NSObject *InAppPurchaseAPIGetCodec(void); - (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error; - (void)addPaymentPaymentMap:(NSDictionary *)paymentMap error:(FlutterError *_Nullable *_Nonnull)error; +- (void)startProductRequestProductIdentifiers:(NSArray *)productIdentifiers + completion:(void (^)(SKProductsResponseMessage *_Nullable, + FlutterError *_Nullable))completion; +- (void)finishTransactionFinishMap:(NSDictionary *)finishMap + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)restoreTransactionsApplicationUserName:(nullable NSString *)applicationUserName + error:(FlutterError *_Nullable *_Nonnull)error; +- (void)presentCodeRedemptionSheetWithError:(FlutterError *_Nullable *_Nonnull)error; @end extern void SetUpInAppPurchaseAPI(id binaryMessenger, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m index 6608b7a0002b..79599d932e0e 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/darwin/Classes/messages.g.m @@ -1,6 +1,7 @@ // Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. + // Autogenerated from Pigeon (v16.0.4), do not edit directly. // See also: https://pub.dev/packages/pigeon @@ -40,6 +41,36 @@ - (instancetype)initWithValue:(SKPaymentTransactionStateMessage)value { } @end +@implementation SKProductDiscountTypeMessageBox +- (instancetype)initWithValue:(SKProductDiscountTypeMessage)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation SKProductDiscountPaymentModeMessageBox +- (instancetype)initWithValue:(SKProductDiscountPaymentModeMessage)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + +@implementation SKSubscriptionPeriodUnitMessageBox +- (instancetype)initWithValue:(SKSubscriptionPeriodUnitMessage)value { + self = [super init]; + if (self) { + _value = value; + } + return self; +} +@end + @interface SKPaymentTransactionMessage () + (SKPaymentTransactionMessage *)fromList:(NSArray *)list; + (nullable SKPaymentTransactionMessage *)nullableFromList:(NSArray *)list; @@ -70,6 +101,36 @@ + (nullable SKStorefrontMessage *)nullableFromList:(NSArray *)list; - (NSArray *)toList; @end +@interface SKProductsResponseMessage () ++ (SKProductsResponseMessage *)fromList:(NSArray *)list; ++ (nullable SKProductsResponseMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface SKProductMessage () ++ (SKProductMessage *)fromList:(NSArray *)list; ++ (nullable SKProductMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface SKPriceLocaleMessage () ++ (SKPriceLocaleMessage *)fromList:(NSArray *)list; ++ (nullable SKPriceLocaleMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface SKProductDiscountMessage () ++ (SKProductDiscountMessage *)fromList:(NSArray *)list; ++ (nullable SKProductDiscountMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + +@interface SKProductSubscriptionPeriodMessage () ++ (SKProductSubscriptionPeriodMessage *)fromList:(NSArray *)list; ++ (nullable SKProductSubscriptionPeriodMessage *)nullableFromList:(NSArray *)list; +- (NSArray *)toList; +@end + @implementation SKPaymentTransactionMessage + (instancetype)makeWithPayment:(SKPaymentMessage *)payment transactionState:(SKPaymentTransactionStateMessage)transactionState @@ -244,6 +305,191 @@ - (NSArray *)toList { } @end +@implementation SKProductsResponseMessage ++ (instancetype)makeWithProducts:(nullable NSArray *)products + invalidProductIdentifiers:(nullable NSArray *)invalidProductIdentifiers { + SKProductsResponseMessage *pigeonResult = [[SKProductsResponseMessage alloc] init]; + pigeonResult.products = products; + pigeonResult.invalidProductIdentifiers = invalidProductIdentifiers; + return pigeonResult; +} ++ (SKProductsResponseMessage *)fromList:(NSArray *)list { + SKProductsResponseMessage *pigeonResult = [[SKProductsResponseMessage alloc] init]; + pigeonResult.products = GetNullableObjectAtIndex(list, 0); + pigeonResult.invalidProductIdentifiers = GetNullableObjectAtIndex(list, 1); + return pigeonResult; +} ++ (nullable SKProductsResponseMessage *)nullableFromList:(NSArray *)list { + return (list) ? [SKProductsResponseMessage fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + self.products ?: [NSNull null], + self.invalidProductIdentifiers ?: [NSNull null], + ]; +} +@end + +@implementation SKProductMessage ++ (instancetype) + makeWithProductIdentifier:(NSString *)productIdentifier + localizedTitle:(NSString *)localizedTitle + localizedDescription:(NSString *)localizedDescription + priceLocale:(SKPriceLocaleMessage *)priceLocale + subscriptionGroupIdentifier:(nullable NSString *)subscriptionGroupIdentifier + price:(NSString *)price + subscriptionPeriod:(nullable SKProductSubscriptionPeriodMessage *)subscriptionPeriod + introductoryPrice:(nullable SKProductDiscountMessage *)introductoryPrice + discounts:(nullable NSArray *)discounts { + SKProductMessage *pigeonResult = [[SKProductMessage alloc] init]; + pigeonResult.productIdentifier = productIdentifier; + pigeonResult.localizedTitle = localizedTitle; + pigeonResult.localizedDescription = localizedDescription; + pigeonResult.priceLocale = priceLocale; + pigeonResult.subscriptionGroupIdentifier = subscriptionGroupIdentifier; + pigeonResult.price = price; + pigeonResult.subscriptionPeriod = subscriptionPeriod; + pigeonResult.introductoryPrice = introductoryPrice; + pigeonResult.discounts = discounts; + return pigeonResult; +} ++ (SKProductMessage *)fromList:(NSArray *)list { + SKProductMessage *pigeonResult = [[SKProductMessage alloc] init]; + pigeonResult.productIdentifier = GetNullableObjectAtIndex(list, 0); + pigeonResult.localizedTitle = GetNullableObjectAtIndex(list, 1); + pigeonResult.localizedDescription = GetNullableObjectAtIndex(list, 2); + pigeonResult.priceLocale = + [SKPriceLocaleMessage nullableFromList:(GetNullableObjectAtIndex(list, 3))]; + pigeonResult.subscriptionGroupIdentifier = GetNullableObjectAtIndex(list, 4); + pigeonResult.price = GetNullableObjectAtIndex(list, 5); + pigeonResult.subscriptionPeriod = + [SKProductSubscriptionPeriodMessage nullableFromList:(GetNullableObjectAtIndex(list, 6))]; + pigeonResult.introductoryPrice = + [SKProductDiscountMessage nullableFromList:(GetNullableObjectAtIndex(list, 7))]; + pigeonResult.discounts = GetNullableObjectAtIndex(list, 8); + return pigeonResult; +} ++ (nullable SKProductMessage *)nullableFromList:(NSArray *)list { + return (list) ? [SKProductMessage fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + self.productIdentifier ?: [NSNull null], + self.localizedTitle ?: [NSNull null], + self.localizedDescription ?: [NSNull null], + (self.priceLocale ? [self.priceLocale toList] : [NSNull null]), + self.subscriptionGroupIdentifier ?: [NSNull null], + self.price ?: [NSNull null], + (self.subscriptionPeriod ? [self.subscriptionPeriod toList] : [NSNull null]), + (self.introductoryPrice ? [self.introductoryPrice toList] : [NSNull null]), + self.discounts ?: [NSNull null], + ]; +} +@end + +@implementation SKPriceLocaleMessage ++ (instancetype)makeWithCurrencySymbol:(NSString *)currencySymbol + currencyCode:(NSString *)currencyCode + countryCode:(NSString *)countryCode { + SKPriceLocaleMessage *pigeonResult = [[SKPriceLocaleMessage alloc] init]; + pigeonResult.currencySymbol = currencySymbol; + pigeonResult.currencyCode = currencyCode; + pigeonResult.countryCode = countryCode; + return pigeonResult; +} ++ (SKPriceLocaleMessage *)fromList:(NSArray *)list { + SKPriceLocaleMessage *pigeonResult = [[SKPriceLocaleMessage alloc] init]; + pigeonResult.currencySymbol = GetNullableObjectAtIndex(list, 0); + pigeonResult.currencyCode = GetNullableObjectAtIndex(list, 1); + pigeonResult.countryCode = GetNullableObjectAtIndex(list, 2); + return pigeonResult; +} ++ (nullable SKPriceLocaleMessage *)nullableFromList:(NSArray *)list { + return (list) ? [SKPriceLocaleMessage fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + self.currencySymbol ?: [NSNull null], + self.currencyCode ?: [NSNull null], + self.countryCode ?: [NSNull null], + ]; +} +@end + +@implementation SKProductDiscountMessage ++ (instancetype)makeWithPrice:(NSString *)price + priceLocale:(SKPriceLocaleMessage *)priceLocale + numberOfPeriods:(NSInteger)numberOfPeriods + paymentMode:(SKProductDiscountPaymentModeMessage)paymentMode + subscriptionPeriod:(SKProductSubscriptionPeriodMessage *)subscriptionPeriod + identifier:(nullable NSString *)identifier + type:(SKProductDiscountTypeMessage)type { + SKProductDiscountMessage *pigeonResult = [[SKProductDiscountMessage alloc] init]; + pigeonResult.price = price; + pigeonResult.priceLocale = priceLocale; + pigeonResult.numberOfPeriods = numberOfPeriods; + pigeonResult.paymentMode = paymentMode; + pigeonResult.subscriptionPeriod = subscriptionPeriod; + pigeonResult.identifier = identifier; + pigeonResult.type = type; + return pigeonResult; +} ++ (SKProductDiscountMessage *)fromList:(NSArray *)list { + SKProductDiscountMessage *pigeonResult = [[SKProductDiscountMessage alloc] init]; + pigeonResult.price = GetNullableObjectAtIndex(list, 0); + pigeonResult.priceLocale = + [SKPriceLocaleMessage nullableFromList:(GetNullableObjectAtIndex(list, 1))]; + pigeonResult.numberOfPeriods = [GetNullableObjectAtIndex(list, 2) integerValue]; + pigeonResult.paymentMode = [GetNullableObjectAtIndex(list, 3) integerValue]; + pigeonResult.subscriptionPeriod = + [SKProductSubscriptionPeriodMessage nullableFromList:(GetNullableObjectAtIndex(list, 4))]; + pigeonResult.identifier = GetNullableObjectAtIndex(list, 5); + pigeonResult.type = [GetNullableObjectAtIndex(list, 6) integerValue]; + return pigeonResult; +} ++ (nullable SKProductDiscountMessage *)nullableFromList:(NSArray *)list { + return (list) ? [SKProductDiscountMessage fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + self.price ?: [NSNull null], + (self.priceLocale ? [self.priceLocale toList] : [NSNull null]), + @(self.numberOfPeriods), + @(self.paymentMode), + (self.subscriptionPeriod ? [self.subscriptionPeriod toList] : [NSNull null]), + self.identifier ?: [NSNull null], + @(self.type), + ]; +} +@end + +@implementation SKProductSubscriptionPeriodMessage ++ (instancetype)makeWithNumberOfUnits:(NSInteger)numberOfUnits + unit:(SKSubscriptionPeriodUnitMessage)unit { + SKProductSubscriptionPeriodMessage *pigeonResult = + [[SKProductSubscriptionPeriodMessage alloc] init]; + pigeonResult.numberOfUnits = numberOfUnits; + pigeonResult.unit = unit; + return pigeonResult; +} ++ (SKProductSubscriptionPeriodMessage *)fromList:(NSArray *)list { + SKProductSubscriptionPeriodMessage *pigeonResult = + [[SKProductSubscriptionPeriodMessage alloc] init]; + pigeonResult.numberOfUnits = [GetNullableObjectAtIndex(list, 0) integerValue]; + pigeonResult.unit = [GetNullableObjectAtIndex(list, 1) integerValue]; + return pigeonResult; +} ++ (nullable SKProductSubscriptionPeriodMessage *)nullableFromList:(NSArray *)list { + return (list) ? [SKProductSubscriptionPeriodMessage fromList:list] : nil; +} +- (NSArray *)toList { + return @[ + @(self.numberOfUnits), + @(self.unit), + ]; +} +@end + @interface InAppPurchaseAPICodecReader : FlutterStandardReader @end @implementation InAppPurchaseAPICodecReader @@ -258,6 +504,16 @@ - (nullable id)readValueOfType:(UInt8)type { case 131: return [SKPaymentTransactionMessage fromList:[self readValue]]; case 132: + return [SKPriceLocaleMessage fromList:[self readValue]]; + case 133: + return [SKProductDiscountMessage fromList:[self readValue]]; + case 134: + return [SKProductMessage fromList:[self readValue]]; + case 135: + return [SKProductSubscriptionPeriodMessage fromList:[self readValue]]; + case 136: + return [SKProductsResponseMessage fromList:[self readValue]]; + case 137: return [SKStorefrontMessage fromList:[self readValue]]; default: return [super readValueOfType:type]; @@ -281,9 +537,24 @@ - (void)writeValue:(id)value { } else if ([value isKindOfClass:[SKPaymentTransactionMessage class]]) { [self writeByte:131]; [self writeValue:[value toList]]; - } else if ([value isKindOfClass:[SKStorefrontMessage class]]) { + } else if ([value isKindOfClass:[SKPriceLocaleMessage class]]) { [self writeByte:132]; [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[SKProductDiscountMessage class]]) { + [self writeByte:133]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[SKProductMessage class]]) { + [self writeByte:134]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[SKProductSubscriptionPeriodMessage class]]) { + [self writeByte:135]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[SKProductsResponseMessage class]]) { + [self writeByte:136]; + [self writeValue:[value toList]]; + } else if ([value isKindOfClass:[SKStorefrontMessage class]]) { + [self writeByte:137]; + [self writeValue:[value toList]]; } else { [super writeValue:value]; } @@ -392,4 +663,93 @@ void SetUpInAppPurchaseAPI(id binaryMessenger, [channel setMessageHandler:nil]; } } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(startProductRequestProductIdentifiers: + completion:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(startProductRequestProductIdentifiers:completion:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSArray *arg_productIdentifiers = GetNullableObjectAtIndex(args, 0); + [api startProductRequestProductIdentifiers:arg_productIdentifiers + completion:^(SKProductsResponseMessage *_Nullable output, + FlutterError *_Nullable error) { + callback(wrapResult(output, error)); + }]; + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(finishTransactionFinishMap:error:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(finishTransactionFinishMap:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSDictionary *arg_finishMap = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + [api finishTransactionFinishMap:arg_finishMap error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName: + @"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(restoreTransactionsApplicationUserName:error:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(restoreTransactionsApplicationUserName:error:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + NSArray *args = message; + NSString *arg_applicationUserName = GetNullableObjectAtIndex(args, 0); + FlutterError *error; + [api restoreTransactionsApplicationUserName:arg_applicationUserName error:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } + { + FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc] + initWithName:@"dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI." + @"presentCodeRedemptionSheet" + binaryMessenger:binaryMessenger + codec:InAppPurchaseAPIGetCodec()]; + if (api) { + NSCAssert([api respondsToSelector:@selector(presentCodeRedemptionSheetWithError:)], + @"InAppPurchaseAPI api (%@) doesn't respond to " + @"@selector(presentCodeRedemptionSheetWithError:)", + api); + [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) { + FlutterError *error; + [api presentCodeRedemptionSheetWithError:&error]; + callback(wrapResult(nil, error)); + }]; + } else { + [channel setMessageHandler:nil]; + } + } } 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 3b855991b8f7..d53301e54c39 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 @@ -105,22 +105,21 @@ - (void)testPaymentQueueStorefrontReturnsNil { } - (void)testGetProductResponse { + NSArray *argument = @[ @"123" ]; XCTestExpectation *expectation = - [self expectationWithDescription:@"expect response contains 1 item"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin startProductRequest:result:]" - arguments:@[ @"123" ]]; - __block id result; - [self.plugin handleMethodCall:call - result:^(id r) { - [expectation fulfill]; - result = r; - }]; + [self expectationWithDescription:@"completion handler successfully called"]; + [self.plugin + startProductRequestProductIdentifiers:argument + completion:^(SKProductsResponseMessage *_Nullable response, + FlutterError *_Nullable startProductRequestError) { + XCTAssert( + [response isKindOfClass:[SKProductsResponseMessage class]]); + XCTAssertEqual(response.products.count, 1); + XCTAssertEqual(response.invalidProductIdentifiers.count, 0); + XCTAssertEqual(response.products[0].productIdentifier, @"123"); + [expectation fulfill]; + }]; [self waitForExpectations:@[ expectation ] timeout:5]; - XCTAssert([result isKindOfClass:[NSDictionary class]]); - NSArray *resultArray = [result objectForKey:@"products"]; - XCTAssertEqual(resultArray.count, 1); - XCTAssertTrue([resultArray.firstObject[@"productIdentifier"] isEqualToString:@"123"]); } - (void)testAddPaymentShouldReturnFlutterErrorWhenPaymentFails { @@ -273,11 +272,10 @@ - (void)testAddPaymentWithNullSandboxArgument { - (void)testRestoreTransactions { XCTestExpectation *expectation = [self expectationWithDescription:@"result successfully restore transactions"]; - FlutterMethodCall *call = [FlutterMethodCall - methodCallWithMethodName:@"-[InAppPurchasePlugin restoreTransactions:result:]" - arguments:nil]; + SKPaymentQueueStub *queue = [SKPaymentQueueStub new]; queue.testState = SKPaymentTransactionStatePurchased; + __block BOOL callbackInvoked = NO; self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:queue transactionsUpdated:^(NSArray *_Nonnull transactions) { @@ -292,9 +290,10 @@ - (void)testRestoreTransactions { updatedDownloads:nil transactionCache:OCMClassMock(FIATransactionCache.class)]; [queue addTransactionObserver:self.plugin.paymentQueueHandler]; - [self.plugin handleMethodCall:call - result:^(id r){ - }]; + + FlutterError *error; + [self.plugin restoreTransactionsApplicationUserName:nil error:&error]; + [self waitForExpectations:@[ expectation ] timeout:5]; XCTAssertTrue(callbackInvoked); } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m index 23597321de9a..0060051dad6a 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m +++ b/packages/in_app_purchase/in_app_purchase_storekit/example/shared/RunnerTests/TranslatorTests.m @@ -458,4 +458,41 @@ - (void)testSKPaymentTransactionConvertToPigeon { XCTAssertEqual(paymentTransaction.transactionIdentifier, msg.transactionIdentifier); } +- (void)testSKProductResponseCovertToPigeon { + SKProductsResponseStub *response = + [[SKProductsResponseStub alloc] initWithMap:self.productResponseMap]; + SKProductsResponseMessage *responseMsg = + [FIAObjectTranslator convertProductsResponseToPigeon:response]; + + XCTAssertEqual(responseMsg.products.count, 1); + XCTAssertEqual(responseMsg.invalidProductIdentifiers.count, 0); + + SKProductMessage *productMsg = responseMsg.products[0]; + + // These values are being set in productResponseMap in setUp() + XCTAssertEqualObjects(productMsg.price, @"1"); + XCTAssertEqualObjects(productMsg.productIdentifier, @"123"); + XCTAssertEqualObjects(productMsg.localizedTitle, @"title"); + XCTAssertEqualObjects(productMsg.localizedDescription, @"des"); + XCTAssertEqualObjects(productMsg.subscriptionGroupIdentifier, @"com.group"); + + SKPriceLocaleMessage *localeMsg = productMsg.priceLocale; + SKProductSubscriptionPeriodMessage *subPeriod = productMsg.subscriptionPeriod; + SKProductDiscountMessage *introDiscount = productMsg.introductoryPrice; + NSArray *discounts = productMsg.discounts; + + XCTAssertEqualObjects(localeMsg.countryCode, nil); + XCTAssertEqualObjects(localeMsg.currencyCode, nil); + XCTAssertEqualObjects(localeMsg.currencySymbol, @"\u00a4"); + + XCTAssertEqual(subPeriod.unit, SKSubscriptionPeriodUnitMessageDay); + XCTAssertEqual(subPeriod.numberOfUnits, 0); + + XCTAssertEqualObjects(introDiscount.price, @"1"); + XCTAssertEqual(introDiscount.numberOfPeriods, 1); + XCTAssertEqual(introDiscount.paymentMode, SKProductDiscountPaymentModeMessagePayUpFront); + + XCTAssertEqual(discounts.count, 1); +} + @end diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart index ba454380fbc9..bfbd447de677 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/messages.g.dart @@ -63,6 +63,35 @@ enum SKPaymentTransactionStateMessage { unspecified, } +enum SKProductDiscountTypeMessage { + /// A constant indicating the discount type is an introductory offer. + introductory, + + /// A constant indicating the discount type is a promotional offer. + subscription, +} + +enum SKProductDiscountPaymentModeMessage { + /// Allows user to pay the discounted price at each payment period. + payAsYouGo, + + /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. + payUpFront, + + /// User pays nothing during the discounted period. + freeTrial, + + /// Unspecified mode. + unspecified, +} + +enum SKSubscriptionPeriodUnitMessage { + day, + week, + month, + year, +} + class SKPaymentTransactionMessage { SKPaymentTransactionMessage({ required this.payment, @@ -260,6 +289,212 @@ class SKStorefrontMessage { } } +class SKProductsResponseMessage { + SKProductsResponseMessage({ + this.products, + this.invalidProductIdentifiers, + }); + + List? products; + + List? invalidProductIdentifiers; + + Object encode() { + return [ + products, + invalidProductIdentifiers, + ]; + } + + static SKProductsResponseMessage decode(Object result) { + result as List; + return SKProductsResponseMessage( + products: (result[0] as List?)?.cast(), + invalidProductIdentifiers: (result[1] as List?)?.cast(), + ); + } +} + +class SKProductMessage { + SKProductMessage({ + required this.productIdentifier, + required this.localizedTitle, + required this.localizedDescription, + required this.priceLocale, + this.subscriptionGroupIdentifier, + required this.price, + this.subscriptionPeriod, + this.introductoryPrice, + this.discounts, + }); + + String productIdentifier; + + String localizedTitle; + + String localizedDescription; + + SKPriceLocaleMessage priceLocale; + + String? subscriptionGroupIdentifier; + + String price; + + SKProductSubscriptionPeriodMessage? subscriptionPeriod; + + SKProductDiscountMessage? introductoryPrice; + + List? discounts; + + Object encode() { + return [ + productIdentifier, + localizedTitle, + localizedDescription, + priceLocale.encode(), + subscriptionGroupIdentifier, + price, + subscriptionPeriod?.encode(), + introductoryPrice?.encode(), + discounts, + ]; + } + + static SKProductMessage decode(Object result) { + result as List; + return SKProductMessage( + productIdentifier: result[0]! as String, + localizedTitle: result[1]! as String, + localizedDescription: result[2]! as String, + priceLocale: SKPriceLocaleMessage.decode(result[3]! as List), + subscriptionGroupIdentifier: result[4] as String?, + price: result[5]! as String, + subscriptionPeriod: result[6] != null + ? SKProductSubscriptionPeriodMessage.decode( + result[6]! as List) + : null, + introductoryPrice: result[7] != null + ? SKProductDiscountMessage.decode(result[7]! as List) + : null, + discounts: + (result[8] as List?)?.cast(), + ); + } +} + +class SKPriceLocaleMessage { + SKPriceLocaleMessage({ + required this.currencySymbol, + required this.currencyCode, + required this.countryCode, + }); + + ///The currency symbol for the locale, e.g. $ for US locale. + String currencySymbol; + + ///The currency code for the locale, e.g. USD for US locale. + String currencyCode; + + ///The country code for the locale, e.g. US for US locale. + String countryCode; + + Object encode() { + return [ + currencySymbol, + currencyCode, + countryCode, + ]; + } + + static SKPriceLocaleMessage decode(Object result) { + result as List; + return SKPriceLocaleMessage( + currencySymbol: result[0]! as String, + currencyCode: result[1]! as String, + countryCode: result[2]! as String, + ); + } +} + +class SKProductDiscountMessage { + SKProductDiscountMessage({ + required this.price, + required this.priceLocale, + required this.numberOfPeriods, + required this.paymentMode, + required this.subscriptionPeriod, + this.identifier, + required this.type, + }); + + String price; + + SKPriceLocaleMessage priceLocale; + + int numberOfPeriods; + + SKProductDiscountPaymentModeMessage paymentMode; + + SKProductSubscriptionPeriodMessage subscriptionPeriod; + + String? identifier; + + SKProductDiscountTypeMessage type; + + Object encode() { + return [ + price, + priceLocale.encode(), + numberOfPeriods, + paymentMode.index, + subscriptionPeriod.encode(), + identifier, + type.index, + ]; + } + + static SKProductDiscountMessage decode(Object result) { + result as List; + return SKProductDiscountMessage( + price: result[0]! as String, + priceLocale: SKPriceLocaleMessage.decode(result[1]! as List), + numberOfPeriods: result[2]! as int, + paymentMode: + SKProductDiscountPaymentModeMessage.values[result[3]! as int], + subscriptionPeriod: SKProductSubscriptionPeriodMessage.decode( + result[4]! as List), + identifier: result[5] as String?, + type: SKProductDiscountTypeMessage.values[result[6]! as int], + ); + } +} + +class SKProductSubscriptionPeriodMessage { + SKProductSubscriptionPeriodMessage({ + required this.numberOfUnits, + required this.unit, + }); + + int numberOfUnits; + + SKSubscriptionPeriodUnitMessage unit; + + Object encode() { + return [ + numberOfUnits, + unit.index, + ]; + } + + static SKProductSubscriptionPeriodMessage decode(Object result) { + result as List; + return SKProductSubscriptionPeriodMessage( + numberOfUnits: result[0]! as int, + unit: SKSubscriptionPeriodUnitMessage.values[result[1]! as int], + ); + } +} + class _InAppPurchaseAPICodec extends StandardMessageCodec { const _InAppPurchaseAPICodec(); @override @@ -276,9 +511,24 @@ class _InAppPurchaseAPICodec extends StandardMessageCodec { } else if (value is SKPaymentTransactionMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is SKStorefrontMessage) { + } else if (value is SKPriceLocaleMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is SKProductDiscountMessage) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is SKProductMessage) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is SKProductSubscriptionPeriodMessage) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is SKProductsResponseMessage) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is SKStorefrontMessage) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -296,6 +546,16 @@ class _InAppPurchaseAPICodec extends StandardMessageCodec { case 131: return SKPaymentTransactionMessage.decode(readValue(buffer)!); case 132: + return SKPriceLocaleMessage.decode(readValue(buffer)!); + case 133: + return SKProductDiscountMessage.decode(readValue(buffer)!); + case 134: + return SKProductMessage.decode(readValue(buffer)!); + case 135: + return SKProductSubscriptionPeriodMessage.decode(readValue(buffer)!); + case 136: + return SKProductsResponseMessage.decode(readValue(buffer)!); + case 137: return SKStorefrontMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -426,4 +686,106 @@ class InAppPurchaseAPI { return; } } + + Future startProductRequest( + List productIdentifiers) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([productIdentifiers]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else if (__pigeon_replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (__pigeon_replyList[0] as SKProductsResponseMessage?)!; + } + } + + Future finishTransaction(Map finishMap) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send([finishMap]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future restoreTransactions(String? applicationUserName) async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = await __pigeon_channel + .send([applicationUserName]) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } + + Future presentCodeRedemptionSheet() async { + const String __pigeon_channelName = + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.presentCodeRedemptionSheet'; + final BasicMessageChannel __pigeon_channel = + BasicMessageChannel( + __pigeon_channelName, + pigeonChannelCodec, + binaryMessenger: __pigeon_binaryMessenger, + ); + final List? __pigeon_replyList = + await __pigeon_channel.send(null) as List?; + if (__pigeon_replyList == null) { + throw _createConnectionError(__pigeon_channelName); + } else if (__pigeon_replyList.length > 1) { + throw PlatformException( + code: __pigeon_replyList[0]! as String, + message: __pigeon_replyList[1] as String?, + details: __pigeon_replyList[2], + ); + } else { + return; + } + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart index 5c2e3c395a35..f8fd85fcc6b0 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart @@ -161,10 +161,7 @@ class SKPaymentQueueWrapper { Future finishTransaction( SKPaymentTransactionWrapper transaction) async { final Map requestMap = transaction.toFinishMap(); - await channel.invokeMethod( - '-[InAppPurchasePlugin finishTransaction:result:]', - requestMap, - ); + await _hostApi.finishTransaction(requestMap); } /// Restore previously purchased transactions. @@ -188,9 +185,7 @@ class SKPaymentQueueWrapper { /// or [`-[SKPayment restoreCompletedTransactionsWithApplicationUsername:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1505992-restorecompletedtransactionswith?language=objc) /// depending on whether the `applicationUserName` is set. Future restoreTransactions({String? applicationUserName}) async { - await channel.invokeMethod( - '-[InAppPurchasePlugin restoreTransactions:result:]', - applicationUserName); + await _hostApi.restoreTransactions(applicationUserName); } /// Present Code Redemption Sheet @@ -200,8 +195,7 @@ class SKPaymentQueueWrapper { /// This method triggers [`-[SKPayment /// presentCodeRedemptionSheet]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3566726-presentcoderedemptionsheet?language=objc) Future presentCodeRedemptionSheet() async { - await channel.invokeMethod( - '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]'); + await _hostApi.presentCodeRedemptionSheet(); } /// Shows the price consent sheet if the user has not yet responded to a diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart index 5eace6fda69e..dfd8c3083fac 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_product_wrapper.dart @@ -5,6 +5,8 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:json_annotation/json_annotation.dart'; + +import '../messages.g.dart'; import 'enum_converters.dart'; // WARNING: Changes to `@JsonSerializable` classes need to be reflected in the @@ -64,6 +66,31 @@ class SkProductResponseWrapper { @override int get hashCode => Object.hash(products, invalidProductIdentifiers); + + /// Convert from [SkProductResponseMessage] to [SkProductResponseWrapper] + static SkProductResponseWrapper convertFromPigeon( + SKProductsResponseMessage msg) { + return SkProductResponseWrapper( + products: msg.products! + .map((SKProductMessage? e) => SKProductWrapper.convertFromPigeon(e!)) + .toList(), + invalidProductIdentifiers: msg.invalidProductIdentifiers != null + ? msg.invalidProductIdentifiers!.cast() + : [], + ); + } + + /// Convert from [SkProductResponseWrapper] to [SkProductResponseWrapper] + @visibleForTesting + static SKProductsResponseMessage convertToPigeon( + SkProductResponseWrapper wrapper) { + return SKProductsResponseMessage( + products: wrapper.products + .map((SKProductWrapper? e) => SKProductWrapper.convertToPigeon(e!)) + .toList(), + invalidProductIdentifiers: + wrapper.invalidProductIdentifiers.cast()); + } } /// Dart wrapper around StoreKit's [SKProductPeriodUnit](https://developer.apple.com/documentation/storekit/skproductperiodunit?language=objc). @@ -88,7 +115,38 @@ enum SKSubscriptionPeriodUnit { /// An interval lasting one year. @JsonValue(3) - year, + year; + + /// Convert from [SKSubscriptionPeriodUnitMessage] to [SKSubscriptionPeriodUnit] + static SKSubscriptionPeriodUnit convertFromPigeon( + SKSubscriptionPeriodUnitMessage msg) { + switch (msg) { + case SKSubscriptionPeriodUnitMessage.day: + return SKSubscriptionPeriodUnit.day; + case SKSubscriptionPeriodUnitMessage.week: + return SKSubscriptionPeriodUnit.week; + case SKSubscriptionPeriodUnitMessage.month: + return SKSubscriptionPeriodUnit.month; + case SKSubscriptionPeriodUnitMessage.year: + return SKSubscriptionPeriodUnit.year; + } + } + + /// Convert from [SKSubscriptionPeriodUnit] to [SKSubscriptionPeriodUnitMessage] + @visibleForTesting + static SKSubscriptionPeriodUnitMessage convertToPigeon( + SKSubscriptionPeriodUnit msg) { + switch (msg) { + case SKSubscriptionPeriodUnit.day: + return SKSubscriptionPeriodUnitMessage.day; + case SKSubscriptionPeriodUnit.week: + return SKSubscriptionPeriodUnitMessage.week; + case SKSubscriptionPeriodUnit.month: + return SKSubscriptionPeriodUnitMessage.month; + case SKSubscriptionPeriodUnit.year: + return SKSubscriptionPeriodUnitMessage.year; + } + } } /// Dart wrapper around StoreKit's [SKProductSubscriptionPeriod](https://developer.apple.com/documentation/storekit/skproductsubscriptionperiod?language=objc). @@ -142,6 +200,23 @@ class SKProductSubscriptionPeriodWrapper { @override int get hashCode => Object.hash(numberOfUnits, unit); + + /// Convert from [SKProductSubscriptionPeriodMessage] to [SKProductSubscriptionPeriodWrapper] + static SKProductSubscriptionPeriodWrapper convertFromPigeon( + SKProductSubscriptionPeriodMessage msg) { + return SKProductSubscriptionPeriodWrapper( + numberOfUnits: msg.numberOfUnits, + unit: SKSubscriptionPeriodUnit.convertFromPigeon(msg.unit)); + } + + /// Convert from [SKProductSubscriptionPeriodWrapper] to [SKProductSubscriptionPeriodMessage] + @visibleForTesting + static SKProductSubscriptionPeriodMessage convertToPigeon( + SKProductSubscriptionPeriodWrapper wrapper) { + return SKProductSubscriptionPeriodMessage( + numberOfUnits: wrapper.numberOfUnits, + unit: SKSubscriptionPeriodUnit.convertToPigeon(wrapper.unit)); + } } /// Dart wrapper around StoreKit's [SKProductDiscountPaymentMode](https://developer.apple.com/documentation/storekit/skproductdiscountpaymentmode?language=objc). @@ -164,7 +239,38 @@ enum SKProductDiscountPaymentMode { /// Unspecified mode. @JsonValue(-1) - unspecified, + unspecified; + + /// Convert from [SKProductDiscountPaymentModeMessage] to [SKProductDiscountPaymentModeWrapper] + static SKProductDiscountPaymentMode convertFromPigeon( + SKProductDiscountPaymentModeMessage msg) { + switch (msg) { + case SKProductDiscountPaymentModeMessage.payAsYouGo: + return SKProductDiscountPaymentMode.payAsYouGo; + case SKProductDiscountPaymentModeMessage.payUpFront: + return SKProductDiscountPaymentMode.payUpFront; + case SKProductDiscountPaymentModeMessage.freeTrial: + return SKProductDiscountPaymentMode.freeTrail; + case SKProductDiscountPaymentModeMessage.unspecified: + return SKProductDiscountPaymentMode.unspecified; + } + } + + /// Convert from [SKProductDiscountPaymentModeMessage] to [SKProductDiscountPaymentMode] + @visibleForTesting + static SKProductDiscountPaymentModeMessage convertToPigeon( + SKProductDiscountPaymentMode wrapper) { + switch (wrapper) { + case SKProductDiscountPaymentMode.payAsYouGo: + return SKProductDiscountPaymentModeMessage.payAsYouGo; + case SKProductDiscountPaymentMode.payUpFront: + return SKProductDiscountPaymentModeMessage.payUpFront; + case SKProductDiscountPaymentMode.freeTrail: + return SKProductDiscountPaymentModeMessage.freeTrial; + case SKProductDiscountPaymentMode.unspecified: + return SKProductDiscountPaymentModeMessage.unspecified; + } + } } /// Dart wrapper around StoreKit's [SKProductDiscountType] @@ -182,7 +288,30 @@ enum SKProductDiscountType { /// A constant indicating the discount type is a promotional offer. @JsonValue(1) - subscription, + subscription; + + /// Convert from [SKProductDiscountTypeMessage] to [SKProductDiscountType] + static SKProductDiscountType convertFromPigeon( + SKProductDiscountTypeMessage msg) { + switch (msg) { + case SKProductDiscountTypeMessage.introductory: + return SKProductDiscountType.introductory; + case SKProductDiscountTypeMessage.subscription: + return SKProductDiscountType.subscription; + } + } + + /// Convert from [SKProductDiscountType] to [SKProductDiscountTypeMessage] + @visibleForTesting + static SKProductDiscountTypeMessage convertToPigeon( + SKProductDiscountType wrapper) { + switch (wrapper) { + case SKProductDiscountType.introductory: + return SKProductDiscountTypeMessage.introductory; + case SKProductDiscountType.subscription: + return SKProductDiscountTypeMessage.subscription; + } + } } /// Dart wrapper around StoreKit's [SKProductDiscount](https://developer.apple.com/documentation/storekit/skproductdiscount?language=objc). @@ -265,6 +394,38 @@ class SKProductDiscountWrapper { @override int get hashCode => Object.hash(price, priceLocale, numberOfPeriods, paymentMode, subscriptionPeriod, identifier, type); + + /// Convert from [SKProductDiscountMessage] to [SKProductDiscountWrapper] + static SKProductDiscountWrapper convertFromPigeon( + SKProductDiscountMessage msg) { + return SKProductDiscountWrapper( + price: msg.price, + priceLocale: SKPriceLocaleWrapper.convertFromPigeon(msg.priceLocale), + numberOfPeriods: msg.numberOfPeriods, + paymentMode: + SKProductDiscountPaymentMode.convertFromPigeon(msg.paymentMode), + subscriptionPeriod: + SKProductSubscriptionPeriodWrapper.convertFromPigeon( + msg.subscriptionPeriod), + identifier: msg.identifier, + type: SKProductDiscountType.convertFromPigeon(msg.type)); + } + + /// Convert from [SKProductDiscountWrapper] to [SKProductDiscountMessage] + @visibleForTesting + static SKProductDiscountMessage convertToPigeon( + SKProductDiscountWrapper wrapper) { + return SKProductDiscountMessage( + price: wrapper.price, + priceLocale: SKPriceLocaleWrapper.convertToPigeon(wrapper.priceLocale), + numberOfPeriods: wrapper.numberOfPeriods, + paymentMode: + SKProductDiscountPaymentMode.convertToPigeon(wrapper.paymentMode), + subscriptionPeriod: SKProductSubscriptionPeriodWrapper.convertToPigeon( + wrapper.subscriptionPeriod), + identifier: wrapper.identifier, + type: SKProductDiscountType.convertToPigeon(wrapper.type)); + } } /// Dart wrapper around StoreKit's [SKProduct](https://developer.apple.com/documentation/storekit/skproduct?language=objc). @@ -383,6 +544,53 @@ class SKProductWrapper { subscriptionPeriod, introductoryPrice, discounts); + + /// Convert from [SKProductMessage] to [SKProductWrapper] + static SKProductWrapper convertFromPigeon(SKProductMessage msg) { + return SKProductWrapper( + productIdentifier: msg.productIdentifier, + localizedTitle: msg.localizedTitle, + localizedDescription: msg.localizedDescription, + priceLocale: SKPriceLocaleWrapper.convertFromPigeon(msg.priceLocale), + price: msg.price, + subscriptionGroupIdentifier: msg.subscriptionGroupIdentifier, + subscriptionPeriod: msg.subscriptionPeriod != null + ? SKProductSubscriptionPeriodWrapper.convertFromPigeon( + msg.subscriptionPeriod!) + : null, + introductoryPrice: msg.introductoryPrice != null + ? SKProductDiscountWrapper.convertFromPigeon(msg.introductoryPrice!) + : null, + discounts: msg.discounts != null + ? msg.discounts! + .map((SKProductDiscountMessage? e) => + SKProductDiscountWrapper.convertFromPigeon(e!)) + .toList() + : []); + } + + /// Convert from [SKProductWrapper] to [SKProductMessage] + static SKProductMessage convertToPigeon(SKProductWrapper wrapper) { + return SKProductMessage( + productIdentifier: wrapper.productIdentifier, + localizedTitle: wrapper.localizedTitle, + localizedDescription: wrapper.localizedDescription, + priceLocale: SKPriceLocaleWrapper.convertToPigeon(wrapper.priceLocale), + price: wrapper.price, + subscriptionGroupIdentifier: wrapper.subscriptionGroupIdentifier, + subscriptionPeriod: wrapper.subscriptionPeriod != null + ? SKProductSubscriptionPeriodWrapper.convertToPigeon( + wrapper.subscriptionPeriod!) + : null, + introductoryPrice: wrapper.introductoryPrice != null + ? SKProductDiscountWrapper.convertToPigeon( + wrapper.introductoryPrice!) + : null, + discounts: wrapper.discounts + .map((SKProductDiscountWrapper? e) => + SKProductDiscountWrapper.convertToPigeon(e!)) + .toList()); + } } /// Object that indicates the locale of the price @@ -442,4 +650,21 @@ class SKPriceLocaleWrapper { @override int get hashCode => Object.hash(currencySymbol, currencyCode); + + /// Convert from [SKPriceLocaleMessage] to [SKPriceLocaleWrapper] + static SKPriceLocaleWrapper convertFromPigeon(SKPriceLocaleMessage msg) { + return SKPriceLocaleWrapper( + currencySymbol: msg.currencySymbol, + currencyCode: msg.currencyCode, + countryCode: msg.countryCode); + } + + /// Convert from [SKPriceLocaleWrapper] to [SKPriceLocaleMessage] + @visibleForTesting + static SKPriceLocaleMessage convertToPigeon(SKPriceLocaleWrapper msg) { + return SKPriceLocaleMessage( + currencySymbol: msg.currencySymbol, + currencyCode: msg.currencyCode, + countryCode: msg.countryCode); + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart index d59f66fce2c9..2dc2ad7baef2 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/lib/src/store_kit_wrappers/sk_request_maker.dart @@ -7,8 +7,11 @@ import 'dart:async'; import 'package:flutter/services.dart'; import '../channel.dart'; +import '../messages.g.dart'; import 'sk_product_wrapper.dart'; +InAppPurchaseAPI _hostApi = InAppPurchaseAPI(); + /// A request maker that handles all the requests made by SKRequest subclasses. /// /// There are multiple [SKRequest](https://developer.apple.com/documentation/storekit/skrequest?language=objc) subclasses handling different requests in the `StoreKit` with multiple delegate methods, @@ -26,18 +29,18 @@ class SKRequestMaker { /// A [PlatformException] is thrown if the platform code making the request fails. Future startProductRequest( List productIdentifiers) async { - final Map? productResponseMap = - await channel.invokeMapMethod( - '-[InAppPurchasePlugin startProductRequest:result:]', - productIdentifiers, - ); - if (productResponseMap == null) { + final SKProductsResponseMessage productResponsePigeon = + await _hostApi.startProductRequest(productIdentifiers); + + // should products be null or [] ? + if (productResponsePigeon.products == null) { throw PlatformException( code: 'storekit_no_response', message: 'StoreKit: Failed to get response from platform.', ); } - return SkProductResponseWrapper.fromJson(productResponseMap); + + return SkProductResponseWrapper.convertFromPigeon(productResponsePigeon); } /// Uses [SKReceiptRefreshRequest](https://developer.apple.com/documentation/storekit/skreceiptrefreshrequest?language=objc) to request a new receipt. diff --git a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart index d9d43cd7eff2..9ada32ed2e0d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/pigeons/messages.dart @@ -22,15 +22,10 @@ class SKPaymentTransactionMessage { }); final SKPaymentMessage payment; - final SKPaymentTransactionStateMessage transactionState; - final SKPaymentTransactionMessage? originalTransaction; - final double? transactionTimeStamp; - final String? transactionIdentifier; - final SKErrorMessage? error; } @@ -83,15 +78,10 @@ class SKPaymentMessage { }); final String productIdentifier; - final String? applicationUsername; - final String? requestData; - final int quantity; - final bool simulatesAskToBuyInSandbox; - final SKPaymentDiscountMessage? paymentDiscount; } @@ -130,6 +120,109 @@ class SKStorefrontMessage { final String identifier; } +class SKProductsResponseMessage { + const SKProductsResponseMessage( + {required this.products, required this.invalidProductIdentifiers}); + final List? products; + final List? invalidProductIdentifiers; +} + +class SKProductMessage { + const SKProductMessage( + {required this.productIdentifier, + required this.localizedTitle, + required this.localizedDescription, + required this.priceLocale, + required this.price, + this.subscriptionGroupIdentifier, + this.subscriptionPeriod, + this.introductoryPrice, + this.discounts}); + + final String productIdentifier; + final String localizedTitle; + final String localizedDescription; + final SKPriceLocaleMessage priceLocale; + final String? subscriptionGroupIdentifier; + final String price; + final SKProductSubscriptionPeriodMessage? subscriptionPeriod; + final SKProductDiscountMessage? introductoryPrice; + final List? discounts; +} + +class SKPriceLocaleMessage { + SKPriceLocaleMessage({ + required this.currencySymbol, + required this.currencyCode, + required this.countryCode, + }); + + ///The currency symbol for the locale, e.g. $ for US locale. + final String currencySymbol; + + ///The currency code for the locale, e.g. USD for US locale. + final String currencyCode; + + ///The country code for the locale, e.g. US for US locale. + final String countryCode; +} + +class SKProductDiscountMessage { + const SKProductDiscountMessage( + {required this.price, + required this.priceLocale, + required this.numberOfPeriods, + required this.paymentMode, + required this.subscriptionPeriod, + required this.identifier, + required this.type}); + + final String price; + final SKPriceLocaleMessage priceLocale; + final int numberOfPeriods; + final SKProductDiscountPaymentModeMessage paymentMode; + final SKProductSubscriptionPeriodMessage subscriptionPeriod; + final String? identifier; + final SKProductDiscountTypeMessage type; +} + +enum SKProductDiscountTypeMessage { + /// A constant indicating the discount type is an introductory offer. + introductory, + + /// A constant indicating the discount type is a promotional offer. + subscription, +} + +enum SKProductDiscountPaymentModeMessage { + /// Allows user to pay the discounted price at each payment period. + payAsYouGo, + + /// Allows user to pay the discounted price upfront and receive the product for the rest of time that was paid for. + payUpFront, + + /// User pays nothing during the discounted period. + freeTrial, + + /// Unspecified mode. + unspecified, +} + +class SKProductSubscriptionPeriodMessage { + SKProductSubscriptionPeriodMessage( + {required this.numberOfUnits, required this.unit}); + + final int numberOfUnits; + final SKSubscriptionPeriodUnitMessage unit; +} + +enum SKSubscriptionPeriodUnitMessage { + day, + week, + month, + year, +} + @HostApi(dartHostTestHandler: 'TestInAppPurchaseApi') abstract class InAppPurchaseAPI { /// Returns if the current device is able to make payments @@ -140,4 +233,14 @@ abstract class InAppPurchaseAPI { SKStorefrontMessage storefront(); void addPayment(Map paymentMap); + + @async + SKProductsResponseMessage startProductRequest( + List productIdentifiers); + + void finishTransaction(Map finishMap); + + void restoreTransactions(String? applicationUserName); + + void presentCodeRedemptionSheet(); } 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 fc7fde8d6220..97fa1dbd1869 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.9 +version: 0.3.10 environment: sdk: ^3.2.3 diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart index d26840ad8c07..48db0847bf30 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/fakes/fake_storekit_platform.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:io'; - import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_storekit/in_app_purchase_storekit.dart'; @@ -124,42 +122,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { Future onMethodCall(MethodCall call) { switch (call.method) { - case '-[InAppPurchasePlugin startProductRequest:result:]': - if (queryProductException != null) { - throw queryProductException!; - } - final List productIDS = - List.castFrom(call.arguments as List); - final List invalidFound = []; - final List products = []; - for (final String productID in productIDS) { - if (!validProductIDs.contains(productID)) { - invalidFound.add(productID); - } else { - products.add(validProducts[productID]!); - } - } - final SkProductResponseWrapper response = SkProductResponseWrapper( - products: products, invalidProductIdentifiers: invalidFound); - return Future>.value( - buildProductResponseMap(response)); - case '-[InAppPurchasePlugin restoreTransactions:result:]': - if (restoreException != null) { - throw restoreException!; - } - if (testRestoredError != null) { - InAppPurchaseStoreKitPlatform.observer - .restoreCompletedTransactionsFailed(error: testRestoredError!); - return Future.sync(() {}); - } - if (!testRestoredTransactionsNull) { - InAppPurchaseStoreKitPlatform.observer - .updatedTransactions(transactions: transactionList); - } - InAppPurchaseStoreKitPlatform.observer - .paymentQueueRestoreCompletedTransactionsFinished(); - - return Future.sync(() {}); case '-[InAppPurchasePlugin retrieveReceiptData:result:]': if (receiptData != null) { return Future.value(receiptData!); @@ -169,53 +131,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { case '-[InAppPurchasePlugin refreshReceipt:result:]': receiptData = 'refreshed receipt data'; return Future.sync(() {}); - case '-[InAppPurchasePlugin addPayment:result:]': - final Map arguments = _getArgumentDictionary(call); - final String id = arguments['productIdentifier']! as String; - final int quantity = arguments['quantity']! as int; - - // Keep the received paymentDiscount parameter when testing payment with discount. - if (arguments['applicationUsername']! == 'userWithDiscount') { - final Map? discountArgument = - arguments['paymentDiscount'] as Map?; - if (discountArgument != null) { - discountReceived = discountArgument.cast(); - } else { - discountReceived = {}; - } - } - - final SKPaymentTransactionWrapper transaction = - createPendingTransaction(id, quantity: quantity); - transactionList.add(transaction); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transaction]); - sleep(const Duration(milliseconds: 30)); - if (testTransactionFail) { - final SKPaymentTransactionWrapper transactionFailed = - createFailedTransaction(id, quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionFailed]); - } else if (testTransactionCancel > 0) { - final SKPaymentTransactionWrapper transactionCanceled = - createCanceledTransaction(id, testTransactionCancel, - quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionCanceled]); - } else { - final SKPaymentTransactionWrapper transactionFinished = - createPurchasedTransaction( - id, transaction.transactionIdentifier ?? '', - quantity: quantity); - InAppPurchaseStoreKitPlatform.observer.updatedTransactions( - transactions: [transactionFinished]); - } - case '-[InAppPurchasePlugin finishTransaction:result:]': - final Map arguments = _getArgumentDictionary(call); - finishedTransactions.add(createPurchasedTransaction( - arguments['productIdentifier']! as String, - arguments['transactionIdentifier']! as String, - quantity: transactionList.first.payment.quantity)); case '-[SKPaymentQueue startObservingTransactionQueue]': queueIsActive = true; case '-[SKPaymentQueue stopObservingTransactionQueue]': @@ -224,14 +139,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { return Future.sync(() {}); } - /// Returns the arguments of [call] as typed string-keyed Map. - /// - /// This does not do any type validation, so is only safe to call if the - /// arguments are known to be a map. - Map _getArgumentDictionary(MethodCall call) { - return (call.arguments as Map).cast(); - } - @override bool canMakePayments() { return true; @@ -288,4 +195,55 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { List transactions() { throw UnimplementedError(); } + + @override + void finishTransaction(Map finishMap) { + finishedTransactions.add(createPurchasedTransaction( + finishMap['productIdentifier']!, finishMap['transactionIdentifier']!, + quantity: transactionList.first.payment.quantity)); + } + + @override + void presentCodeRedemptionSheet() {} + + @override + void restoreTransactions(String? applicationUserName) { + if (restoreException != null) { + throw restoreException!; + } + if (testRestoredError != null) { + InAppPurchaseStoreKitPlatform.observer + .restoreCompletedTransactionsFailed(error: testRestoredError!); + return; + } + if (!testRestoredTransactionsNull) { + InAppPurchaseStoreKitPlatform.observer + .updatedTransactions(transactions: transactionList); + } + InAppPurchaseStoreKitPlatform.observer + .paymentQueueRestoreCompletedTransactionsFinished(); + } + + @override + Future startProductRequest( + List productIdentifiers) { + if (queryProductException != null) { + throw queryProductException!; + } + final List productIDS = productIdentifiers; + final List invalidFound = []; + final List products = []; + for (final String? productID in productIDS) { + if (!validProductIDs.contains(productID)) { + invalidFound.add(productID!); + } else { + products.add(validProducts[productID]!); + } + } + final SkProductResponseWrapper response = SkProductResponseWrapper( + products: products, invalidProductIdentifiers: invalidFound); + + return Future.value( + SkProductResponseWrapper.convertToPigeon(response)); + } } diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart new file mode 100644 index 000000000000..36881eb07986 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/pigeon_converter_test.dart @@ -0,0 +1,98 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:in_app_purchase_storekit/src/messages.g.dart'; +import 'package:in_app_purchase_storekit/store_kit_wrappers.dart'; + +void main() { + final SKPriceLocaleWrapper locale = SKPriceLocaleWrapper( + currencySymbol: r'$', currencyCode: 'USD', countryCode: 'USA'); + + final SKProductSubscriptionPeriodWrapper subPeriod = + SKProductSubscriptionPeriodWrapper( + numberOfUnits: 1, unit: SKSubscriptionPeriodUnit.month); + + final SKProductDiscountWrapper discount = SKProductDiscountWrapper( + price: '0.99', + priceLocale: locale, + numberOfPeriods: 1, + paymentMode: SKProductDiscountPaymentMode.payUpFront, + subscriptionPeriod: subPeriod, + identifier: 'discount', + type: SKProductDiscountType.subscription); + + final SKProductWrapper product = SKProductWrapper( + productIdentifier: 'fake_product', + localizedTitle: 'title', + localizedDescription: 'description', + priceLocale: locale, + price: '3.99', + subscriptionGroupIdentifier: 'sub_group', + discounts: [discount]); + + final SkProductResponseWrapper productResponse = SkProductResponseWrapper( + products: [product], + invalidProductIdentifiers: const ['invalid_identifier']); + + test('test SKPriceLocale pigeon converters', () { + final SKPriceLocaleMessage msg = + SKPriceLocaleWrapper.convertToPigeon(locale); + expect(msg.currencySymbol, r'$'); + expect(msg.currencyCode, 'USD'); + expect(msg.countryCode, 'USA'); + + final SKPriceLocaleWrapper convertedWrapper = + SKPriceLocaleWrapper.convertFromPigeon(msg); + expect(convertedWrapper, locale); + }); + + test('test SKProductSubscription pigeon converters', () { + final SKProductSubscriptionPeriodMessage msg = + SKProductSubscriptionPeriodWrapper.convertToPigeon(subPeriod); + expect(msg.unit, SKSubscriptionPeriodUnitMessage.month); + expect(msg.numberOfUnits, 1); + final SKProductSubscriptionPeriodWrapper convertedWrapper = + SKProductSubscriptionPeriodWrapper.convertFromPigeon(msg); + expect(convertedWrapper, subPeriod); + }); + + test('test SKProductDiscount pigeon converters', () { + final SKProductDiscountMessage msg = + SKProductDiscountWrapper.convertToPigeon(discount); + expect(msg.price, '0.99'); + expect(msg.numberOfPeriods, 1); + expect(msg.paymentMode, SKProductDiscountPaymentModeMessage.payUpFront); + expect(msg.identifier, 'discount'); + expect(msg.type, SKProductDiscountTypeMessage.subscription); + + final SKProductDiscountWrapper convertedWrapper = + SKProductDiscountWrapper.convertFromPigeon(msg); + expect(convertedWrapper, discount); + }); + + test('test SKProduct pigeon converters', () { + final SKProductMessage msg = SKProductWrapper.convertToPigeon(product); + expect(msg.productIdentifier, 'fake_product'); + expect(msg.localizedTitle, 'title'); + expect(msg.localizedDescription, 'description'); + expect(msg.price, '3.99'); + expect(msg.discounts?.length, 1); + + final SKProductWrapper convertedWrapper = + SKProductWrapper.convertFromPigeon(msg); + expect(convertedWrapper, product); + }); + + test('test SKProductResponse pigeon converters', () { + final SKProductsResponseMessage msg = + SkProductResponseWrapper.convertToPigeon(productResponse); + expect(msg.products?.length, 1); + expect(msg.invalidProductIdentifiers, ['invalid_identifier']); + + final SkProductResponseWrapper convertedWrapper = + SkProductResponseWrapper.convertFromPigeon(msg); + expect(convertedWrapper, productResponse); + }); +} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart index 44228ebb523b..704be83c8e25 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_methodchannel_apis_test.dart @@ -228,13 +228,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { Future onMethodCall(MethodCall call) { switch (call.method) { // request makers - case '-[InAppPurchasePlugin startProductRequest:result:]': - startProductRequestParam = call.arguments as List; - if (getProductRequestFailTest) { - return Future.value(); - } - return Future>.value( - buildProductResponseMap(dummyProductResponseWrapper)); case '-[InAppPurchasePlugin refreshReceipt:result:]': refreshReceipt++; refreshReceiptParam = Map.castFrom( @@ -246,16 +239,6 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { throw Exception('some arbitrary error'); } return Future.value('receipt data'); - case '-[InAppPurchasePlugin finishTransaction:result:]': - transactionsFinished.add( - Map.from(call.arguments as Map)); - return Future.sync(() {}); - case '-[InAppPurchasePlugin restoreTransactions:result:]': - applicationNameHasTransactionRestored = call.arguments as String; - return Future.sync(() {}); - case '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]': - presentCodeRedemption = true; - return Future.sync(() {}); case '-[SKPaymentQueue startObservingTransactionQueue]': queueIsActive = true; return Future.sync(() {}); @@ -295,6 +278,32 @@ class FakeStoreKitPlatform implements TestInAppPurchaseApi { @override List transactions() => [dummyTransactionMessage]; + + @override + void finishTransaction(Map finishMap) { + transactionsFinished.add(Map.from(finishMap)); + } + + @override + void presentCodeRedemptionSheet() { + presentCodeRedemption = true; + } + + @override + void restoreTransactions(String? applicationUserName) { + applicationNameHasTransactionRestored = applicationUserName!; + } + + @override + Future startProductRequest( + List productIdentifiers) { + startProductRequestParam = productIdentifiers; + if (getProductRequestFailTest) { + return Future.value( + SKProductsResponseMessage()); + } + return Future.value(dummyProductResponseMessage); + } } class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {} diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart index ad32a0a5c14a..59022aa2fd24 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/store_kit_wrappers/sk_test_stub_objects.dart @@ -62,6 +62,12 @@ final SKPriceLocaleWrapper dollarLocale = SKPriceLocaleWrapper( countryCode: 'US', ); +final SKPriceLocaleMessage dollarLocaleMessage = SKPriceLocaleMessage( + currencySymbol: r'$', + currencyCode: 'USD', + countryCode: 'US', +); + final SKPriceLocaleWrapper noSymbolLocale = SKPriceLocaleWrapper( currencySymbol: '', currencyCode: 'EUR', @@ -74,6 +80,12 @@ final SKProductSubscriptionPeriodWrapper dummySubscription = unit: SKSubscriptionPeriodUnit.month, ); +final SKProductSubscriptionPeriodMessage dummySubscriptionMessage = + SKProductSubscriptionPeriodMessage( + numberOfUnits: 1, + unit: SKSubscriptionPeriodUnitMessage.month, +); + final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( price: '1.0', priceLocale: dollarLocale, @@ -84,6 +96,16 @@ final SKProductDiscountWrapper dummyDiscount = SKProductDiscountWrapper( type: SKProductDiscountType.subscription, ); +final SKProductDiscountMessage dummyDiscountMessage = SKProductDiscountMessage( + price: '1.0', + priceLocale: dollarLocaleMessage, + numberOfPeriods: 1, + paymentMode: SKProductDiscountPaymentModeMessage.payUpFront, + subscriptionPeriod: dummySubscriptionMessage, + identifier: 'id', + type: SKProductDiscountTypeMessage.subscription, +); + final SKProductDiscountWrapper dummyDiscountMissingIdentifierAndType = SKProductDiscountWrapper( price: '1.0', @@ -107,12 +129,30 @@ final SKProductWrapper dummyProductWrapper = SKProductWrapper( discounts: [dummyDiscount], ); +final SKProductMessage dummyProductMessage = SKProductMessage( + productIdentifier: 'id', + localizedTitle: 'title', + localizedDescription: 'description', + priceLocale: dollarLocaleMessage, + subscriptionGroupIdentifier: 'com.group', + price: '1.0', + subscriptionPeriod: dummySubscriptionMessage, + introductoryPrice: dummyDiscountMessage, + discounts: [dummyDiscountMessage], +); + final SkProductResponseWrapper dummyProductResponseWrapper = SkProductResponseWrapper( products: [dummyProductWrapper], invalidProductIdentifiers: const ['123'], ); +final SKProductsResponseMessage dummyProductResponseMessage = + SKProductsResponseMessage( + products: [dummyProductMessage], + invalidProductIdentifiers: const ['123'], +); + Map buildLocaleMap(SKPriceLocaleWrapper local) { return { 'currencySymbol': local.currencySymbol, diff --git a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart index a640a188c43c..80debcebb79d 100644 --- a/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart +++ b/packages/in_app_purchase/in_app_purchase_storekit/test/test_api.g.dart @@ -26,9 +26,24 @@ class _TestInAppPurchaseApiCodec extends StandardMessageCodec { } else if (value is SKPaymentTransactionMessage) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is SKStorefrontMessage) { + } else if (value is SKPriceLocaleMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); + } else if (value is SKProductDiscountMessage) { + buffer.putUint8(133); + writeValue(buffer, value.encode()); + } else if (value is SKProductMessage) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); + } else if (value is SKProductSubscriptionPeriodMessage) { + buffer.putUint8(135); + writeValue(buffer, value.encode()); + } else if (value is SKProductsResponseMessage) { + buffer.putUint8(136); + writeValue(buffer, value.encode()); + } else if (value is SKStorefrontMessage) { + buffer.putUint8(137); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -46,6 +61,16 @@ class _TestInAppPurchaseApiCodec extends StandardMessageCodec { case 131: return SKPaymentTransactionMessage.decode(readValue(buffer)!); case 132: + return SKPriceLocaleMessage.decode(readValue(buffer)!); + case 133: + return SKProductDiscountMessage.decode(readValue(buffer)!); + case 134: + return SKProductMessage.decode(readValue(buffer)!); + case 135: + return SKProductSubscriptionPeriodMessage.decode(readValue(buffer)!); + case 136: + return SKProductsResponseMessage.decode(readValue(buffer)!); + case 137: return SKStorefrontMessage.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -68,6 +93,15 @@ abstract class TestInAppPurchaseApi { void addPayment(Map paymentMap); + Future startProductRequest( + List productIdentifiers); + + void finishTransaction(Map finishMap); + + void restoreTransactions(String? applicationUserName); + + void presentCodeRedemptionSheet(); + static void setup(TestInAppPurchaseApi? api, {BinaryMessenger? binaryMessenger}) { { @@ -178,5 +212,124 @@ abstract class TestInAppPurchaseApi { }); } } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest was null.'); + final List args = (message as List?)!; + final List? arg_productIdentifiers = + (args[0] as List?)?.cast(); + assert(arg_productIdentifiers != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.startProductRequest was null, expected non-null List.'); + try { + final SKProductsResponseMessage output = + await api.startProductRequest(arg_productIdentifiers!); + return [output]; + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null.'); + final List args = (message as List?)!; + final Map? arg_finishMap = + (args[0] as Map?)?.cast(); + assert(arg_finishMap != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.finishTransaction was null, expected non-null Map.'); + try { + api.finishTransaction(arg_finishMap!); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.restoreTransactions was null.'); + final List args = (message as List?)!; + final String? arg_applicationUserName = (args[0] as String?); + try { + api.restoreTransactions(arg_applicationUserName); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } + { + final BasicMessageChannel __pigeon_channel = BasicMessageChannel< + Object?>( + 'dev.flutter.pigeon.in_app_purchase_storekit.InAppPurchaseAPI.presentCodeRedemptionSheet', + pigeonChannelCodec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger + .setMockDecodedMessageHandler(__pigeon_channel, + (Object? message) async { + try { + api.presentCodeRedemptionSheet(); + return wrapResponse(empty: true); + } on PlatformException catch (e) { + return wrapResponse(error: e); + } catch (e) { + return wrapResponse( + error: PlatformException(code: 'error', message: e.toString())); + } + }); + } + } } }