Add InAppPurchaseException to platform interface (#3852)

diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart
index 9e12a9d..25eb4a4 100644
--- a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart
+++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+export 'src/errors/errors.dart';
 export 'src/in_app_purchase_platform.dart';
 export 'src/in_app_purchase_platform_addition.dart';
 export 'src/in_app_purchase_platform_addition_provider.dart';
diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/errors.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/errors.dart
new file mode 100644
index 0000000..95a7703
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/errors.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'errors.dart';
diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/in_app_purchase_exception.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/in_app_purchase_exception.dart
new file mode 100644
index 0000000..0a89a6e
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_platform_interface/lib/src/errors/in_app_purchase_exception.dart
@@ -0,0 +1,27 @@
+// 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.
+
+/// Thrown to indicate that an action failed while interacting with the
+/// in_app_purchase plugin.
+class InAppPurchaseException implements Exception {
+  /// Creates a [InAppPurchaseException] with the specified source and error
+  /// [code] and optional [message].
+  InAppPurchaseException({
+    required this.source,
+    required this.code,
+    this.message,
+  }) : assert(code != null);
+
+  /// An error code.
+  final String code;
+
+  /// A human-readable error message, possibly null.
+  final String? message;
+
+  /// Which source is the error on.
+  final String source;
+
+  @override
+  String toString() => 'InAppPurchaseException($code, $message, $source)';
+}
diff --git a/packages/in_app_purchase/in_app_purchase_platform_interface/test/src/errors/in_app_purchase_exception_test.dart b/packages/in_app_purchase/in_app_purchase_platform_interface/test/src/errors/in_app_purchase_exception_test.dart
new file mode 100644
index 0000000..ff9468e
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_platform_interface/test/src/errors/in_app_purchase_exception_test.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:flutter_test/flutter_test.dart';
+import 'package:in_app_purchase_platform_interface/src/errors/in_app_purchase_exception.dart';
+
+void main() {
+  test('toString: Should return a description of the exception', () {
+    final InAppPurchaseException exception = InAppPurchaseException(
+      code: 'error_code',
+      message: 'dummy message',
+      source: 'dummy_source',
+    );
+
+    // Act
+    final String actual = exception.toString();
+
+    // Assert
+    expect(actual,
+        'InAppPurchaseException(error_code, dummy message, dummy_source)');
+  });
+}