Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 66972ed

Browse files
authored
[in_app_purchase] Implementation of platform interface (#3781)
1 parent b0c2742 commit 66972ed

17 files changed

+741
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## 1.0.0
2+
3+
* Initial open-source release.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright 2013 The Flutter Authors. All rights reserved.
2+
3+
Redistribution and use in source and binary forms, with or without modification,
4+
are permitted provided that the following conditions are met:
5+
6+
* Redistributions of source code must retain the above copyright
7+
notice, this list of conditions and the following disclaimer.
8+
* Redistributions in binary form must reproduce the above
9+
copyright notice, this list of conditions and the following
10+
disclaimer in the documentation and/or other materials provided
11+
with the distribution.
12+
* Neither the name of Google Inc. nor the names of its
13+
contributors may be used to endorse or promote products derived
14+
from this software without specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# in_app_purchase_platform_interface
2+
3+
A common platform interface for the [`in_app_purchase`][1] plugin.
4+
5+
This interface allows platform-specific implementations of the `in_app_purchase`
6+
plugin, as well as the plugin itself, to ensure they are supporting the
7+
same interface.
8+
9+
# Usage
10+
11+
To implement a new platform-specific implementation of `in_app_purchase`, extend
12+
[`InAppPurchasePlatform`][2] with an implementation that performs the
13+
platform-specific behavior, and when you register your plugin, set the default
14+
`InAppPurchasePlatform` by calling
15+
`InAppPurchasePlatform.setInstance(MyPlatformInAppPurchase())`.
16+
17+
To implement functionality that is specific to the platform and is not covered
18+
by the [`InAppPurchasePlatform`][2] idiomatic API, extend
19+
[`InAppPurchasePlatformAddition`][3] with the platform-specific functionality,
20+
and when the plugin is registered, set the addition instance by calling
21+
`InAppPurchasePlatformAddition.instance = MyPlatformInAppPurchaseAddition()`.
22+
23+
# Note on breaking changes
24+
25+
Strongly prefer non-breaking changes (such as adding a method to the interface)
26+
over breaking changes for this package.
27+
28+
See https://flutter.dev/go/platform-interface-breaking-changes for a discussion
29+
on why a less-clean interface is preferable to a breaking change.
30+
31+
[1]: ../in_app_purchase
32+
[2]: lib/in_app_purchase_platform_interface.dart
33+
[3]: lib/in_app_purchase_platform_addition.dart
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
export 'src/in_app_purchase_platform.dart';
6+
export 'src/in_app_purchase_platform_addition.dart';
7+
export 'src/in_app_purchase_platform_addition_provider.dart';
8+
export 'src/types/types.dart';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'dart:async';
6+
7+
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
8+
9+
import 'types/types.dart';
10+
11+
/// The interface that implementations of in_app_purchase must implement.
12+
///
13+
/// Platform implementations should extend this class rather than implement it as `in_app_purchase`
14+
/// does not consider newly added methods to be breaking changes. Extending this class
15+
/// (using `extends`) ensures that the subclass will get the default implementation, while
16+
/// platform implementations that `implements` this interface will be broken by newly added
17+
/// [InAppPurchasePlatform] methods.
18+
abstract class InAppPurchasePlatform extends PlatformInterface {
19+
/// Constructs a InAppPurchasePlatform.
20+
InAppPurchasePlatform() : super(token: _token);
21+
22+
static final Object _token = Object();
23+
24+
/// The instance of [InAppPurchasePlatform] to use.
25+
///
26+
/// Defaults to `null`.
27+
static InAppPurchasePlatform? get instance => _instance;
28+
29+
static InAppPurchasePlatform? _instance;
30+
31+
/// Platform-specific plugins should set this with their own platform-specific
32+
/// class that extends [InAppPurchasePlatform] when they register themselves.
33+
// TODO(amirh): Extract common platform interface logic.
34+
// https://github.com/flutter/flutter/issues/43368
35+
static void setInstance(InAppPurchasePlatform instance) {
36+
PlatformInterface.verifyToken(instance, _token);
37+
_instance = instance;
38+
}
39+
40+
/// Listen to this broadcast stream to get real time update for purchases.
41+
///
42+
/// This stream will never close as long as the app is active.
43+
///
44+
/// Purchase updates can happen in several situations:
45+
/// * When a purchase is triggered by user in the app.
46+
/// * When a purchase is triggered by user from the platform specific store front.
47+
/// * When a purchase is restored on the device by the user in the app.
48+
/// * If a purchase is not completed ([completePurchase] is not called on the
49+
/// purchase object) from the last app session. Purchase updates will happen
50+
/// when a new app session starts instead.
51+
///
52+
/// IMPORTANT! You must subscribe to this stream as soon as your app launches,
53+
/// preferably before returning your main App Widget in main(). Otherwise you
54+
/// will miss purchase updated made before this stream is subscribed to.
55+
///
56+
/// We also recommend listening to the stream with one subscription at a given
57+
/// time. If you choose to have multiple subscription at the same time, you
58+
/// should be careful at the fact that each subscription will receive all the
59+
/// events after they start to listen.
60+
Stream<List<PurchaseDetails>> get purchaseStream =>
61+
throw UnimplementedError('purchaseStream has not been implemented.');
62+
63+
/// Returns `true` if the payment platform is ready and available.
64+
Future<bool> isAvailable() =>
65+
throw UnimplementedError('isAvailable() has not been implemented.');
66+
67+
/// Query product details for the given set of IDs.
68+
///
69+
/// Identifiers in the underlying payment platform, for example, [App Store
70+
/// Connect](https://appstoreconnect.apple.com/) for iOS and [Google Play
71+
/// Console](https://play.google.com/) for Android.
72+
Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) =>
73+
throw UnimplementedError(
74+
'queryProductDetails() had not been implemented.');
75+
76+
/// Buy a non consumable product or subscription.
77+
///
78+
/// Non consumable items can only be bought once. For example, a purchase that
79+
/// unlocks a special content in your app. Subscriptions are also non
80+
/// consumable products.
81+
///
82+
/// You always need to restore all the non consumable products for user when
83+
/// they switch their phones.
84+
///
85+
/// This method does not return the result of the purchase. Instead, after
86+
/// triggering this method, purchase updates will be sent to
87+
/// [purchaseStream]. You should [Stream.listen] to [purchaseStream] to get
88+
/// [PurchaseDetails] objects in different [PurchaseDetails.status] and update
89+
/// your UI accordingly. When the [PurchaseDetails.status] is
90+
/// [PurchaseStatus.purchased], [PurchaseStatus.restored] or
91+
/// [PurchaseStatus.error] you should deliver the content or handle the error,
92+
/// then call [completePurchase] to finish the purchasing process.
93+
///
94+
/// This method does return whether or not the purchase request was initially
95+
/// sent successfully.
96+
///
97+
/// Consumable items are defined differently by the different underlying
98+
/// payment platforms, and there's no way to query for whether or not the
99+
/// [ProductDetail] is a consumable at runtime.
100+
///
101+
/// See also:
102+
///
103+
/// * [buyConsumable], for buying a consumable product.
104+
/// * [restorePurchases], for restoring non consumable products.
105+
///
106+
/// Calling this method for consumable items will cause unwanted behaviors!
107+
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) =>
108+
throw UnimplementedError('buyNonConsumable() has not been implemented.');
109+
110+
/// Buy a consumable product.
111+
///
112+
/// Consumable items can be "consumed" to mark that they've been used and then
113+
/// bought additional times. For example, a health potion.
114+
///
115+
/// To restore consumable purchases across devices, you should keep track of
116+
/// those purchase on your own server and restore the purchase for your users.
117+
/// Consumed products are no longer considered to be "owned" by payment
118+
/// platforms and will not be delivered by calling [restorePurchases].
119+
///
120+
/// Consumable items are defined differently by the different underlying
121+
/// payment platforms, and there's no way to query for whether or not the
122+
/// [ProductDetail] is a consumable at runtime.
123+
///
124+
/// `autoConsume` is provided as a utility and will instruct the plugin to
125+
/// automatically consume the product after a succesful purchase.
126+
/// `autoConsume` is `true` by default.
127+
///
128+
/// This method does not return the result of the purchase. Instead, after
129+
/// triggering this method, purchase updates will be sent to
130+
/// [purchaseStream]. You should [Stream.listen] to
131+
/// [purchaseStream] to get [PurchaseDetails] objects in different
132+
/// [PurchaseDetails.status] and update your UI accordingly. When the
133+
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or
134+
/// [PurchaseStatus.error], you should deliver the content or handle the
135+
/// error, then call [completePurchase] to finish the purchasing process.
136+
///
137+
/// This method does return whether or not the purchase request was initially
138+
/// sent succesfully.
139+
///
140+
/// See also:
141+
///
142+
/// * [buyNonConsumable], for buying a non consumable product or
143+
/// subscription.
144+
/// * [restorePurchases], for restoring non consumable products.
145+
///
146+
/// Calling this method for non consumable items will cause unwanted
147+
/// behaviors!
148+
Future<bool> buyConsumable({
149+
required PurchaseParam purchaseParam,
150+
bool autoConsume = true,
151+
}) =>
152+
throw UnimplementedError('buyConsumable() has not been implemented.');
153+
154+
/// Mark that purchased content has been delivered to the user.
155+
///
156+
/// You are responsible for completing every [PurchaseDetails] whose
157+
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or
158+
/// [PurchaseStatus.restored].
159+
/// Completing a [PurchaseStatus.pending] purchase will cause an exception.
160+
/// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a
161+
/// purchase is pending for completion.
162+
///
163+
/// The method will throw a [PurchaseException] when the purchase could not be
164+
/// finished. Depending on the [PurchaseException.errorCode] the developer
165+
/// should try to complete the purchase via this method again, or retry the
166+
/// [completePurchase] method at a later time. If the
167+
/// [PurchaseException.errorCode] indicates you should not retry there might
168+
/// be some issue with the app's code or the configuration of the app in the
169+
/// respective store. The developer is responsible to fix this issue. The
170+
/// [PurchaseException.message] field might provide more information on what
171+
/// went wrong.
172+
Future<void> completePurchase(PurchaseDetails purchase) =>
173+
throw UnimplementedError('completePurchase() has not been implemented.');
174+
175+
/// Restore all previous purchases.
176+
///
177+
/// The `applicationUserName` should match whatever was sent in the initial
178+
/// `PurchaseParam`, if anything. If no `applicationUserName` was specified in the initial
179+
/// `PurchaseParam`, use `null`.
180+
///
181+
/// Restored purchases are delivered through the [purchaseStream] with a
182+
/// status of [PurchaseStatus.restored]. You should listen for these purchases,
183+
/// validate their receipts, deliver the content and mark the purchase complete
184+
/// by calling the [finishPurchase] method for each purchase.
185+
///
186+
/// This does not return consumed products. If you want to restore unused
187+
/// consumable products, you need to persist consumable product information
188+
/// for your user on your own server.
189+
///
190+
/// See also:
191+
///
192+
/// * [refreshPurchaseVerificationData], for reloading failed
193+
/// [PurchaseDetails.verificationData].
194+
Future<void> restorePurchases({String? applicationUserName}) =>
195+
throw UnimplementedError('restorePurchases() has not been implemented.');
196+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore: avoid_classes_with_only_static_members
6+
/// The interface that platform implementations must implement when they want to
7+
/// provide platform specific in_app_purchase features.
8+
abstract class InAppPurchasePlatformAddition {
9+
/// The instance containing the platform-specific in_app_purchase
10+
/// functionality.
11+
///
12+
/// To implement additional functionality extend
13+
/// [`InAppPurchasePlatformAddition`][3] with the platform-specific
14+
/// functionality, and when the plugin is registered, set the
15+
/// `InAppPurchasePlatformAddition.instance` with the new addition
16+
/// implementationinstance.
17+
///
18+
/// Example implementation might look like this:
19+
/// ```dart
20+
/// class InAppPurchaseMyPlatformAddition extends InAppPurchasePlatformAddition {
21+
/// Future<void> myPlatformMethod() {}
22+
/// }
23+
/// ```
24+
///
25+
/// The following snippit shows how to register the `InAppPurchaseMyPlatformAddition`:
26+
/// ```dart
27+
/// class InAppPurchaseMyPlatformPlugin {
28+
/// static void registerWith(Registrar registrar) {
29+
/// // Register the platform-specific implementation of the idiomatic
30+
/// // InAppPurchase API.
31+
/// InAppPurchasePlatform.instance = InAppPurchaseMyPlatformPlugin();
32+
///
33+
/// // Register the [InAppPurchaseMyPlatformAddition] containing the
34+
/// // platform-specific functionality.
35+
/// InAppPurchasePlatformAddition.instance = InAppPurchaseMyPlatformAddition();
36+
/// }
37+
/// }
38+
/// ```
39+
static InAppPurchasePlatformAddition? instance;
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:in_app_purchase_platform_interface/src/in_app_purchase_platform_addition.dart';
6+
7+
/// The [InAppPurchasePlatformAdditionProvider] is responsible for providing
8+
/// a platform specific [InAppPurchasePlatformAddition].
9+
///
10+
/// [InAppPurchasePlatformAddition] implementation contain platform specific
11+
/// features that are not available from the platform idiomatic
12+
/// [InAppPurchasePlatform] API.
13+
abstract class InAppPurchasePlatformAdditionProvider {
14+
/// Provides a platform specific implementation of the [InAppPurchasePlatformAddition]
15+
/// class.
16+
T getPlatformAddition<T extends InAppPurchasePlatformAddition>();
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// Captures an error from the underlying purchase platform.
6+
///
7+
/// The error can happen during the purchase, restoring a purchase, or querying product.
8+
/// Errors from restoring a purchase are not indicative of any errors during the original purchase.
9+
/// See also:
10+
/// * [ProductDetailsResponse] for error when querying product details.
11+
/// * [PurchaseDetails] for error happened in purchase.
12+
class IAPError {
13+
/// Creates a new IAP error object with the given error details.
14+
IAPError(
15+
{required this.source,
16+
required this.code,
17+
required this.message,
18+
this.details});
19+
20+
/// Which source is the error on.
21+
final String source;
22+
23+
/// The error code.
24+
final String code;
25+
26+
/// A human-readable error message.
27+
final String message;
28+
29+
/// Error details, possibly null.
30+
final dynamic details;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
/// The class represents the information of a product.
6+
class ProductDetails {
7+
/// Creates a new product details object with the provided details.
8+
ProductDetails({
9+
required this.id,
10+
required this.title,
11+
required this.description,
12+
required this.price,
13+
});
14+
15+
/// The identifier of the product.
16+
///
17+
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.
18+
final String id;
19+
20+
/// The title of the product.
21+
///
22+
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.
23+
final String title;
24+
25+
/// The description of the product.
26+
///
27+
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.
28+
final String description;
29+
30+
/// The price of the product, formatted with currency symbol ("$0.99").
31+
///
32+
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console.
33+
final String price;
34+
}

0 commit comments

Comments
 (0)