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

[in_app_purchase] Convert storefront(), transactions(), canMakePayment(), and addPayment() to pigeon #5910

Merged
merged 33 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
451365d
wip
LouiseHsu Jan 3, 2024
942a623
wip
LouiseHsu Jan 3, 2024
90ff466
Add pigeon converters
LouiseHsu Jan 10, 2024
020e024
more converters yay
LouiseHsu Jan 11, 2024
82670b1
boop
LouiseHsu Jan 12, 2024
0b110d7
canMakePayment, transactions, storefront, addPayment
LouiseHsu Jan 17, 2024
2c9c545
comments
LouiseHsu Jan 17, 2024
9a085ba
formatting
LouiseHsu Jan 18, 2024
5ddd517
pr updates
LouiseHsu Jan 18, 2024
7a47ab8
analyze
LouiseHsu Jan 18, 2024
d434931
fix tests
LouiseHsu Jan 19, 2024
427181a
changelog, silence warnings
LouiseHsu Jan 20, 2024
1585d3a
format, more tests, fix converters
LouiseHsu Jan 23, 2024
c5610c7
pubspec, failing tests
LouiseHsu Jan 23, 2024
4c0672c
pubspec?
LouiseHsu Jan 23, 2024
7c68126
pubspec?
LouiseHsu Jan 23, 2024
56b8bc8
pubspec!!
LouiseHsu Jan 23, 2024
020623e
pubspec :(
LouiseHsu Jan 23, 2024
8fba660
p u b s p e c
LouiseHsu Jan 23, 2024
bc666b8
analyze, shifted methods around for clearer diffs
LouiseHsu Jan 23, 2024
3279b2b
license
LouiseHsu Jan 23, 2024
fb7f98c
Merge branch 'main' into in_app_purchase_pigeon_conversion
LouiseHsu Jan 23, 2024
208844e
merge conflict
LouiseHsu Jan 23, 2024
eb3320c
changelog
LouiseHsu Jan 23, 2024
91554c6
pr comments
LouiseHsu Jan 25, 2024
8f8e242
fix tests
LouiseHsu Jan 25, 2024
0442eb5
oops
LouiseHsu Jan 25, 2024
d2f96fc
license
LouiseHsu Jan 25, 2024
b3a8314
encode userInfo properly
LouiseHsu Jan 26, 2024
4629236
oops
LouiseHsu Jan 26, 2024
b4e1a5f
format
LouiseHsu Jan 29, 2024
082ad70
pr comments
LouiseHsu Jan 30, 2024
3e53e09
Merge branch 'main' into in_app_purchase_pigeon_conversion
LouiseHsu Jan 30, 2024
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,5 +1,6 @@
## NEXT
## 0.3.9

* Converts `storefront()`, `transactions()`, `addPayment()`, `canMakePayment` to pigeon.
* Updates minimum iOS version to 12.0 and minimum Flutter version to 3.16.6.

## 0.3.8+1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
#import "messages.g.h"

NS_ASSUME_NONNULL_BEGIN

Expand Down Expand Up @@ -56,7 +57,19 @@ NS_ASSUME_NONNULL_BEGIN
withError:(NSString *_Nullable *_Nullable)error
API_AVAILABLE(ios(12.2));

+ (nullable SKPaymentTransactionMessage *)convertTransactionToPigeon:
(nullable SKPaymentTransaction *)transaction;

+ (nullable SKStorefrontMessage *)convertStorefrontToPigeon:(nullable SKStorefront *)storefront
API_AVAILABLE(ios(13.0));

+ (nullable SKPaymentDiscountMessage *)convertPaymentDiscountToPigeon:
(nullable SKPaymentDiscount *)discount API_AVAILABLE(ios(12.2));

+ (nullable SKPaymentMessage *)convertPaymentToPigeon:(nullable SKPayment *)payment
API_AVAILABLE(ios(12.2));

+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(NSError *)error;
@end
;

NS_ASSUME_NONNULL_END
Original file line number Diff line number Diff line change
Expand Up @@ -288,4 +288,92 @@ + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map
return discount;
}

+ (nullable SKPaymentTransactionMessage *)convertTransactionToPigeon:
(nullable SKPaymentTransaction *)transaction API_AVAILABLE(ios(12.2)) {
if (!transaction) {
return nil;
}
SKPaymentTransactionMessage *msg = [SKPaymentTransactionMessage
makeWithPayment:[self convertPaymentToPigeon:transaction.payment]
transactionState:[self convertTransactionStateToPigeon:transaction.transactionState]
originalTransaction:transaction.originalTransaction
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need to null check, since this method returns a nil value if it is null already?

? [self convertTransactionToPigeon:transaction.originalTransaction]
: nil
transactionTimeStamp:[NSNumber numberWithDouble:[transaction.transactionDate
timeIntervalSince1970]]
transactionIdentifier:transaction.transactionIdentifier
error:[self convertSKErrorToPigeon:transaction.error]];
return msg;
}

+ (nullable SKErrorMessage *)convertSKErrorToPigeon:(NSError *)error {
NSMutableDictionary *userInfo = [NSMutableDictionary new];
for (NSErrorUserInfoKey key in error.userInfo) {
id value = error.userInfo[key];
userInfo[key] = [FIAObjectTranslator encodeNSErrorUserInfo:value];
}

SKErrorMessage *msg = [SKErrorMessage makeWithCode:error.code
domain:error.domain
userInfo:userInfo];
return msg;
}

+ (SKPaymentTransactionStateMessage)convertTransactionStateToPigeon:
(SKPaymentTransactionState)state {
switch (state) {
case SKPaymentTransactionStatePurchasing:
return SKPaymentTransactionStateMessagePurchasing;
case SKPaymentTransactionStatePurchased:
return SKPaymentTransactionStateMessagePurchased;
case SKPaymentTransactionStateFailed:
return SKPaymentTransactionStateMessageFailed;
case SKPaymentTransactionStateRestored:
return SKPaymentTransactionStateMessageRestored;
case SKPaymentTransactionStateDeferred:
return SKPaymentTransactionStateMessageDeferred;
}
}

+ (nullable SKPaymentMessage *)convertPaymentToPigeon:(nullable SKPayment *)payment
API_AVAILABLE(ios(12.2)) {
if (!payment) {
return nil;
}
SKPaymentMessage *msg = [SKPaymentMessage
makeWithProductIdentifier:payment.productIdentifier
applicationUsername:payment.applicationUsername
requestData:[[NSString alloc] initWithData:payment.requestData
encoding:NSUTF8StringEncoding]
quantity:payment.quantity
simulatesAskToBuyInSandbox:payment.simulatesAskToBuyInSandbox
paymentDiscount:[self convertPaymentDiscountToPigeon:payment.paymentDiscount]];
return msg;
}

+ (nullable SKPaymentDiscountMessage *)convertPaymentDiscountToPigeon:
(nullable SKPaymentDiscount *)discount API_AVAILABLE(ios(12.2)) {
if (!discount) {
return nil;
}
SKPaymentDiscountMessage *msg =
[SKPaymentDiscountMessage makeWithIdentifier:discount.identifier
keyIdentifier:discount.keyIdentifier
nonce:[discount.nonce UUIDString]
signature:discount.signature
timestamp:[discount.timestamp intValue]];

return msg;
}

+ (nullable SKStorefrontMessage *)convertStorefrontToPigeon:(nullable SKStorefront *)storefront
API_AVAILABLE(ios(13.0)) {
if (!storefront) {
return nil;
}
SKStorefrontMessage *msg = [SKStorefrontMessage makeWithCountryCode:storefront.countryCode
identifier:storefront.identifier];
return msg;
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
#else
#import <Flutter/Flutter.h>
#endif
#import "messages.g.h"

@class FIAPaymentQueueHandler;
@class FIAPReceiptManager;

@interface InAppPurchasePlugin : NSObject <FlutterPlugin>
@interface InAppPurchasePlugin : NSObject <FlutterPlugin, InAppPurchaseAPI>

@property(strong, nonatomic) FIAPaymentQueueHandler *paymentQueueHandler;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,11 @@ + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase"
binaryMessenger:[registrar messenger]];

InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar];
[registrar addMethodCallDelegate:instance channel:channel];
[registrar addApplicationDelegate:instance];
SetUpInAppPurchaseAPI(registrar.messenger, instance);
}

- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager {
Expand Down Expand Up @@ -85,16 +88,8 @@ - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar
}

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if ([@"-[SKPaymentQueue canMakePayments:]" isEqualToString:call.method]) {
[self canMakePayments:result];
} else if ([@"-[SKPaymentQueue transactions]" isEqualToString:call.method]) {
[self getPendingTransactions:result];
} else if ([@"-[SKPaymentQueue storefront]" isEqualToString:call.method]) {
[self getStorefront:result];
} else if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) {
Comment on lines -90 to -94
Copy link
Member

Choose a reason for hiding this comment

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

nice!

if ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) {
[self handleProductRequestMethodCall:call result:result];
} else if ([@"-[InAppPurchasePlugin addPayment:result:]" isEqualToString:call.method]) {
[self addPayment:call result:result];
} else if ([@"-[InAppPurchasePlugin finishTransaction:result:]" isEqualToString:call.method]) {
[self finishTransaction:call result:result];
} else if ([@"-[InAppPurchasePlugin restoreTransactions:result:]" isEqualToString:call.method]) {
Expand Down Expand Up @@ -127,34 +122,29 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result
}
}

- (void)canMakePayments:(FlutterResult)result {
result(@([SKPaymentQueue canMakePayments]));
- (nullable NSNumber *)canMakePaymentsWithError:
(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
return @([SKPaymentQueue canMakePayments]);
}

- (void)getPendingTransactions:(FlutterResult)result {
- (nullable NSArray<SKPaymentTransactionMessage *> *)transactionsWithError:
(FlutterError *_Nullable *_Nonnull)error {
NSArray<SKPaymentTransaction *> *transactions =
[self.paymentQueueHandler getUnfinishedTransactions];
NSMutableArray *transactionMaps = [[NSMutableArray alloc] init];
for (SKPaymentTransaction *transaction in transactions) {
[transactionMaps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]];
[transactionMaps addObject:[FIAObjectTranslator convertTransactionToPigeon:transaction]];
}
result(transactionMaps);
return transactionMaps;
}

- (void)getStorefront:(FlutterResult)result {
if (@available(iOS 13.0, macOS 10.15, *)) {
SKStorefront *storefront = self.paymentQueueHandler.storefront;
if (!storefront) {
result(nil);
return;
}
result([FIAObjectTranslator getMapFromSKStorefront:storefront]);
return;
- (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error
API_AVAILABLE(ios(13.0), macos(10.15)) {
SKStorefront *storefront = self.paymentQueueHandler.storefront;
if (!storefront) {
return nil;
}

NSLog(@"storefront is not avaialbe in iOS below 13.0 or macOS below 10.15.");
result(nil);
return;
return [FIAObjectTranslator convertStorefrontToPigeon:storefront];
}

- (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
Expand Down Expand Up @@ -193,28 +183,23 @@ - (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(Flutter
}];
}

- (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result {
Copy link
Member

Choose a reason for hiding this comment

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

It may be better for your sanity if you keep these methods around in the same place, but just change the signatures. That's going to be easier for you and the reviewer to verify the changes.

Maybe something like

- (void)addPayment:(SKProductMessage*)message withError:(NSError**)error;

It's not mandatory, just a suggestion. Maybe something to consider next time.

if (![call.arguments isKindOfClass:[NSDictionary class]]) {
result([FlutterError errorWithCode:@"storekit_invalid_argument"
message:@"Argument type of addPayment is not a Dictionary"
details:call.arguments]);
return;
}
NSDictionary *paymentMap = (NSDictionary *)call.arguments;
- (void)addPaymentPaymentMap:(nonnull NSDictionary *)paymentMap
error:(FlutterError *_Nullable __autoreleasing *_Nonnull)error {
NSString *productID = [paymentMap objectForKey:@"productIdentifier"];
// When a product is already fetched, we create a payment object with
// the product to process the payment.
SKProduct *product = [self getProduct:productID];
if (!product) {
result([FlutterError
*error = [FlutterError
errorWithCode:@"storekit_invalid_payment_object"
message:
@"You have requested a payment for an invalid product. Either the "
@"`productIdentifier` of the payment is not valid or the product has not been "
@"fetched before adding the payment to the payment queue."
details:call.arguments]);
details:paymentMap];
return;
}

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"];
NSNumber *quantity = [paymentMap objectForKey:@"quantity"];
Expand All @@ -227,34 +212,32 @@ - (void)addPayment:(FlutterMethodCall *)call result:(FlutterResult)result {
if (@available(iOS 12.2, *)) {
NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap
forKey:@"paymentDiscount"];
NSString *error = nil;
NSString *errorMsg = nil;
SKPaymentDiscount *paymentDiscount =
[FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error];
[FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&errorMsg];

if (error) {
result([FlutterError
if (errorMsg) {
*error = [FlutterError
errorWithCode:@"storekit_invalid_payment_discount_object"
message:[NSString stringWithFormat:@"You have requested a payment and specified a "
@"payment discount with invalid properties. %@",
error]
details:call.arguments]);
errorMsg]
details:paymentMap];
return;
}

payment.paymentDiscount = paymentDiscount;
}

if (![self.paymentQueueHandler addPayment:payment]) {
result([FlutterError
*error = [FlutterError
errorWithCode:@"storekit_duplicate_product_object"
message:@"There is a pending transaction for the same product identifier. Please "
@"either wait for it to be finished or finish it manually using "
@"`completePurchase` to avoid edge cases."

details:call.arguments]);
details:paymentMap];
return;
}
result(nil);
}

- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result {
Expand Down Expand Up @@ -465,5 +448,4 @@ - (SKProduct *)getProduct:(NSString *)productID {
- (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties {
return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties];
}

@end
Loading