Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
Expand Up @@ -176,7 +176,7 @@
isa = PBXProject;
attributes = {
DefaultBuildSystemTypeForWorkspace = Original;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "The Flutter Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
Expand Down Expand Up @@ -418,14 +418,17 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = S8QB4VV633;
Copy link
Member

Choose a reason for hiding this comment

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

please revert these. xcode does this automatically, it's annoying

ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
Expand All @@ -442,14 +445,17 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = S8QB4VV633;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
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,6 +57,9 @@ NS_ASSUME_NONNULL_BEGIN
withError:(NSString *_Nullable *_Nullable)error
API_AVAILABLE(ios(12.2));

+ (nullable SKPaymentTransactionMessage *) convertTransactionToPigeon:(SKPaymentTransaction *) transaction;
Copy link
Member

Choose a reason for hiding this comment

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

These could have their own tests. Not a huge deal if they have transitive coverage.


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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#import "FIAObjectTranslator.h"
#import "messages.g.h"

#pragma mark - SKProduct Coders

Expand Down Expand Up @@ -294,4 +295,59 @@ + (SKPaymentDiscount *)getSKPaymentDiscountFromMap:(NSDictionary *)map
return discount;
}

+ (nullable SKPaymentTransactionMessage *) convertTransactionToPigeon:(SKPaymentTransaction *) transaction {
SKPaymentTransactionMessage *msg = [SKPaymentTransactionMessage makeWithPayment:[self convertPaymentToPigeon:transaction.payment] transactionState:[self convertTransactionStateToPigeon:transaction.transactionState] originalTransaction:transaction.originalTransaction ? [self convertTransactionToPigeon:transaction.originalTransaction] : nil transactionTimeStamp:[NSNumber numberWithDouble:[transaction.transactionDate timeIntervalSince1970]] transactionIdentifier:transaction.transactionIdentifier
Copy link
Member

Choose a reason for hiding this comment

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

Can you insert some newlines here between arguments? The formatter should respect that. Traditionally objc would align things at the colon. I don't know what clang-tidy does by default.

error:[self convertSKErrorToPigeon:transaction.error]];
return msg;
}

+ (nullable SKErrorMessage *) convertSKErrorToPigeon:(NSError *) error {
SKErrorMessage *msg = [SKErrorMessage makeWithCode:@(error.code) domain:error.domain userInfo:error.userInfo];
return msg;
}

+ (SKPaymentTransactionStateMessage) convertTransactionStateToPigeon:(SKPaymentTransactionState) state {
switch (state) {
case SKPaymentTransactionStatePurchasing:
return SKPaymentTransactionStateMessagePurchasing;
break;
Copy link
Member

Choose a reason for hiding this comment

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

You can remove these breaks.

case SKPaymentTransactionStatePurchased:
return SKPaymentTransactionStateMessagePurchased;
break;
case SKPaymentTransactionStateFailed:
return SKPaymentTransactionStateMessageFailed;
break;
case SKPaymentTransactionStateRestored:
return SKPaymentTransactionStateMessageRestored;
break;
case SKPaymentTransactionStateDeferred:
return SKPaymentTransactionStateMessageDeferred;
break;
}
}

+ (nullable SKPaymentMessage *) convertPaymentToPigeon:(SKPayment *) payment {
if (@available(iOS 12.2, *)) {
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;
}
return nil;
Copy link
Member

Choose a reason for hiding this comment

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

Can you make this guard more specific to the API you are protecting? Right now it's around makeWithProductIdentifier.

If we do really want to return nil we should have a comment (maybe just a TODO if you haven't gotten around to it).

}

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

return msg;
}

+ (nullable SKStorefrontMessage *) convertStorefrontToPigeon:(SKStorefront *)storefront API_AVAILABLE(ios(13.0)){
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 @@ -9,6 +9,7 @@
#import "FIAPReceiptManager.h"
#import "FIAPRequestHandler.h"
#import "FIAPaymentQueueHandler.h"
#import "messages.g.h"

@interface InAppPurchasePlugin ()

Expand Down Expand Up @@ -40,8 +41,13 @@ + (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];
// InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar];
// SetUpInAppPurchaseAPI([registrar messenger], instance);

InAppPurchasePlugin *instance = [[InAppPurchasePlugin alloc] initWithRegistrar:registrar];
[registrar addMethodCallDelegate:instance channel:channel];
[registrar addApplicationDelegate:instance];
InAppPurchaseAPISetup([registrar messenger], instance);
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure why the existing code does this but I'd just put registrar.messenger.

}

- (instancetype)initWithReceiptManager:(FIAPReceiptManager *)receiptManager {
Expand Down Expand Up @@ -85,16 +91,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 @@ -193,70 +191,6 @@ - (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;
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
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]);
return;
}
SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"];
NSNumber *quantity = [paymentMap objectForKey:@"quantity"];
payment.quantity = (quantity != nil) ? quantity.integerValue : 1;
NSNumber *simulatesAskToBuyInSandbox = [paymentMap objectForKey:@"simulatesAskToBuyInSandbox"];
payment.simulatesAskToBuyInSandbox = (id)simulatesAskToBuyInSandbox == (id)[NSNull null]
? NO
: [simulatesAskToBuyInSandbox boolValue];

if (@available(iOS 12.2, *)) {
NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap
forKey:@"paymentDiscount"];
NSString *error = nil;
SKPaymentDiscount *paymentDiscount =
[FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&error];

if (error) {
result([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]);
return;
}

payment.paymentDiscount = paymentDiscount;
}

if (![self.paymentQueueHandler addPayment:payment]) {
result([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]);
return;
}
result(nil);
}

- (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result {
if (![call.arguments isKindOfClass:[NSDictionary class]]) {
result([FlutterError errorWithCode:@"storekit_invalid_argument"
Expand Down Expand Up @@ -466,4 +400,87 @@ - (SKReceiptRefreshRequest *)getRefreshReceiptRequest:(NSDictionary *)properties
return [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:properties];
}

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

- (nullable SKStorefrontMessage *)storefrontWithError:(FlutterError *_Nullable *_Nonnull)error {
if (@available(iOS 13.0, macOS 10.15, *)) {
SKStorefront *storefront = self.paymentQueueHandler.storefront;
if (!storefront) {
return nil;
Copy link
Member

Choose a reason for hiding this comment

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

This should probably set error right?

}
return[FIAObjectTranslator convertStorefrontToPigeon:storefront];
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return[FIAObjectTranslator convertStorefrontToPigeon:storefront];
return [FIAObjectTranslator convertStorefrontToPigeon:storefront];

} else {
NSLog(@"storefront is not avaialbe in iOS below 13.0 or macOS below 10.15.");
return nil;
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, should be a reported error.

}
}

- (nullable NSArray<SKPaymentTransactionMessage *> *)transactionsWithError:(FlutterError *_Nullable *_Nonnull)error {
NSArray<SKPaymentTransaction *> *transactions =
[self.paymentQueueHandler getUnfinishedTransactions];
NSMutableArray *transactionMaps = [[NSMutableArray alloc] init];
Copy link
Member

Choose a reason for hiding this comment

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

s/transactionMaps/transactions?

for (SKPaymentTransaction *transaction in transactions) {
[transactionMaps addObject:[FIAObjectTranslator convertTransactionToPigeon:transaction]];
Copy link
Member

Choose a reason for hiding this comment

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

You should have a nil check here since addObject will crash if you try to send it a nil.

}
return(transactionMaps);
}

- (void)addPayment:(nonnull NSDictionary *)paymentMap error:(FlutterError * _Nullable __autoreleasing * _Nonnull)error {
Copy link
Member

Choose a reason for hiding this comment

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

This is working with a dictionary argument. Did we plan on moving this over to a class at some point? Maybe a class pigeon generates?

NSLog(@"Here");
Copy link
Member

Choose a reason for hiding this comment

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

There

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) {
*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:paymentMap];
return;
}

SKMutablePayment *payment = [SKMutablePayment paymentWithProduct:product];
payment.applicationUsername = [paymentMap objectForKey:@"applicationUsername"];
NSNumber *quantity = [paymentMap objectForKey:@"quantity"];
payment.quantity = (quantity != nil) ? quantity.integerValue : 1;
NSNumber *simulatesAskToBuyInSandbox = [paymentMap objectForKey:@"simulatesAskToBuyInSandbox"];
payment.simulatesAskToBuyInSandbox = (id)simulatesAskToBuyInSandbox == (id)[NSNull null]
? NO
: [simulatesAskToBuyInSandbox boolValue];

if (@available(iOS 12.2, *)) {
NSDictionary *paymentDiscountMap = [self getNonNullValueFromDictionary:paymentMap
forKey:@"paymentDiscount"];
NSString *errorMsg = nil;
SKPaymentDiscount *paymentDiscount =
[FIAObjectTranslator getSKPaymentDiscountFromMap:paymentDiscountMap withError:&errorMsg];

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. %@",
errorMsg]
details:paymentMap];
return;
}

payment.paymentDiscount = paymentDiscount;
}
if (![self.paymentQueueHandler addPayment:payment]) {
*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:paymentMap];
return;
}
}
@end
Loading