[in_app_purchase] Expose SKPaymentQueue.transactions to dart (#2510)

diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md
index b1b3f2a..647ff0b 100644
--- a/packages/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.3.3
+
+* Introduce `SKPaymentQueueWrapper.transactions`.
+
 ## 0.3.2+2
 
 * Fix CocoaPods podspec lint warnings.
diff --git a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.h
index 55b9ca9..ed17881 100644
--- a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.h
+++ b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.h
@@ -32,6 +32,7 @@
 // Can throw exceptions if the transaction type is purchasing, should always used in a @try block.
 - (void)finishTransaction:(nonnull SKPaymentTransaction *)transaction;
 - (void)restoreTransactions:(nullable NSString *)applicationName;
+- (NSArray<SKPaymentTransaction *> *)getUnfinishedTransactions;
 
 // This method needs to be called before any other methods.
 - (void)startObservingPaymentQueue;
diff --git a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m
index 8ca39dd..8bdb7f2 100644
--- a/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m
+++ b/packages/in_app_purchase/ios/Classes/FIAPaymentQueueHandler.m
@@ -124,6 +124,10 @@
   return (self.shouldAddStorePayment(payment, product));
 }
 
+- (NSArray<SKPaymentTransaction *> *)getUnfinishedTransactions {
+  return self.queue.transactions;
+}
+
 #pragma mark - getter
 
 - (NSDictionary<NSString *, SKPaymentTransaction *> *)transactions {
diff --git a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
index 4d2a55f..bfe29f9 100644
--- a/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
+++ b/packages/in_app_purchase/ios/Classes/InAppPurchasePlugin.m
@@ -83,6 +83,8 @@
 - (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 ([@"-[InAppPurchasePlugin startProductRequest:result:]" isEqualToString:call.method]) {
     [self handleProductRequestMethodCall:call result:result];
   } else if ([@"-[InAppPurchasePlugin addPayment:result:]" isEqualToString:call.method]) {
@@ -104,6 +106,16 @@
   result([NSNumber numberWithBool:[SKPaymentQueue canMakePayments]]);
 }
 
+- (void)getPendingTransactions:(FlutterResult)result {
+  NSArray<SKPaymentTransaction *> *transactions =
+      [self.paymentQueueHandler getUnfinishedTransactions];
+  NSMutableArray *transactionMaps = [[NSMutableArray alloc] init];
+  for (SKPaymentTransaction *transaction in transactions) {
+    [transactionMaps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]];
+  }
+  result(transactionMaps);
+}
+
 - (void)handleProductRequestMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
   if (![call.arguments isKindOfClass:[NSArray class]]) {
     result([FlutterError errorWithCode:@"storekit_invalid_argument"
diff --git a/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m b/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m
index eeb76d2..e6a18e0 100644
--- a/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m
+++ b/packages/in_app_purchase/ios/Tests/InAppPurchasePluginTest.m
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#import <OCMock/OCMock.h>
 #import <XCTest/XCTest.h>
 #import "FIAPaymentQueueHandler.h"
 #import "Stubs.h"
@@ -248,4 +249,39 @@
   XCTAssertTrue(result);
 }
 
+- (void)testGetPendingTransactions {
+  XCTestExpectation* expectation = [self expectationWithDescription:@"expect success"];
+  FlutterMethodCall* call =
+      [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue transactions]" arguments:nil];
+  SKPaymentQueue* mockQueue = OCMClassMock(SKPaymentQueue.class);
+  NSDictionary* transactionMap = @{
+    @"transactionIdentifier" : [NSNull null],
+    @"transactionState" : @(SKPaymentTransactionStatePurchasing),
+    @"payment" : [NSNull null],
+    @"error" : [FIAObjectTranslator getMapFromNSError:[NSError errorWithDomain:@"test_stub"
+                                                                          code:123
+                                                                      userInfo:@{}]],
+    @"transactionTimeStamp" : @([NSDate date].timeIntervalSince1970),
+    @"originalTransaction" : [NSNull null],
+  };
+  OCMStub(mockQueue.transactions).andReturn(@[ [[SKPaymentTransactionStub alloc]
+      initWithMap:transactionMap] ]);
+
+  __block NSArray* resultArray;
+  self.plugin.paymentQueueHandler = [[FIAPaymentQueueHandler alloc] initWithQueue:mockQueue
+                                                              transactionsUpdated:nil
+                                                               transactionRemoved:nil
+                                                         restoreTransactionFailed:nil
+                                             restoreCompletedTransactionsFinished:nil
+                                                            shouldAddStorePayment:nil
+                                                                 updatedDownloads:nil];
+  [self.plugin handleMethodCall:call
+                         result:^(id r) {
+                           resultArray = r;
+                           [expectation fulfill];
+                         }];
+  [self waitForExpectations:@[ expectation ] timeout:5];
+  XCTAssertEqualObjects(resultArray, @[ transactionMap ]);
+}
+
 @end
diff --git a/packages/in_app_purchase/ios/in_app_purchase.podspec b/packages/in_app_purchase/ios/in_app_purchase.podspec
index 60109eb..8da9d78 100644
--- a/packages/in_app_purchase/ios/in_app_purchase.podspec
+++ b/packages/in_app_purchase/ios/in_app_purchase.podspec
@@ -22,5 +22,6 @@
 
   s.test_spec 'Tests' do |test_spec|
     test_spec.source_files = 'Tests/**/*'
+    test_spec.dependency 'OCMock','3.5'
   end
 end
diff --git a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
index 036c602..49c438e 100644
--- a/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
+++ b/packages/in_app_purchase/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
@@ -41,6 +41,12 @@
     callbackChannel.setMethodCallHandler(_handleObserverCallbacks);
   }
 
+  /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc)
+  Future<List<SKPaymentTransactionWrapper>> transactions() async {
+    return _getTransactionList(
+        await channel.invokeListMethod<Map>('-[SKPaymentQueue transactions]'));
+  }
+
   /// Calls [`-[SKPaymentQueue canMakePayments:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506139-canmakepayments?language=objc).
   static Future<bool> canMakePayments() async =>
       await channel.invokeMethod<bool>('-[SKPaymentQueue canMakePayments:]');
diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml
index ee04f34..ac8971e 100644
--- a/packages/in_app_purchase/pubspec.yaml
+++ b/packages/in_app_purchase/pubspec.yaml
@@ -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.2+2
+version: 0.3.3
 
 dependencies:
   async: ^2.0.8
diff --git a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
index 78c3c2e..c8da68a 100644
--- a/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
+++ b/packages/in_app_purchase/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
@@ -83,6 +83,10 @@
       expect(await SKPaymentQueueWrapper.canMakePayments(), true);
     });
 
+    test('transactions should return a valid list of transactions', () async {
+      expect(await SKPaymentQueueWrapper().transactions(), isNotEmpty);
+    });
+
     test(
         'throws if observer is not set for payment queue before adding payment',
         () async {
@@ -161,6 +165,8 @@
       // payment queue
       case '-[SKPaymentQueue canMakePayments:]':
         return Future<bool>.value(true);
+      case '-[SKPaymentQueue transactions]':
+        return Future<List<Map>>.value([buildTransactionMap(dummyTransaction)]);
       case '-[InAppPurchasePlugin addPayment:result:]':
         payments.add(SKPaymentWrapper.fromJson(call.arguments));
         return Future<void>.sync(() {});