[in_app_purchase] Add support for SKPaymentQueueDelegate and showPriceConsentIfNeeded (#4085)

diff --git a/packages/in_app_purchase/in_app_purchase_ios/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_ios/CHANGELOG.md
index 4b2d8ce..c4c4eb0 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase_ios/CHANGELOG.md
@@ -1,7 +1,11 @@
+## 0.1.1
+
+* Added support to register a `SKPaymentQueueDelegateWrapper` and handle changes to active subscriptions accordingly (see also Store Kit's [SKPaymentQueueDelegate](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc)).
+
 ## 0.1.0+2
 
-* Changed the iOS payment queue handler in such a way that it only adds a listener to the SKPaymentQueue when there 
-  is a listener to the Dart purchaseStream.
+* Changed the iOS payment queue handler in such a way that it only adds a listener to the `SKPaymentQueue` when there 
+  is a listener to the Dart `purchaseStream`.
 
 ## 0.1.0+1
 
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Podfile b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Podfile
index ae87502..5200b9f 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Podfile
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Podfile
@@ -29,12 +29,12 @@
 
 target 'Runner' do
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-
+  
   target 'RunnerTests' do
     inherit! :search_paths
 
     # Matches in_app_purchase test_spec dependency.
-    pod 'OCMock','3.5'
+    pod 'OCMock', '~> 3.6'
   end
 end
 
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner.xcodeproj/project.pbxproj
index 590b07f..61a5da6 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner.xcodeproj/project.pbxproj
@@ -13,6 +13,7 @@
 		688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 688DE35021F2A5A100EA2684 /* TranslatorTests.m */; };
 		6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */; };
 		6896B34C21EEB4B800D37AEF /* Stubs.m in Sources */ = {isa = PBXBuildFile; fileRef = 6896B34B21EEB4B800D37AEF /* Stubs.m */; };
+		7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -20,7 +21,7 @@
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
 		A5279298219369C600FF69E6 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5279297219369C600FF69E6 /* StoreKit.framework */; };
 		A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */; };
-		AB7252348F077C046D6617D3 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 630DD71BB3F145A22B1DE15D /* libPods-RunnerTests.a */; };
+		F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */; };
 		F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F78AF3132342BC89008449C7 /* PaymentQueueTests.m */; };
 /* End PBXBuildFile section */
 
@@ -48,14 +49,13 @@
 /* End PBXCopyFilesBuildPhase section */
 
 /* Begin PBXFileReference section */
-		027D04BC80EACAAB3B5232B8 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
+		10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		1630769A874F9381BC761FE1 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
-		194D4829A79EF6C7426B39F7 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		630DD71BB3F145A22B1DE15D /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		688DE35021F2A5A100EA2684 /* TranslatorTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TranslatorTests.m; sourceTree = "<group>"; };
 		6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductRequestHandlerTests.m; sourceTree = "<group>"; };
 		6896B34A21EEB4B800D37AEF /* Stubs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Stubs.h; sourceTree = "<group>"; };
@@ -71,11 +71,13 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
 		A5279297219369C600FF69E6 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
 		A59001A421E69658004A3E5E /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
 		A59001A621E69658004A3E5E /* InAppPurchasePluginTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = InAppPurchasePluginTests.m; sourceTree = "<group>"; };
 		A59001A821E69658004A3E5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FIAPPaymentQueueDeleteTests.m; sourceTree = "<group>"; };
 		F6E5D5F926131C4800C68BED /* Configuration.storekit */ = {isa = PBXFileReference; lastKnownFileType = text; path = Configuration.storekit; sourceTree = "<group>"; };
 		F78AF3132342BC89008449C7 /* PaymentQueueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PaymentQueueTests.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -94,7 +96,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				AB7252348F077C046D6617D3 /* libPods-RunnerTests.a in Frameworks */,
+				7E34217B7715B1918134647A /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -106,8 +108,8 @@
 			children = (
 				E4F9651425A612301059769C /* Pods-Runner.debug.xcconfig */,
 				2550EB3A5A3E749A54ADCA2D /* Pods-Runner.release.xcconfig */,
-				194D4829A79EF6C7426B39F7 /* Pods-RunnerTests.debug.xcconfig */,
-				027D04BC80EACAAB3B5232B8 /* Pods-RunnerTests.release.xcconfig */,
+				9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */,
+				10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */,
 			);
 			path = Pods;
 			sourceTree = "<group>";
@@ -187,6 +189,7 @@
 				6896B34521E9363700D37AEF /* ProductRequestHandlerTests.m */,
 				F78AF3132342BC89008449C7 /* PaymentQueueTests.m */,
 				688DE35021F2A5A100EA2684 /* TranslatorTests.m */,
+				F67646F72681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m */,
 			);
 			path = RunnerTests;
 			sourceTree = "<group>";
@@ -196,7 +199,7 @@
 			children = (
 				A5279297219369C600FF69E6 /* StoreKit.framework */,
 				1630769A874F9381BC761FE1 /* libPods-Runner.a */,
-				630DD71BB3F145A22B1DE15D /* libPods-RunnerTests.a */,
+				18D02AB334F1C07BB9A4374A /* libPods-RunnerTests.a */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
@@ -229,7 +232,7 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = A59001AD21E69658004A3E5E /* Build configuration list for PBXNativeTarget "RunnerTests" */;
 			buildPhases = (
-				321E2F5767F55B0A360AA77E /* [CP] Check Pods Manifest.lock */,
+				95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */,
 				A59001A021E69658004A3E5E /* Sources */,
 				A59001A121E69658004A3E5E /* Frameworks */,
 				A59001A221E69658004A3E5E /* Resources */,
@@ -310,7 +313,21 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		321E2F5767F55B0A360AA77E /* [CP] Check Pods Manifest.lock */ = {
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		95C7A5986B77A8DF76F6DF3A /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -332,20 +349,6 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
-		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -400,6 +403,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				F78AF3142342BC89008449C7 /* PaymentQueueTests.m in Sources */,
+				F67646F82681D9A80048C2EA /* FIAPPaymentQueueDeleteTests.m in Sources */,
 				6896B34621E9363700D37AEF /* ProductRequestHandlerTests.m in Sources */,
 				688DE35121F2A5A100EA2684 /* TranslatorTests.m in Sources */,
 				A59001A721E69658004A3E5E /* InAppPurchasePluginTests.m in Sources */,
@@ -593,7 +597,7 @@
 		};
 		A59001AB21E69658004A3E5E /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 194D4829A79EF6C7426B39F7 /* Pods-RunnerTests.debug.xcconfig */;
+			baseConfigurationReference = 9D681E092EB0D20D652F69FC /* Pods-RunnerTests.debug.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
@@ -616,7 +620,7 @@
 		};
 		A59001AC21E69658004A3E5E /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 027D04BC80EACAAB3B5232B8 /* Pods-RunnerTests.release.xcconfig */;
+			baseConfigurationReference = 10B860DFD91A1DF639D7BE1D /* Pods-RunnerTests.release.xcconfig */;
 			buildSettings = {
 				BUNDLE_LOADER = "$(TEST_HOST)";
 				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner/Configuration.storekit b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner/Configuration.storekit
index 4958a84..b98fefb 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner/Configuration.storekit
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/Runner/Configuration.storekit
@@ -1,4 +1,8 @@
 {
+  "identifier" : "6073E9A3",
+  "nonRenewingSubscriptions" : [
+
+  ],
   "products" : [
     {
       "displayPrice" : "0.99",
@@ -46,7 +50,7 @@
           "adHocOffers" : [
 
           ],
-          "displayPrice" : "3.99",
+          "displayPrice" : "4.99",
           "familyShareable" : false,
           "groupNumber" : 1,
           "internalID" : "922EB597",
@@ -59,7 +63,7 @@
             }
           ],
           "productID" : "subscription_silver",
-          "recurringSubscriptionPeriod" : "P1M",
+          "recurringSubscriptionPeriod" : "P1W",
           "referenceName" : "subscription_silver",
           "subscriptionGroupID" : "D0FEE8D8",
           "type" : "RecurringSubscription"
@@ -91,6 +95,6 @@
   ],
   "version" : {
     "major" : 1,
-    "minor" : 0
+    "minor" : 1
   }
 }
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/FIAPPaymentQueueDeleteTests.m b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/FIAPPaymentQueueDeleteTests.m
new file mode 100644
index 0000000..810e1fa
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/FIAPPaymentQueueDeleteTests.m
@@ -0,0 +1,120 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// 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 "FIAObjectTranslator.h"
+#import "FIAPaymentQueueHandler.h"
+#import "Stubs.h"
+
+@import in_app_purchase_ios;
+
+API_AVAILABLE(ios(13.0))
+@interface FIAPPaymentQueueDelegateTests : XCTestCase
+
+@property(strong, nonatomic) FlutterMethodChannel *channel;
+@property(strong, nonatomic) SKPaymentTransaction *transaction;
+@property(strong, nonatomic) SKStorefront *storefront;
+
+@end
+
+@implementation FIAPPaymentQueueDelegateTests
+
+- (void)setUp {
+  self.channel = OCMClassMock(FlutterMethodChannel.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],
+  };
+  self.transaction = [[SKPaymentTransactionStub alloc] initWithMap:transactionMap];
+
+  NSDictionary *storefrontMap = @{
+    @"countryCode" : @"USA",
+    @"identifier" : @"unique_identifier",
+  };
+  self.storefront = [[SKStorefrontStub alloc] initWithMap:storefrontMap];
+}
+
+- (void)tearDown {
+  self.channel = nil;
+}
+
+- (void)testShouldContinueTransaction {
+  if (@available(iOS 13.0, *)) {
+    FIAPPaymentQueueDelegate *delegate =
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+
+    OCMStub([self.channel
+        invokeMethod:@"shouldContinueTransaction"
+           arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront
+                                         andSKPaymentTransaction:self.transaction]
+              result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]);
+
+    BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class)
+                       shouldContinueTransaction:self.transaction
+                                    inStorefront:self.storefront];
+
+    XCTAssertFalse(shouldContinue);
+  }
+}
+
+- (void)testShouldContinueTransaction_should_default_to_yes {
+  if (@available(iOS 13.0, *)) {
+    FIAPPaymentQueueDelegate *delegate =
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+
+    OCMStub([self.channel invokeMethod:@"shouldContinueTransaction"
+                             arguments:[FIAObjectTranslator getMapFromSKStorefront:self.storefront
+                                                           andSKPaymentTransaction:self.transaction]
+                                result:[OCMArg any]]);
+
+    BOOL shouldContinue = [delegate paymentQueue:OCMClassMock(SKPaymentQueue.class)
+                       shouldContinueTransaction:self.transaction
+                                    inStorefront:self.storefront];
+
+    XCTAssertTrue(shouldContinue);
+  }
+}
+
+- (void)testShouldShowPriceConsentIfNeeded {
+  if (@available(iOS 13.4, *)) {
+    FIAPPaymentQueueDelegate *delegate =
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+
+    OCMStub([self.channel
+        invokeMethod:@"shouldShowPriceConsent"
+           arguments:nil
+              result:([OCMArg invokeBlockWithArgs:[NSNumber numberWithBool:NO], nil])]);
+
+    BOOL shouldShow =
+        [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)];
+
+    XCTAssertFalse(shouldShow);
+  }
+}
+
+- (void)testShouldShowPriceConsentIfNeeded_should_default_to_yes {
+  if (@available(iOS 13.4, *)) {
+    FIAPPaymentQueueDelegate *delegate =
+        [[FIAPPaymentQueueDelegate alloc] initWithMethodChannel:self.channel];
+
+    OCMStub([self.channel invokeMethod:@"shouldShowPriceConsent"
+                             arguments:nil
+                                result:[OCMArg any]]);
+
+    BOOL shouldShow =
+        [delegate paymentQueueShouldShowPriceConsent:OCMClassMock(SKPaymentQueue.class)];
+
+    XCTAssertTrue(shouldShow);
+  }
+}
+
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/InAppPurchasePluginTests.m b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/InAppPurchasePluginTests.m
index 241ea0d..045abcd 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/InAppPurchasePluginTests.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/InAppPurchasePluginTests.m
@@ -343,4 +343,81 @@
   XCTAssertNil(queue.observer);
 }
 
+- (void)testRegisterPaymentQueueDelegate {
+  if (@available(iOS 13, *)) {
+    FlutterMethodCall* call =
+        [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue registerDelegate]"
+                                          arguments:nil];
+
+    self.plugin.paymentQueueHandler =
+        [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
+                                  transactionsUpdated:nil
+                                   transactionRemoved:nil
+                             restoreTransactionFailed:nil
+                 restoreCompletedTransactionsFinished:nil
+                                shouldAddStorePayment:nil
+                                     updatedDownloads:nil];
+
+    // Verify the delegate is nil before we register one.
+    XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
+
+    [self.plugin handleMethodCall:call
+                           result:^(id r){
+                           }];
+
+    // Verify the delegate is not nil after we registered one.
+    XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate);
+  }
+}
+
+- (void)testRemovePaymentQueueDelegate {
+  if (@available(iOS 13, *)) {
+    FlutterMethodCall* call =
+        [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue removeDelegate]"
+                                          arguments:nil];
+
+    self.plugin.paymentQueueHandler =
+        [[FIAPaymentQueueHandler alloc] initWithQueue:[SKPaymentQueueStub new]
+                                  transactionsUpdated:nil
+                                   transactionRemoved:nil
+                             restoreTransactionFailed:nil
+                 restoreCompletedTransactionsFinished:nil
+                                shouldAddStorePayment:nil
+                                     updatedDownloads:nil];
+    self.plugin.paymentQueueHandler.delegate = OCMProtocolMock(@protocol(SKPaymentQueueDelegate));
+
+    // Verify the delegate is not nil before removing it.
+    XCTAssertNotNil(self.plugin.paymentQueueHandler.delegate);
+
+    [self.plugin handleMethodCall:call
+                           result:^(id r){
+                           }];
+
+    // Verify the delegate is nill after removing it.
+    XCTAssertNil(self.plugin.paymentQueueHandler.delegate);
+  }
+}
+
+- (void)testShowPriceConsentIfNeeded {
+  FlutterMethodCall* call =
+      [FlutterMethodCall methodCallWithMethodName:@"-[SKPaymentQueue showPriceConsentIfNeeded]"
+                                        arguments:nil];
+
+  FIAPaymentQueueHandler* mockQueueHandler = OCMClassMock(FIAPaymentQueueHandler.class);
+  self.plugin.paymentQueueHandler = mockQueueHandler;
+
+  [self.plugin handleMethodCall:call
+                         result:^(id r){
+                         }];
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpartial-availability"
+  if (@available(iOS 13.4, *)) {
+    OCMVerify(times(1), [mockQueueHandler showPriceConsentIfNeeded]);
+  } else {
+    OCMVerify(never(), [mockQueueHandler showPriceConsentIfNeeded]);
+  }
+#pragma clang diagnostic pop
+}
+
 @end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.h b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.h
index 687118f..7b6842d 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.h
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.h
@@ -60,4 +60,9 @@
 - (instancetype)initWithFailureError:(NSError *)error;
 @end
 
+API_AVAILABLE(ios(13.0), macos(10.15))
+@interface SKStorefrontStub : SKStorefront
+- (instancetype)initWithMap:(NSDictionary *)map;
+@end
+
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.m b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.m
index 8af326a..a57831c 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/Stubs.m
@@ -290,3 +290,17 @@
 }
 
 @end
+
+@implementation SKStorefrontStub
+
+- (instancetype)initWithMap:(NSDictionary *)map {
+  self = [super init];
+  if (self) {
+    // Set stub values
+    [self setValue:map[@"countryCode"] forKey:@"countryCode"];
+    [self setValue:map[@"identifier"] forKey:@"identifier"];
+  }
+  return self;
+}
+
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/TranslatorTests.m b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/TranslatorTests.m
index 385d291..42c51b8 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/TranslatorTests.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/ios/RunnerTests/TranslatorTests.m
@@ -17,6 +17,8 @@
 @property(strong, nonatomic) NSDictionary *transactionMap;
 @property(strong, nonatomic) NSDictionary *errorMap;
 @property(strong, nonatomic) NSDictionary *localeMap;
+@property(strong, nonatomic) NSDictionary *storefrontMap;
+@property(strong, nonatomic) NSDictionary *storefrontAndPaymentTransactionMap;
 
 @end
 
@@ -84,6 +86,15 @@
       @"key" : @"value",
     }
   };
+  self.storefrontMap = @{
+    @"countryCode" : @"USA",
+    @"identifier" : @"unique_identifier",
+  };
+
+  self.storefrontAndPaymentTransactionMap = @{
+    @"storefront" : self.storefrontMap,
+    @"transaction" : self.transactionMap,
+  };
 }
 
 - (void)testSKProductSubscriptionPeriodStubToMap {
@@ -144,4 +155,23 @@
   }
 }
 
+- (void)testSKStorefrontToMap {
+  if (@available(iOS 13.0, *)) {
+    SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap];
+    NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront];
+    XCTAssertEqualObjects(map, self.storefrontMap);
+  }
+}
+
+- (void)testSKStorefrontAndSKPaymentTransactionToMap {
+  if (@available(iOS 13.0, *)) {
+    SKStorefront *storefront = [[SKStorefrontStub alloc] initWithMap:self.storefrontMap];
+    SKPaymentTransaction *transaction =
+        [[SKPaymentTransactionStub alloc] initWithMap:self.transactionMap];
+    NSDictionary *map = [FIAObjectTranslator getMapFromSKStorefront:storefront
+                                            andSKPaymentTransaction:transaction];
+    XCTAssertEqualObjects(map, self.storefrontAndPaymentTransactionMap);
+  }
+}
+
 @end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/lib/example_payment_queue_delegate.dart b/packages/in_app_purchase/in_app_purchase_ios/example/lib/example_payment_queue_delegate.dart
new file mode 100644
index 0000000..dfebdf9
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/lib/example_payment_queue_delegate.dart
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:in_app_purchase_ios/store_kit_wrappers.dart';
+
+/// Example implementation of the
+/// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc).
+///
+/// The payment queue delegate can be implementated to provide information
+/// needed to complete transactions.
+class ExamplePaymentQueueDelegate implements SKPaymentQueueDelegateWrapper {
+  @override
+  bool shouldContinueTransaction(
+      SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
+    return true;
+  }
+
+  @override
+  bool shouldShowPriceConsent() {
+    return false;
+  }
+}
diff --git a/packages/in_app_purchase/in_app_purchase_ios/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_ios/example/lib/main.dart
index 5452f5a..1988474 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/example/lib/main.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/example/lib/main.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 import 'package:flutter/material.dart';
 import 'package:in_app_purchase_ios/in_app_purchase_ios.dart';
+import 'package:in_app_purchase_ios_example/example_payment_queue_delegate.dart';
 import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
 import 'consumable_store.dart';
 
@@ -40,6 +41,9 @@
 class _MyAppState extends State<_MyApp> {
   final InAppPurchaseIosPlatform _iapIosPlatform =
       InAppPurchasePlatform.instance as InAppPurchaseIosPlatform;
+  final InAppPurchaseIosPlatformAddition _iapIosPlatformAddition =
+      InAppPurchasePlatformAddition.instance
+          as InAppPurchaseIosPlatformAddition;
   late StreamSubscription<List<PurchaseDetails>> _subscription;
   List<String> _notFoundIds = [];
   List<ProductDetails> _products = [];
@@ -61,6 +65,10 @@
     }, onError: (error) {
       // handle error here.
     });
+
+    // Register the example payment queue delegate
+    _iapIosPlatformAddition.setDelegate(ExamplePaymentQueueDelegate());
+
     initStoreInfo();
     super.initState();
   }
@@ -241,7 +249,11 @@
               productDetails.description,
             ),
             trailing: previousPurchase != null
-                ? Icon(Icons.check)
+                ? IconButton(
+                    onPressed: () {
+                      _iapIosPlatformAddition.showPriceConsentIfNeeded();
+                    },
+                    icon: Icon(Icons.upgrade))
                 : TextButton(
                     child: Text(productDetails.price),
                     style: TextButton.styleFrom(
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.h b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.h
index 2d0187e..95a5edc 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.h
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.h
@@ -9,26 +9,44 @@
 
 @interface FIAObjectTranslator : NSObject
 
+// Converts an instance of SKProduct into a dictionary.
 + (NSDictionary *)getMapFromSKProduct:(SKProduct *)product;
 
+// Converts an instance of SKProductSubscriptionPeriod into a dictionary.
 + (NSDictionary *)getMapFromSKProductSubscriptionPeriod:(SKProductSubscriptionPeriod *)period
     API_AVAILABLE(ios(11.2));
 
+// Converts an instance of SKProductDiscount into a dictionary.
 + (NSDictionary *)getMapFromSKProductDiscount:(SKProductDiscount *)discount
     API_AVAILABLE(ios(11.2));
 
+// Converts an instance of SKProductsResponse into a dictionary.
 + (NSDictionary *)getMapFromSKProductsResponse:(SKProductsResponse *)productResponse;
 
+// Converts an instance of SKPayment into a dictionary.
 + (NSDictionary *)getMapFromSKPayment:(SKPayment *)payment;
 
+// Converts an instance of NSLocale into a dictionary.
 + (NSDictionary *)getMapFromNSLocale:(NSLocale *)locale;
 
+// Creates an instance of the SKMutablePayment class based on the supplied dictionary.
 + (SKMutablePayment *)getSKMutablePaymentFromMap:(NSDictionary *)map;
 
+// Converts an instance of SKPaymentTransaction into a dictionary.
 + (NSDictionary *)getMapFromSKPaymentTransaction:(SKPaymentTransaction *)transaction;
 
+// Converts an instance of NSError into a dictionary.
 + (NSDictionary *)getMapFromNSError:(NSError *)error;
 
+// Converts an instance of SKStorefront into a dictionary.
++ (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront
+    API_AVAILABLE(ios(13), macos(10.15), watchos(6.2));
+
+// Converts the supplied instances of SKStorefront and SKPaymentTransaction into a dictionary.
++ (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront
+                 andSKPaymentTransaction:(SKPaymentTransaction *)transaction
+    API_AVAILABLE(ios(13), macos(10.15), watchos(6.2));
+
 @end
 ;
 
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.m b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.m
index 5d6e0a2..30b0b81 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAObjectTranslator.m
@@ -169,4 +169,31 @@
   return @{@"code" : @(error.code), @"domain" : error.domain ?: @"", @"userInfo" : userInfo};
 }
 
++ (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront {
+  if (!storefront) {
+    return nil;
+  }
+
+  NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
+    @"countryCode" : storefront.countryCode,
+    @"identifier" : storefront.identifier
+  }];
+
+  return map;
+}
+
++ (NSDictionary *)getMapFromSKStorefront:(SKStorefront *)storefront
+                 andSKPaymentTransaction:(SKPaymentTransaction *)transaction {
+  if (!storefront || !transaction) {
+    return nil;
+  }
+
+  NSMutableDictionary *map = [[NSMutableDictionary alloc] initWithDictionary:@{
+    @"storefront" : [FIAObjectTranslator getMapFromSKStorefront:storefront],
+    @"transaction" : [FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]
+  }];
+
+  return map;
+}
+
 @end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.h b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.h
new file mode 100644
index 0000000..a6c91fa
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.h
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <Foundation/Foundation.h>
+#import <StoreKit/StoreKit.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+API_AVAILABLE(ios(13))
+@interface FIAPPaymentQueueDelegate : NSObject <SKPaymentQueueDelegate>
+- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel;
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.m b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.m
new file mode 100644
index 0000000..1056086
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPPaymentQueueDelegate.m
@@ -0,0 +1,78 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FIAPPaymentQueueDelegate.h"
+#import "FIAObjectTranslator.h"
+
+@interface FIAPPaymentQueueDelegate ()
+
+@property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel;
+
+@end
+
+@implementation FIAPPaymentQueueDelegate
+
+- (id)initWithMethodChannel:(FlutterMethodChannel *)methodChannel {
+  self = [super init];
+  if (self) {
+    _callbackChannel = methodChannel;
+  }
+
+  return self;
+}
+
+- (BOOL)paymentQueue:(SKPaymentQueue *)paymentQueue
+    shouldContinueTransaction:(SKPaymentTransaction *)transaction
+                 inStorefront:(SKStorefront *)newStorefront {
+  // Default return value for this method is true (see
+  // https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc)
+  __block BOOL shouldContinue = YES;
+  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+  [self.callbackChannel invokeMethod:@"shouldContinueTransaction"
+                           arguments:[FIAObjectTranslator getMapFromSKStorefront:newStorefront
+                                                         andSKPaymentTransaction:transaction]
+                              result:^(id _Nullable result) {
+                                // When result is a valid instance of NSNumber use it to determine
+                                // if the transaction should continue. Otherwise use the default
+                                // value.
+                                if (result && [result isKindOfClass:[NSNumber class]]) {
+                                  shouldContinue = [(NSNumber *)result boolValue];
+                                }
+
+                                dispatch_semaphore_signal(semaphore);
+                              }];
+
+  // The client should respond within 1 second otherwise continue
+  // with default value.
+  dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC));
+
+  return shouldContinue;
+}
+
+- (BOOL)paymentQueueShouldShowPriceConsent:(SKPaymentQueue *)paymentQueue {
+  // Default return value for this method is true (see
+  // https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc)
+  __block BOOL shouldShowPriceConsent = YES;
+  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
+  [self.callbackChannel invokeMethod:@"shouldShowPriceConsent"
+                           arguments:nil
+                              result:^(id _Nullable result) {
+                                // When result is a valid instance of NSNumber use it to determine
+                                // if the transaction should continue. Otherwise use the default
+                                // value.
+                                if (result && [result isKindOfClass:[NSNumber class]]) {
+                                  shouldShowPriceConsent = [(NSNumber *)result boolValue];
+                                }
+
+                                dispatch_semaphore_signal(semaphore);
+                              }];
+
+  // The client should respond within 1 second otherwise continue
+  // with default value.
+  dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC));
+
+  return shouldShowPriceConsent;
+}
+
+@end
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.h b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.h
index 30865b2..8019831 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.h
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.h
@@ -18,6 +18,9 @@
 
 @interface FIAPaymentQueueHandler : NSObject <SKPaymentTransactionObserver>
 
+@property(NS_NONATOMIC_IOSONLY, weak, nullable) id<SKPaymentQueueDelegate> delegate API_AVAILABLE(
+    ios(13.0), macos(10.15), watchos(6.2));
+
 - (instancetype)initWithQueue:(nonnull SKPaymentQueue *)queue
                      transactionsUpdated:(nullable TransactionsUpdated)transactionsUpdated
                       transactionRemoved:(nullable TransactionsRemoved)transactionsRemoved
@@ -43,6 +46,15 @@
 // @return whether "addPayment" was successful.
 - (BOOL)addPayment:(SKPayment *)payment;
 
+// Displays the price consent sheet.
+//
+// The price consent sheet is only displayed when the following
+// it true:
+// - You have increased the price of the subscription in App Store Connect.
+// - The subscriber has not yet responded to a price consent query.
+// Otherwise the method has no effect.
+- (void)showPriceConsentIfNeeded API_AVAILABLE(ios(13.4));
+
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.m b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.m
index 20ccbc5..2166795 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/FIAPaymentQueueHandler.m
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 #import "FIAPaymentQueueHandler.h"
+#import "FIAPPaymentQueueDelegate.h"
 
 @interface FIAPaymentQueueHandler ()
 
@@ -36,6 +37,10 @@
     _paymentQueueRestoreCompletedTransactionsFinished = restoreCompletedTransactionsFinished;
     _shouldAddStorePayment = shouldAddStorePayment;
     _updatedDownloads = updatedDownloads;
+
+    if (@available(iOS 13.0, macOS 10.15, *)) {
+      queue.delegate = self.delegate;
+    }
   }
   return self;
 }
@@ -78,6 +83,10 @@
   }
 }
 
+- (void)showPriceConsentIfNeeded {
+  [self.queue showPriceConsentIfNeeded];
+}
+
 #pragma mark - observing
 
 // Sent when the transaction array has changed (additions or state changes).  Client should check
diff --git a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/InAppPurchasePlugin.m b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/InAppPurchasePlugin.m
index 8a998d9..c0db38e 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/InAppPurchasePlugin.m
+++ b/packages/in_app_purchase/in_app_purchase_ios/ios/Classes/InAppPurchasePlugin.m
@@ -5,6 +5,7 @@
 #import "InAppPurchasePlugin.h"
 #import <StoreKit/StoreKit.h>
 #import "FIAObjectTranslator.h"
+#import "FIAPPaymentQueueDelegate.h"
 #import "FIAPReceiptManager.h"
 #import "FIAPRequestHandler.h"
 #import "FIAPaymentQueueHandler.h"
@@ -19,13 +20,19 @@
 // for purchase.
 @property(strong, nonatomic, readonly) NSMutableDictionary *productsCache;
 
-// Call back channel to dart used for when a listener function is triggered.
-@property(strong, nonatomic, readonly) FlutterMethodChannel *callbackChannel;
+// Callback channel to dart used for when a function from the transaction observer is triggered.
+@property(strong, nonatomic, readonly) FlutterMethodChannel *transactionObserverCallbackChannel;
+
+// Callback channel to dart used for when a function from the payment queue delegate is triggered.
+@property(strong, nonatomic, readonly) FlutterMethodChannel *paymentQueueDelegateCallbackChannel;
+
 @property(strong, nonatomic, readonly) NSObject<FlutterTextureRegistry> *registry;
 @property(strong, nonatomic, readonly) NSObject<FlutterBinaryMessenger> *messenger;
 @property(strong, nonatomic, readonly) NSObject<FlutterPluginRegistrar> *registrar;
 
 @property(strong, nonatomic, readonly) FIAPReceiptManager *receiptManager;
+@property(strong, nonatomic, readonly)
+    FIAPPaymentQueueDelegate *paymentQueueDelegate API_AVAILABLE(ios(13));
 
 @end
 
@@ -73,7 +80,8 @@
       updatedDownloads:^void(NSArray<SKDownload *> *_Nonnull downloads) {
         [weakSelf updatedDownloads:downloads];
       }];
-  _callbackChannel =
+
+  _transactionObserverCallbackChannel =
       [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/in_app_purchase"
                                   binaryMessenger:[registrar messenger]];
   return self;
@@ -100,9 +108,15 @@
   } else if ([@"-[InAppPurchasePlugin refreshReceipt:result:]" isEqualToString:call.method]) {
     [self refreshReceipt:call result:result];
   } else if ([@"-[SKPaymentQueue startObservingTransactionQueue]" isEqualToString:call.method]) {
-    [_paymentQueueHandler startObservingPaymentQueue];
+    [self startObservingPaymentQueue:result];
   } else if ([@"-[SKPaymentQueue stopObservingTransactionQueue]" isEqualToString:call.method]) {
-    [_paymentQueueHandler stopObservingPaymentQueue];
+    [self stopObservingPaymentQueue:result];
+  } else if ([@"-[SKPaymentQueue registerDelegate]" isEqualToString:call.method]) {
+    [self registerPaymentQueueDelegate:result];
+  } else if ([@"-[SKPaymentQueue removeDelegate]" isEqualToString:call.method]) {
+    [self removePaymentQueueDelegate:result];
+  } else if ([@"-[SKPaymentQueue showPriceConsentIfNeeded]" isEqualToString:call.method]) {
+    [self showPriceConsentIfNeeded:result];
   } else {
     result(FlutterMethodNotImplemented);
   }
@@ -301,14 +315,53 @@
   }];
 }
 
-#pragma mark - delegates:
+- (void)startObservingPaymentQueue:(FlutterResult)result {
+  [_paymentQueueHandler startObservingPaymentQueue];
+  result(nil);
+}
+
+- (void)stopObservingPaymentQueue:(FlutterResult)result {
+  [_paymentQueueHandler stopObservingPaymentQueue];
+  result(nil);
+}
+
+- (void)registerPaymentQueueDelegate:(FlutterResult)result {
+  if (@available(iOS 13.0, *)) {
+    _paymentQueueDelegateCallbackChannel = [FlutterMethodChannel
+        methodChannelWithName:@"plugins.flutter.io/in_app_purchase_payment_queue_delegate"
+              binaryMessenger:_messenger];
+
+    _paymentQueueDelegate = [[FIAPPaymentQueueDelegate alloc]
+        initWithMethodChannel:_paymentQueueDelegateCallbackChannel];
+    _paymentQueueHandler.delegate = _paymentQueueDelegate;
+  }
+  result(nil);
+}
+
+- (void)removePaymentQueueDelegate:(FlutterResult)result {
+  if (@available(iOS 13.0, *)) {
+    _paymentQueueHandler.delegate = nil;
+  }
+  _paymentQueueDelegate = nil;
+  _paymentQueueDelegateCallbackChannel = nil;
+  result(nil);
+}
+
+- (void)showPriceConsentIfNeeded:(FlutterResult)result {
+  if (@available(iOS 13.4, *)) {
+    [_paymentQueueHandler showPriceConsentIfNeeded];
+  }
+  result(nil);
+}
+
+#pragma mark - transaction observer:
 
 - (void)handleTransactionsUpdated:(NSArray<SKPaymentTransaction *> *)transactions {
   NSMutableArray *maps = [NSMutableArray new];
   for (SKPaymentTransaction *transaction in transactions) {
     [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]];
   }
-  [self.callbackChannel invokeMethod:@"updatedTransactions" arguments:maps];
+  [self.transactionObserverCallbackChannel invokeMethod:@"updatedTransactions" arguments:maps];
 }
 
 - (void)handleTransactionsRemoved:(NSArray<SKPaymentTransaction *> *)transactions {
@@ -316,17 +369,19 @@
   for (SKPaymentTransaction *transaction in transactions) {
     [maps addObject:[FIAObjectTranslator getMapFromSKPaymentTransaction:transaction]];
   }
-  [self.callbackChannel invokeMethod:@"removedTransactions" arguments:maps];
+  [self.transactionObserverCallbackChannel invokeMethod:@"removedTransactions" arguments:maps];
 }
 
 - (void)handleTransactionRestoreFailed:(NSError *)error {
-  [self.callbackChannel invokeMethod:@"restoreCompletedTransactionsFailed"
-                           arguments:[FIAObjectTranslator getMapFromNSError:error]];
+  [self.transactionObserverCallbackChannel
+      invokeMethod:@"restoreCompletedTransactionsFailed"
+         arguments:[FIAObjectTranslator getMapFromNSError:error]];
 }
 
 - (void)restoreCompletedTransactionsFinished {
-  [self.callbackChannel invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished"
-                           arguments:nil];
+  [self.transactionObserverCallbackChannel
+      invokeMethod:@"paymentQueueRestoreCompletedTransactionsFinished"
+         arguments:nil];
 }
 
 - (void)updatedDownloads:(NSArray<SKDownload *> *)downloads {
@@ -338,11 +393,12 @@
   // have a interception method that deciding if the payment should be processed (implemented by the
   // programmer).
   [self.productsCache setObject:product forKey:product.productIdentifier];
-  [self.callbackChannel invokeMethod:@"shouldAddStorePayment"
-                           arguments:@{
-                             @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment],
-                             @"product" : [FIAObjectTranslator getMapFromSKProduct:product]
-                           }];
+  [self.transactionObserverCallbackChannel
+      invokeMethod:@"shouldAddStorePayment"
+         arguments:@{
+           @"payment" : [FIAObjectTranslator getMapFromSKPayment:payment],
+           @"product" : [FIAObjectTranslator getMapFromSKProduct:product]
+         }];
   return NO;
 }
 
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/channel.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/channel.dart
index f8ab4d4..d045dab 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/lib/src/channel.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/channel.dart
@@ -7,3 +7,8 @@
 /// Method channel for the plugin's platform<-->Dart calls.
 const MethodChannel channel =
     MethodChannel('plugins.flutter.io/in_app_purchase');
+
+/// Method channel used to deliver the payment queue delegate system calls to
+/// Dart.
+const MethodChannel paymentQueueDelegateChannel =
+    MethodChannel('plugins.flutter.io/in_app_purchase_payment_queue_delegate');
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/in_app_purchase_ios_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/in_app_purchase_ios_platform_addition.dart
index 0c7b2de..bcc4ddf 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/lib/src/in_app_purchase_ios_platform_addition.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/in_app_purchase_ios_platform_addition.dart
@@ -30,4 +30,28 @@
         serverVerificationData: receipt,
         source: kIAPSource);
   }
+
+  /// Sets an implementation of the [SKPaymentQueueDelegateWrapper].
+  ///
+  /// The [SKPaymentQueueDelegateWrapper] can be used to inform iOS how to
+  /// finish transactions when the storefront changes or if the price consent
+  /// sheet should be displayed when the price of a subscription has changed. If
+  /// no delegate is registered iOS will fallback to it's default configuration.
+  /// See the documentation on StoreKite's [`-[SKPaymentQueue delegate:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc).
+  ///
+  /// When set to `null` the payment queue delegate will be removed and the
+  /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)).
+  Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) =>
+      SKPaymentQueueWrapper().setDelegate(delegate);
+
+  /// Shows the price consent sheet if the user has not yet responded to a
+  /// subscription price change.
+  ///
+  /// Use this function when you have registered a [SKPaymentQueueDelegateWrapper]
+  /// (using the [setDelegate] method) and returned `false` when the
+  /// `SKPaymentQueueDelegateWrapper.shouldShowPriceConsent()` method was called.
+  ///
+  /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc).
+  Future showPriceConsentIfNeeded() =>
+      SKPaymentQueueWrapper().showPriceConsentIfNeeded();
 }
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart
new file mode 100644
index 0000000..2759a29
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart
@@ -0,0 +1,39 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:in_app_purchase_ios/store_kit_wrappers.dart';
+
+/// A wrapper around
+/// [`SKPaymentQueueDelegate`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate?language=objc).
+///
+/// The payment queue delegate can be implementated to provide information
+/// needed to complete transactions.
+///
+/// The [SKPaymentQueueDelegateWrapper] is only available on iOS 13 and higher.
+/// Using the delegate on older iOS version will be ignored.
+abstract class SKPaymentQueueDelegateWrapper {
+  /// Called by the system to check whether the transaction should continue if
+  /// the device's App Store storefront has changed during a transaction.
+  ///
+  /// - Return `true` if the transaction should continue within the updated
+  /// storefront (default behaviour).
+  /// - Return `false` if the transaction should be cancelled. In this case the
+  /// transaction will fail with the error [SKErrorStoreProductNotAvailable](https://developer.apple.com/documentation/storekit/skerrorcode/skerrorstoreproductnotavailable?language=objc).
+  ///
+  /// See the documentation in StoreKit's [`[-SKPaymentQueueDelegate shouldContinueTransaction]`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3242935-paymentqueue?language=objc).
+  bool shouldContinueTransaction(
+    SKPaymentTransactionWrapper transaction,
+    SKStorefrontWrapper storefront,
+  ) =>
+      true;
+
+  /// Called by the system to check whether to immediately show the price
+  /// consent form.
+  ///
+  /// The default return value is `true`. This will inform the system to display
+  /// the price consent sheet when the subscription price has been changed in
+  /// App Store Connect and the subscriber has not yet taken action. See the
+  /// documentation in StoreKit's [`[-SKPaymentQueueDelegate shouldShowPriceConsent:]`](https://developer.apple.com/documentation/storekit/skpaymentqueuedelegate/3521328-paymentqueueshouldshowpriceconse?language=objc).
+  bool shouldShowPriceConsent() => true;
+}
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
index fe5f14b..c39ad9e 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_payment_queue_wrapper.dart
@@ -6,12 +6,15 @@
 import 'dart:ui' show hashValues;
 
 import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
+import 'package:in_app_purchase_ios/store_kit_wrappers.dart';
 import 'package:json_annotation/json_annotation.dart';
 import 'package:meta/meta.dart';
 
 import '../channel.dart';
 import '../in_app_purchase_ios_platform.dart';
+import 'sk_payment_queue_delegate_wrapper.dart';
 import 'sk_payment_transaction_wrappers.dart';
 import 'sk_product_wrapper.dart';
 
@@ -40,6 +43,7 @@
 
   static final SKPaymentQueueWrapper _singleton = SKPaymentQueueWrapper._();
 
+  SKPaymentQueueDelegateWrapper? _paymentQueueDelegate;
   SKTransactionObserverWrapper? _observer;
 
   /// Calls [`-[SKPaymentQueue transactions]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/1506026-transactions?language=objc)
@@ -70,18 +74,39 @@
   ///
   /// Call this method when the first listener is subscribed to the
   /// [InAppPurchaseIosPlatform.purchaseStream].
-  Future startObservingTransactionQueue() async =>
-      await channel.invokeListMethod<void>(
-          '-[SKPaymentQueue startObservingTransactionQueue]');
+  Future startObservingTransactionQueue() => channel
+      .invokeMethod<void>('-[SKPaymentQueue startObservingTransactionQueue]');
 
   /// Instructs the iOS implementation to remove the transaction observer and
   /// stop listening to it.
   ///
   /// Call this when there are no longer any listeners subscribed to the
   /// [InAppPurchaseIosPlatform.purchaseStream].
-  Future stopObservingTransactionQueue() async =>
-      await channel.invokeListMethod<void>(
-          '-[SKPaymentQueue stopObservingTransactionQueue]');
+  Future stopObservingTransactionQueue() => channel
+      .invokeMethod<void>('-[SKPaymentQueue stopObservingTransactionQueue]');
+
+  /// Sets an implementation of the [SKPaymentQueueDelegateWrapper].
+  ///
+  /// The [SKPaymentQueueDelegateWrapper] can be used to inform iOS how to
+  /// finish transactions when the storefront changes or if the price consent
+  /// sheet should be displayed when the price of a subscription has changed. If
+  /// no delegate is registered iOS will fallback to it's default configuration.
+  /// See the documentation on StoreKite's [`-[SKPaymentQueue delegate:]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc).
+  ///
+  /// When set to `null` the payment queue delegate will be removed and the
+  /// default behaviour will apply (see [documentation](https://developer.apple.com/documentation/storekit/skpaymentqueue/3182429-delegate?language=objc)).
+  Future setDelegate(SKPaymentQueueDelegateWrapper? delegate) async {
+    if (delegate == null) {
+      await channel.invokeMethod<void>('-[SKPaymentQueue removeDelegate]');
+      paymentQueueDelegateChannel.setMethodCallHandler(null);
+    } else {
+      await channel.invokeMethod<void>('-[SKPaymentQueue registerDelegate]');
+      paymentQueueDelegateChannel
+          .setMethodCallHandler(handlePaymentQueueDelegateCallbacks);
+    }
+
+    _paymentQueueDelegate = delegate;
+  }
 
   /// Posts a payment to the queue.
   ///
@@ -170,8 +195,21 @@
         '-[InAppPurchasePlugin presentCodeRedemptionSheet:result:]');
   }
 
+  /// Shows the price consent sheet if the user has not yet responded to a
+  /// subscription price change.
+  ///
+  /// Use this function when you have registered a [SKPaymentQueueDelegateWrapper]
+  /// (using the [setDelegate] method) and returned `false` when the
+  /// `SKPaymentQueueDelegateWrapper.shouldShowPriceConsent()` method was called.
+  ///
+  /// See documentation of StoreKit's [`-[SKPaymentQueue showPriceConsentIfNeeded]`](https://developer.apple.com/documentation/storekit/skpaymentqueue/3521327-showpriceconsentifneeded?language=objc).
+  Future<void> showPriceConsentIfNeeded() async {
+    await channel
+        .invokeMethod<void>('-[SKPaymentQueue showPriceConsentIfNeeded]');
+  }
+
   // Triage a method channel call from the platform and triggers the correct observer method.
-  Future<void> _handleObserverCallbacks(MethodCall call) async {
+  Future<dynamic> _handleObserverCallbacks(MethodCall call) async {
     assert(_observer != null,
         '[in_app_purchase]: (Fatal)The observer has not been set but we received a purchase transaction notification. Please ensure the observer has been set using `setTransactionObserver`. Make sure the observer is added right at the App Launch.');
     final SKTransactionObserverWrapper observer = _observer!;
@@ -235,6 +273,35 @@
           Map.castFrom<dynamic, dynamic, String, dynamic>(map));
     }).toList();
   }
+
+  /// Triage a method channel call from the platform and triggers the correct
+  /// payment queue delegate method.
+  ///
+  /// This method is public for testing purposes only and should not be used
+  /// outside this class.
+  @visibleForTesting
+  Future<dynamic> handlePaymentQueueDelegateCallbacks(MethodCall call) async {
+    assert(_paymentQueueDelegate != null,
+        '[in_app_purchase]: (Fatal)The payment queue delegate has not been set but we received a payment queue notification. Please ensure the payment queue has been set using `setDelegate`.');
+
+    final SKPaymentQueueDelegateWrapper delegate = _paymentQueueDelegate!;
+    switch (call.method) {
+      case 'shouldContinueTransaction':
+        final SKPaymentTransactionWrapper transaction =
+            SKPaymentTransactionWrapper.fromJson(call.arguments['transaction']);
+        final SKStorefrontWrapper storefront =
+            SKStorefrontWrapper.fromJson(call.arguments['storefront']);
+        return delegate.shouldContinueTransaction(transaction, storefront);
+      case 'shouldShowPriceConsent':
+        return delegate.shouldShowPriceConsent();
+      default:
+        break;
+    }
+    throw PlatformException(
+        code: 'no_such_callback',
+        message:
+            'Did not recognize the payment queue delegate callback ${call.method}.');
+  }
 }
 
 /// Dart wrapper around StoreKit's
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart
new file mode 100644
index 0000000..934fdea
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.dart
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:ui' show hashValues;
+
+import 'package:json_annotation/json_annotation.dart';
+
+part 'sk_storefront_wrapper.g.dart';
+
+/// Contains the location and unique identifier of an Apple App Store storefront.
+///
+/// Dart wrapper around StoreKit's
+/// [SKStorefront](https://developer.apple.com/documentation/storekit/skstorefront?language=objc).
+@JsonSerializable()
+class SKStorefrontWrapper {
+  /// Creates a new [SKStorefrontWrapper] with the provided information.
+  SKStorefrontWrapper({
+    required this.countryCode,
+    required this.identifier,
+  });
+
+  /// Constructs an instance of the [SKStorefrontWrapper] from a key value map
+  /// of data.
+  ///
+  /// The map needs to have named string keys with values matching the names and
+  /// types of all of the members on this class. The `map` parameter must not be
+  /// null.
+  factory SKStorefrontWrapper.fromJson(Map<String, dynamic> map) {
+    return _$SKStorefrontWrapperFromJson(map);
+  }
+
+  /// The three-letter code representing the country or region associated with
+  /// the App Store storefront.
+  final String countryCode;
+
+  /// A value defined by Apple that uniquely identifies an App Store storefront.
+  final String identifier;
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(other, this)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    final SKStorefrontWrapper typedOther = other as SKStorefrontWrapper;
+    return typedOther.countryCode == countryCode &&
+        typedOther.identifier == identifier;
+  }
+
+  @override
+  int get hashCode => hashValues(
+        this.countryCode,
+        this.identifier,
+      );
+
+  @override
+  String toString() => _$SKStorefrontWrapperToJson(this).toString();
+
+  /// Converts the instance to a key value map which can be used to serialize
+  /// to JSON format.
+  Map<String, dynamic> toMap() => _$SKStorefrontWrapperToJson(this);
+}
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.g.dart
new file mode 100644
index 0000000..f75cfc5
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/src/store_kit_wrappers/sk_storefront_wrapper.g.dart
@@ -0,0 +1,21 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'sk_storefront_wrapper.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+SKStorefrontWrapper _$SKStorefrontWrapperFromJson(Map json) {
+  return SKStorefrontWrapper(
+    countryCode: json['countryCode'] as String,
+    identifier: json['identifier'] as String,
+  );
+}
+
+Map<String, dynamic> _$SKStorefrontWrapperToJson(
+        SKStorefrontWrapper instance) =>
+    <String, dynamic>{
+      'countryCode': instance.countryCode,
+      'identifier': instance.identifier,
+    };
diff --git a/packages/in_app_purchase/in_app_purchase_ios/lib/store_kit_wrappers.dart b/packages/in_app_purchase/in_app_purchase_ios/lib/store_kit_wrappers.dart
index b687d23..09eb1ac 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/lib/store_kit_wrappers.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/lib/store_kit_wrappers.dart
@@ -2,8 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+export 'src/store_kit_wrappers/sk_payment_queue_delegate_wrapper.dart';
 export 'src/store_kit_wrappers/sk_payment_queue_wrapper.dart';
 export 'src/store_kit_wrappers/sk_payment_transaction_wrappers.dart';
 export 'src/store_kit_wrappers/sk_product_wrapper.dart';
 export 'src/store_kit_wrappers/sk_receipt_manager.dart';
 export 'src/store_kit_wrappers/sk_request_maker.dart';
+export 'src/store_kit_wrappers/sk_storefront_wrapper.dart';
diff --git a/packages/in_app_purchase/in_app_purchase_ios/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_ios/pubspec.yaml
index 5b9e389..00929d9 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_ios/pubspec.yaml
@@ -2,7 +2,7 @@
 description: An implementation for the iOS platform of the Flutter `in_app_purchase` plugin. This uses the iOS StoreKit Framework.
 repository: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase/in_app_purchase_ios
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22
-version: 0.1.0+2
+version: 0.1.1
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_methodchannel_apis_test.dart b/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
index edb50ae..6a01fe4 100644
--- a/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
+++ b/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_methodchannel_apis_test.dart
@@ -145,6 +145,20 @@
       await SKPaymentQueueWrapper().stopObservingTransactionQueue();
       expect(fakeIOSPlatform.queueIsActive, false);
     });
+
+    test('setDelegate should call methodChannel', () async {
+      expect(fakeIOSPlatform.isPaymentQueueDelegateRegistered, false);
+      await SKPaymentQueueWrapper().setDelegate(TestPaymentQueueDelegate());
+      expect(fakeIOSPlatform.isPaymentQueueDelegateRegistered, true);
+      await SKPaymentQueueWrapper().setDelegate(null);
+      expect(fakeIOSPlatform.isPaymentQueueDelegateRegistered, false);
+    });
+
+    test('showPriceConsentIfNeeded should call methodChannel', () async {
+      expect(fakeIOSPlatform.showPriceConsentIfNeeded, false);
+      await SKPaymentQueueWrapper().showPriceConsentIfNeeded();
+      expect(fakeIOSPlatform.showPriceConsentIfNeeded, true);
+    });
   });
 
   group('Code Redemption Sheet', () {
@@ -178,6 +192,12 @@
   // present Code Redemption
   bool presentCodeRedemption = false;
 
+  // show price consent sheet
+  bool showPriceConsentIfNeeded = false;
+
+  // indicate if the payment queue delegate is registered
+  bool isPaymentQueueDelegateRegistered = false;
+
   // Listen to purchase updates
   bool? queueIsActive;
 
@@ -230,11 +250,22 @@
       case '-[SKPaymentQueue stopObservingTransactionQueue]':
         queueIsActive = false;
         return Future<void>.sync(() {});
+      case '-[SKPaymentQueue registerDelegate]':
+        isPaymentQueueDelegateRegistered = true;
+        return Future<void>.sync(() {});
+      case '-[SKPaymentQueue removeDelegate]':
+        isPaymentQueueDelegateRegistered = false;
+        return Future<void>.sync(() {});
+      case '-[SKPaymentQueue showPriceConsentIfNeeded]':
+        showPriceConsentIfNeeded = true;
+        return Future<void>.sync(() {});
     }
     return Future.error('method not mocked');
   }
 }
 
+class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {}
+
 class TestPaymentTransactionObserver extends SKTransactionObserverWrapper {
   void updatedTransactions(
       {required List<SKPaymentTransactionWrapper> transactions}) {}
diff --git a/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart b/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart
new file mode 100644
index 0000000..b61411d
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_ios/test/store_kit_wrappers/sk_payment_queue_delegate_api_test.dart
@@ -0,0 +1,109 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:in_app_purchase_ios/src/channel.dart';
+import 'package:in_app_purchase_ios/store_kit_wrappers.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  final FakeIOSPlatform fakeIOSPlatform = FakeIOSPlatform();
+
+  setUpAll(() {
+    SystemChannels.platform
+        .setMockMethodCallHandler(fakeIOSPlatform.onMethodCall);
+  });
+
+  test(
+      'handlePaymentQueueDelegateCallbacks should call SKPaymentQueueDelegateWrapper.shouldContinueTransaction',
+      () async {
+    SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
+    TestPaymentQueueDelegate testDelegate = TestPaymentQueueDelegate();
+    await queue.setDelegate(testDelegate);
+
+    final Map<String, dynamic> arguments = <String, dynamic>{
+      'storefront': <String, String>{
+        'countryCode': 'USA',
+        'identifier': 'unique_identifier',
+      },
+      'transaction': <String, dynamic>{
+        'payment': <String, dynamic>{
+          'productIdentifier': 'product_identifier',
+        }
+      },
+    };
+
+    final result = await queue.handlePaymentQueueDelegateCallbacks(
+      MethodCall('shouldContinueTransaction', arguments),
+    );
+
+    expect(result, false);
+    expect(
+      testDelegate.log,
+      <Matcher>{
+        equals('shouldContinueTransaction'),
+      },
+    );
+  });
+
+  test(
+      'handlePaymentQueueDelegateCallbacks should call SKPaymentQueueDelegateWrapper.shouldShowPriceConsent',
+      () async {
+    SKPaymentQueueWrapper queue = SKPaymentQueueWrapper();
+    TestPaymentQueueDelegate testDelegate = TestPaymentQueueDelegate();
+    await queue.setDelegate(testDelegate);
+
+    final result = await queue.handlePaymentQueueDelegateCallbacks(
+      MethodCall('shouldShowPriceConsent'),
+    );
+
+    expect(result, false);
+    expect(
+      testDelegate.log,
+      <Matcher>{
+        equals('shouldShowPriceConsent'),
+      },
+    );
+  });
+}
+
+class TestPaymentQueueDelegate extends SKPaymentQueueDelegateWrapper {
+  final List<String> log = <String>[];
+
+  @override
+  bool shouldContinueTransaction(
+      SKPaymentTransactionWrapper transaction, SKStorefrontWrapper storefront) {
+    log.add('shouldContinueTransaction');
+    return false;
+  }
+
+  @override
+  bool shouldShowPriceConsent() {
+    log.add('shouldShowPriceConsent');
+    return false;
+  }
+}
+
+class FakeIOSPlatform {
+  FakeIOSPlatform() {
+    channel.setMockMethodCallHandler(onMethodCall);
+  }
+
+  // indicate if the payment queue delegate is registered
+  bool isPaymentQueueDelegateRegistered = false;
+
+  Future<dynamic> onMethodCall(MethodCall call) {
+    switch (call.method) {
+      case '-[SKPaymentQueue registerDelegate]':
+        isPaymentQueueDelegateRegistered = true;
+        return Future<void>.sync(() {});
+      case '-[SKPaymentQueue removeDelegate]':
+        isPaymentQueueDelegateRegistered = false;
+        return Future<void>.sync(() {});
+    }
+    return Future.error('method not mocked');
+  }
+}