Skip to content

Commit

Permalink
Convert startProductRequest(), finishTransaction(), restoreTransactio…
Browse files Browse the repository at this point in the history
…ns(), presentCodeRedemptionSheet() to pigeon (#6032)

Part 2 of flutter/flutter#117910

This PR converts startProductRequest(), finishTransaction(),
restoreTransactions(), presentCodeRedemptionSheet() to pigeon, as well
as add all remaining converts to and from pigeons for SK objects.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] All existing and new tests are passing.
  • Loading branch information
LouiseHsu committed Feb 13, 2024
1 parent 0d9fea6 commit 9385bbb
Show file tree
Hide file tree
Showing 19 changed files with 1,822 additions and 223 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.3.10

* Converts `startProductRequest()`, `finishTransaction()`, `restoreTransactions()`, `presentCodeRedemptionSheet()` to pigeon.

## 0.3.9

* Converts `storefront()`, `transactions()`, `addPayment()`, `canMakePayment` to pigeon.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -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<SKProductDiscount *> *skProductDiscounts = product.discounts;
NSMutableArray<SKProductDiscountMessage *> *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<SKProduct *> *skProducts = productsResponse.products;
NSMutableArray<SKProductMessage *> *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
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,7 @@ - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)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];
Expand Down Expand Up @@ -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<NSString *> *)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];

This comment has been minimized.

Copy link
@liuxiaoxinxin

liuxiaoxinxin Feb 21, 2024

weakSelf should be used here

}
result([FIAObjectTranslator getMapFromSKProductsResponse:response]);

completion([FIAObjectTranslator convertProductsResponseToPigeon:response], error);
[weakSelf.requestHandlers removeObject:handler];
}];
}
Expand Down Expand Up @@ -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<NSString *, NSString *> *)finishMap
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSString *transactionIdentifier = [finishMap objectForKey:@"transactionIdentifier"];
NSString *productIdentifier = [finishMap objectForKey:@"productIdentifier"];

NSArray<SKPaymentTransaction *> *pendingTransactions =
[self.paymentQueueHandler getUnfinishedTransactions];
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 9385bbb

Please sign in to comment.