Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert startProductRequest(), finishTransaction(), restoreTransactions(), presentCodeRedemptionSheet() to pigeon #6032

Merged
merged 17 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is probably not related to your PR - is this format iOS only? It's very objc-specific. do we intend to change that after migrating to swift?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this entire code block/fcn will be removed after the pigeon migration so it should be fine

[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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This return got lost. You'll want to short circuit any of the following logic. In order to do that you'll have to call the completion handler then 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

}
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];
}];
}
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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is this error used?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all generated pigeon functions have like an extra "error" param, I think it gets passed back to the dart side automatically

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to call out how these calls have disappears now. In the past, failing to call these created bugs, you've eliminated that risk now 👍

}
#endif
}

- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result {
FlutterError *error = nil;
Expand Down
Loading