Skip to content

Commit

Permalink
[in_app_purchase] Convert storefront(), transactions(), canMakePaymen…
Browse files Browse the repository at this point in the history
…t(), and addPayment() to pigeon (#5910)

Part 1 of flutter/flutter#117910

This PR converts storefront(), addPayment(), transactions(), and
canMakePayment().


## 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 authored Jan 31, 2024
1 parent 7b1ae1f commit 871a24b
Show file tree
Hide file tree
Showing 24 changed files with 1,910 additions and 333 deletions.
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
? [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]) {
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 {
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

0 comments on commit 871a24b

Please sign in to comment.