Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 8 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
6 changes: 6 additions & 0 deletions packages/in_app_purchase/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 0.3.4+6

* [iOS] Fixed: purchase dialog not showing always.
* [iOS] Fixed: completing purchases could fail.
* [iOS] Fixed: restorePurchases caused hang (call never returned).

## 0.3.4+5

* Added necessary README docs for getting started with Android.
Expand Down
3 changes: 0 additions & 3 deletions packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ typedef void (^UpdatedDownloads)(NSArray<SKDownload *> *downloads);

@interface FIAPaymentQueueHandler : NSObject <SKPaymentTransactionObserver>

// Unfinished transactions.
@property(nonatomic, readonly) NSDictionary<NSString *, SKPaymentTransaction *> *transactions;

- (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
Expand Down
29 changes: 4 additions & 25 deletions packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ @interface FIAPaymentQueueHandler ()
@property(nullable, copy, nonatomic) ShouldAddStorePayment shouldAddStorePayment;
@property(nullable, copy, nonatomic) UpdatedDownloads updatedDownloads;

@property(strong, nonatomic)
NSMutableDictionary<NSString *, SKPaymentTransaction *> *transactionsSetter;

@end

@implementation FIAPaymentQueueHandler
Expand All @@ -39,7 +36,6 @@ - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
_paymentQueueRestoreCompletedTransactionsFinished = restoreCompletedTransactionsFinished;
_shouldAddStorePayment = shouldAddStorePayment;
_updatedDownloads = updatedDownloads;
_transactionsSetter = [NSMutableDictionary dictionary];
}
return self;
}
Expand All @@ -49,8 +45,10 @@ - (void)startObservingPaymentQueue {
}

- (BOOL)addPayment:(SKPayment *)payment {
if (self.transactionsSetter[payment.productIdentifier]) {
return NO;
for (SKPaymentTransaction *transaction in self.queue.transactions) {
if ([transaction.payment.productIdentifier isEqualToString:payment.productIdentifier]) {
return NO;
}
}
[self.queue addPayment:payment];
return YES;
Expand All @@ -74,26 +72,13 @@ - (void)restoreTransactions:(nullable NSString *)applicationName {
// state of transactions and finish as appropriate.
- (void)paymentQueue:(SKPaymentQueue *)queue
updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
if (transaction.transactionState != SKPaymentTransactionStatePurchasing) {
// Use product identifier instead of transaction identifier for few reasons:
// 1. Only transactions with purchased state and failed state will have a transaction id, it
// will become impossible for clients to finish deferred transactions when needed.
// 2. Using product identifiers can help prevent clients from purchasing the same
// subscription more than once by accident.
self.transactionsSetter[transaction.payment.productIdentifier] = transaction;
}
}
// notify dart through callbacks.
self.transactionsUpdated(transactions);
}

// Sent when transactions are removed from the queue (via finishTransaction:).
- (void)paymentQueue:(SKPaymentQueue *)queue
removedTransactions:(NSArray<SKPaymentTransaction *> *)transactions {
for (SKPaymentTransaction *transaction in transactions) {
[self.transactionsSetter removeObjectForKey:transaction.payment.productIdentifier];
}
self.transactionsRemoved(transactions);
}

Expand Down Expand Up @@ -126,10 +111,4 @@ - (BOOL)paymentQueue:(SKPaymentQueue *)queue
return self.queue.transactions;
}

#pragma mark - getter

- (NSDictionary<NSString *, SKPaymentTransaction *> *)transactions {
return [self.transactionsSetter copy];
}

@end
42 changes: 18 additions & 24 deletions packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -203,31 +203,24 @@ - (void)finishTransaction:(FlutterMethodCall *)call result:(FlutterResult)result
details:call.arguments]);
return;
}
NSString *identifier = call.arguments;
SKPaymentTransaction *transaction =
[self.paymentQueueHandler.transactions objectForKey:identifier];
if (!transaction) {
result([FlutterError
errorWithCode:@"storekit_platform_invalid_transaction"
message:[NSString
stringWithFormat:@"The transaction with transactionIdentifer:%@ does not "
@"exist. Note that if the transactionState is "
@"purchasing, the transactionIdentifier will be "
@"nil(null).",
transaction.transactionIdentifier]
details:call.arguments]);
return;
}
@try {
// finish transaction will throw exception if the transaction type is purchasing. Notify dart
// about this exception.
[self.paymentQueueHandler finishTransaction:transaction];
} @catch (NSException *e) {
result([FlutterError errorWithCode:@"storekit_finish_transaction_exception"
message:e.name
details:e.description]);
return;
NSString *transactionIdentifier = call.arguments;

NSArray<SKPaymentTransaction *> *pendingTransactions =
[self.paymentQueueHandler getUnfinishedTransactions];

for (SKPaymentTransaction *transaction in pendingTransactions) {
if ([transaction.transactionIdentifier isEqualToString:transactionIdentifier]) {
@try {
[self.paymentQueueHandler finishTransaction:transaction];
} @catch (NSException *e) {
result([FlutterError errorWithCode:@"storekit_finish_transaction_exception"
message:e.name
details:e.description]);
return;
}
}
}

result(nil);
}

Expand All @@ -240,6 +233,7 @@ - (void)restoreTransactions:(FlutterMethodCall *)call result:(FlutterResult)resu
return;
}
[self.paymentQueueHandler restoreTransactions:call.arguments];
result(nil);
}

- (void)retrieveReceiptData:(FlutterMethodCall *)call result:(FlutterResult)result {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class SKPaymentQueueWrapper {
SKPaymentTransactionWrapper transaction) async {
await channel.invokeMethod<void>(
'-[InAppPurchasePlugin finishTransaction:result:]',
transaction.payment.productIdentifier);
transaction.transactionIdentifier);
}

/// Restore previously purchased transactions.
Expand Down
2 changes: 1 addition & 1 deletion packages/in_app_purchase/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: in_app_purchase
description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play.
homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase
version: 0.3.4+5
version: 0.3.4+6

dependencies:
async: ^2.0.8
Expand Down