This repository was archived by the owner on Feb 22, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[in_app_purchase] Implementation of platform interface #3781
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
652e5d1
Draft implementation of platform interface
mvanbeusekom bcb9d38
Added finishPurchase and restorePurchase definitions
mvanbeusekom 23230c9
Fix formatting and analysis warnings
mvanbeusekom 8f17a42
Added missing license header
mvanbeusekom 164700d
Fix analysis warnings
mvanbeusekom af8a388
Fix typo
mvanbeusekom a7693bd
Remove NoopInAppPurchase implementation
mvanbeusekom b80a3c6
Apply feedback from PR
mvanbeusekom 12943f4
Removed obsolete PurchaseException
mvanbeusekom 7ec5de6
Updated documentation per feedback
mvanbeusekom 3ceef85
Fixed formatting
mvanbeusekom efc6120
Allow nullable instance
mvanbeusekom e7f6216
Updated readme to reflect setInstance method
mvanbeusekom 24d305b
Removed platform specific comments
mvanbeusekom d86ac55
Add interfaces to support InAppPurchaseAddition
mvanbeusekom 9eb358f
Document the addition functionality in README
mvanbeusekom 33d1118
Added example code and documentation
mvanbeusekom File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
3 changes: 3 additions & 0 deletions
3
packages/in_app_purchase/in_app_purchase_platform_interface/CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
## 1.0.0 | ||
|
||
* Initial open-source release. |
25 changes: 25 additions & 0 deletions
25
packages/in_app_purchase/in_app_purchase_platform_interface/LICENSE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Copyright 2013 The Flutter Authors. All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without modification, | ||
are permitted provided that the following conditions are met: | ||
|
||
* Redistributions of source code must retain the above copyright | ||
notice, this list of conditions and the following disclaimer. | ||
* Redistributions in binary form must reproduce the above | ||
copyright notice, this list of conditions and the following | ||
disclaimer in the documentation and/or other materials provided | ||
with the distribution. | ||
* Neither the name of Google Inc. nor the names of its | ||
contributors may be used to endorse or promote products derived | ||
from this software without specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
33 changes: 33 additions & 0 deletions
33
packages/in_app_purchase/in_app_purchase_platform_interface/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# in_app_purchase_platform_interface | ||
|
||
A common platform interface for the [`in_app_purchase`][1] plugin. | ||
|
||
This interface allows platform-specific implementations of the `in_app_purchase` | ||
plugin, as well as the plugin itself, to ensure they are supporting the | ||
same interface. | ||
|
||
# Usage | ||
|
||
To implement a new platform-specific implementation of `in_app_purchase`, extend | ||
[`InAppPurchasePlatform`][2] with an implementation that performs the | ||
platform-specific behavior, and when you register your plugin, set the default | ||
`InAppPurchasePlatform` by calling | ||
`InAppPurchasePlatform.setInstance(MyPlatformInAppPurchase())`. | ||
|
||
To implement functionality that is specific to the platform and is not covered | ||
by the [`InAppPurchasePlatform`][2] idiomatic API, extend | ||
[`InAppPurchasePlatformAddition`][3] with the platform-specific functionality, | ||
and when the plugin is registered, set the addition instance by calling | ||
`InAppPurchasePlatformAddition.instance = MyPlatformInAppPurchaseAddition()`. | ||
|
||
# Note on breaking changes | ||
|
||
Strongly prefer non-breaking changes (such as adding a method to the interface) | ||
over breaking changes for this package. | ||
|
||
See https://flutter.dev/go/platform-interface-breaking-changes for a discussion | ||
on why a less-clean interface is preferable to a breaking change. | ||
|
||
[1]: ../in_app_purchase | ||
[2]: lib/in_app_purchase_platform_interface.dart | ||
[3]: lib/in_app_purchase_platform_addition.dart |
8 changes: 8 additions & 0 deletions
8
...p_purchase/in_app_purchase_platform_interface/lib/in_app_purchase_platform_interface.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
// 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 'src/in_app_purchase_platform.dart'; | ||
export 'src/in_app_purchase_platform_addition.dart'; | ||
export 'src/in_app_purchase_platform_addition_provider.dart'; | ||
export 'src/types/types.dart'; |
196 changes: 196 additions & 0 deletions
196
.../in_app_purchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
// 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:async'; | ||
|
||
import 'package:plugin_platform_interface/plugin_platform_interface.dart'; | ||
|
||
import 'types/types.dart'; | ||
|
||
/// The interface that implementations of in_app_purchase must implement. | ||
/// | ||
/// Platform implementations should extend this class rather than implement it as `in_app_purchase` | ||
/// does not consider newly added methods to be breaking changes. Extending this class | ||
/// (using `extends`) ensures that the subclass will get the default implementation, while | ||
/// platform implementations that `implements` this interface will be broken by newly added | ||
/// [InAppPurchasePlatform] methods. | ||
abstract class InAppPurchasePlatform extends PlatformInterface { | ||
/// Constructs a InAppPurchasePlatform. | ||
InAppPurchasePlatform() : super(token: _token); | ||
|
||
static final Object _token = Object(); | ||
|
||
/// The instance of [InAppPurchasePlatform] to use. | ||
/// | ||
/// Defaults to `null`. | ||
static InAppPurchasePlatform? get instance => _instance; | ||
|
||
static InAppPurchasePlatform? _instance; | ||
|
||
/// Platform-specific plugins should set this with their own platform-specific | ||
/// class that extends [InAppPurchasePlatform] when they register themselves. | ||
// TODO(amirh): Extract common platform interface logic. | ||
// https://github.com/flutter/flutter/issues/43368 | ||
static void setInstance(InAppPurchasePlatform instance) { | ||
PlatformInterface.verifyToken(instance, _token); | ||
_instance = instance; | ||
} | ||
|
||
/// Listen to this broadcast stream to get real time update for purchases. | ||
/// | ||
/// This stream will never close as long as the app is active. | ||
/// | ||
/// Purchase updates can happen in several situations: | ||
/// * When a purchase is triggered by user in the app. | ||
/// * When a purchase is triggered by user from the platform specific store front. | ||
/// * When a purchase is restored on the device by the user in the app. | ||
/// * If a purchase is not completed ([completePurchase] is not called on the | ||
/// purchase object) from the last app session. Purchase updates will happen | ||
/// when a new app session starts instead. | ||
/// | ||
/// IMPORTANT! You must subscribe to this stream as soon as your app launches, | ||
/// preferably before returning your main App Widget in main(). Otherwise you | ||
/// will miss purchase updated made before this stream is subscribed to. | ||
/// | ||
/// We also recommend listening to the stream with one subscription at a given | ||
/// time. If you choose to have multiple subscription at the same time, you | ||
/// should be careful at the fact that each subscription will receive all the | ||
/// events after they start to listen. | ||
Stream<List<PurchaseDetails>> get purchaseStream => | ||
throw UnimplementedError('purchaseStream has not been implemented.'); | ||
|
||
/// Returns `true` if the payment platform is ready and available. | ||
Future<bool> isAvailable() => | ||
throw UnimplementedError('isAvailable() has not been implemented.'); | ||
|
||
/// Query product details for the given set of IDs. | ||
/// | ||
/// Identifiers in the underlying payment platform, for example, [App Store | ||
/// Connect](https://appstoreconnect.apple.com/) for iOS and [Google Play | ||
/// Console](https://play.google.com/) for Android. | ||
Future<ProductDetailsResponse> queryProductDetails(Set<String> identifiers) => | ||
throw UnimplementedError( | ||
'queryProductDetails() had not been implemented.'); | ||
|
||
/// Buy a non consumable product or subscription. | ||
/// | ||
/// Non consumable items can only be bought once. For example, a purchase that | ||
/// unlocks a special content in your app. Subscriptions are also non | ||
/// consumable products. | ||
/// | ||
/// You always need to restore all the non consumable products for user when | ||
/// they switch their phones. | ||
/// | ||
/// This method does not return the result of the purchase. Instead, after | ||
/// triggering this method, purchase updates will be sent to | ||
/// [purchaseStream]. You should [Stream.listen] to [purchaseStream] to get | ||
/// [PurchaseDetails] objects in different [PurchaseDetails.status] and update | ||
/// your UI accordingly. When the [PurchaseDetails.status] is | ||
/// [PurchaseStatus.purchased], [PurchaseStatus.restored] or | ||
/// [PurchaseStatus.error] you should deliver the content or handle the error, | ||
/// then call [completePurchase] to finish the purchasing process. | ||
/// | ||
/// This method does return whether or not the purchase request was initially | ||
/// sent successfully. | ||
/// | ||
/// Consumable items are defined differently by the different underlying | ||
/// payment platforms, and there's no way to query for whether or not the | ||
/// [ProductDetail] is a consumable at runtime. | ||
/// | ||
/// See also: | ||
/// | ||
/// * [buyConsumable], for buying a consumable product. | ||
/// * [restorePurchases], for restoring non consumable products. | ||
/// | ||
/// Calling this method for consumable items will cause unwanted behaviors! | ||
Future<bool> buyNonConsumable({required PurchaseParam purchaseParam}) => | ||
throw UnimplementedError('buyNonConsumable() has not been implemented.'); | ||
|
||
/// Buy a consumable product. | ||
/// | ||
/// Consumable items can be "consumed" to mark that they've been used and then | ||
/// bought additional times. For example, a health potion. | ||
/// | ||
/// To restore consumable purchases across devices, you should keep track of | ||
/// those purchase on your own server and restore the purchase for your users. | ||
/// Consumed products are no longer considered to be "owned" by payment | ||
/// platforms and will not be delivered by calling [restorePurchases]. | ||
/// | ||
/// Consumable items are defined differently by the different underlying | ||
/// payment platforms, and there's no way to query for whether or not the | ||
/// [ProductDetail] is a consumable at runtime. | ||
/// | ||
/// `autoConsume` is provided as a utility and will instruct the plugin to | ||
/// automatically consume the product after a succesful purchase. | ||
/// `autoConsume` is `true` by default. | ||
/// | ||
/// This method does not return the result of the purchase. Instead, after | ||
/// triggering this method, purchase updates will be sent to | ||
/// [purchaseStream]. You should [Stream.listen] to | ||
/// [purchaseStream] to get [PurchaseDetails] objects in different | ||
/// [PurchaseDetails.status] and update your UI accordingly. When the | ||
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or | ||
/// [PurchaseStatus.error], you should deliver the content or handle the | ||
/// error, then call [completePurchase] to finish the purchasing process. | ||
/// | ||
/// This method does return whether or not the purchase request was initially | ||
/// sent succesfully. | ||
/// | ||
/// See also: | ||
/// | ||
/// * [buyNonConsumable], for buying a non consumable product or | ||
/// subscription. | ||
/// * [restorePurchases], for restoring non consumable products. | ||
/// | ||
/// Calling this method for non consumable items will cause unwanted | ||
/// behaviors! | ||
Future<bool> buyConsumable({ | ||
required PurchaseParam purchaseParam, | ||
bool autoConsume = true, | ||
}) => | ||
throw UnimplementedError('buyConsumable() has not been implemented.'); | ||
|
||
/// Mark that purchased content has been delivered to the user. | ||
/// | ||
/// You are responsible for completing every [PurchaseDetails] whose | ||
/// [PurchaseDetails.status] is [PurchaseStatus.purchased] or | ||
/// [PurchaseStatus.restored]. | ||
/// Completing a [PurchaseStatus.pending] purchase will cause an exception. | ||
/// For convenience, [PurchaseDetails.pendingCompletePurchase] indicates if a | ||
/// purchase is pending for completion. | ||
/// | ||
/// The method will throw a [PurchaseException] when the purchase could not be | ||
/// finished. Depending on the [PurchaseException.errorCode] the developer | ||
/// should try to complete the purchase via this method again, or retry the | ||
/// [completePurchase] method at a later time. If the | ||
/// [PurchaseException.errorCode] indicates you should not retry there might | ||
/// be some issue with the app's code or the configuration of the app in the | ||
/// respective store. The developer is responsible to fix this issue. The | ||
/// [PurchaseException.message] field might provide more information on what | ||
/// went wrong. | ||
Future<void> completePurchase(PurchaseDetails purchase) => | ||
throw UnimplementedError('completePurchase() has not been implemented.'); | ||
|
||
/// Restore all previous purchases. | ||
/// | ||
/// The `applicationUserName` should match whatever was sent in the initial | ||
/// `PurchaseParam`, if anything. If no `applicationUserName` was specified in the initial | ||
/// `PurchaseParam`, use `null`. | ||
/// | ||
/// Restored purchases are delivered through the [purchaseStream] with a | ||
/// status of [PurchaseStatus.restored]. You should listen for these purchases, | ||
/// validate their receipts, deliver the content and mark the purchase complete | ||
/// by calling the [finishPurchase] method for each purchase. | ||
/// | ||
/// This does not return consumed products. If you want to restore unused | ||
/// consumable products, you need to persist consumable product information | ||
/// for your user on your own server. | ||
/// | ||
/// See also: | ||
/// | ||
/// * [refreshPurchaseVerificationData], for reloading failed | ||
/// [PurchaseDetails.verificationData]. | ||
Future<void> restorePurchases({String? applicationUserName}) => | ||
throw UnimplementedError('restorePurchases() has not been implemented.'); | ||
} |
40 changes: 40 additions & 0 deletions
40
...urchase/in_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// 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. | ||
|
||
// ignore: avoid_classes_with_only_static_members | ||
/// The interface that platform implementations must implement when they want to | ||
/// provide platform specific in_app_purchase features. | ||
abstract class InAppPurchasePlatformAddition { | ||
/// The instance containing the platform-specific in_app_purchase | ||
/// functionality. | ||
/// | ||
/// To implement additional functionality extend | ||
/// [`InAppPurchasePlatformAddition`][3] with the platform-specific | ||
/// functionality, and when the plugin is registered, set the | ||
/// `InAppPurchasePlatformAddition.instance` with the new addition | ||
/// implementationinstance. | ||
/// | ||
/// Example implementation might look like this: | ||
/// ```dart | ||
/// class InAppPurchaseMyPlatformAddition extends InAppPurchasePlatformAddition { | ||
/// Future<void> myPlatformMethod() {} | ||
/// } | ||
/// ``` | ||
/// | ||
/// The following snippit shows how to register the `InAppPurchaseMyPlatformAddition`: | ||
/// ```dart | ||
/// class InAppPurchaseMyPlatformPlugin { | ||
/// static void registerWith(Registrar registrar) { | ||
/// // Register the platform-specific implementation of the idiomatic | ||
/// // InAppPurchase API. | ||
/// InAppPurchasePlatform.instance = InAppPurchaseMyPlatformPlugin(); | ||
/// | ||
/// // Register the [InAppPurchaseMyPlatformAddition] containing the | ||
/// // platform-specific functionality. | ||
/// InAppPurchasePlatformAddition.instance = InAppPurchaseMyPlatformAddition(); | ||
/// } | ||
/// } | ||
/// ``` | ||
static InAppPurchasePlatformAddition? instance; | ||
} |
17 changes: 17 additions & 0 deletions
17
...n_app_purchase_platform_interface/lib/src/in_app_purchase_platform_addition_provider.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// 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_platform_interface/src/in_app_purchase_platform_addition.dart'; | ||
|
||
/// The [InAppPurchasePlatformAdditionProvider] is responsible for providing | ||
/// a platform specific [InAppPurchasePlatformAddition]. | ||
/// | ||
/// [InAppPurchasePlatformAddition] implementation contain platform specific | ||
/// features that are not available from the platform idiomatic | ||
/// [InAppPurchasePlatform] API. | ||
abstract class InAppPurchasePlatformAdditionProvider { | ||
/// Provides a platform specific implementation of the [InAppPurchasePlatformAddition] | ||
/// class. | ||
T getPlatformAddition<T extends InAppPurchasePlatformAddition>(); | ||
} |
31 changes: 31 additions & 0 deletions
31
..._app_purchase/in_app_purchase_platform_interface/lib/src/types/in_app_purchase_error.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// 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. | ||
|
||
/// Captures an error from the underlying purchase platform. | ||
/// | ||
/// The error can happen during the purchase, restoring a purchase, or querying product. | ||
/// Errors from restoring a purchase are not indicative of any errors during the original purchase. | ||
/// See also: | ||
/// * [ProductDetailsResponse] for error when querying product details. | ||
/// * [PurchaseDetails] for error happened in purchase. | ||
class IAPError { | ||
/// Creates a new IAP error object with the given error details. | ||
IAPError( | ||
{required this.source, | ||
required this.code, | ||
required this.message, | ||
this.details}); | ||
|
||
/// Which source is the error on. | ||
final String source; | ||
mvanbeusekom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// The error code. | ||
final String code; | ||
|
||
/// A human-readable error message. | ||
final String message; | ||
|
||
/// Error details, possibly null. | ||
final dynamic details; | ||
} |
34 changes: 34 additions & 0 deletions
34
...ges/in_app_purchase/in_app_purchase_platform_interface/lib/src/types/product_details.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// 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. | ||
|
||
/// The class represents the information of a product. | ||
class ProductDetails { | ||
/// Creates a new product details object with the provided details. | ||
ProductDetails({ | ||
required this.id, | ||
required this.title, | ||
required this.description, | ||
required this.price, | ||
}); | ||
|
||
/// The identifier of the product. | ||
/// | ||
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. | ||
final String id; | ||
|
||
/// The title of the product. | ||
/// | ||
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. | ||
final String title; | ||
|
||
/// The description of the product. | ||
/// | ||
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. | ||
final String description; | ||
|
||
/// The price of the product, formatted with currency symbol ("$0.99"). | ||
/// | ||
/// For example, on iOS it is specified in App Store Connect; on Android, it is specified in Google Play Console. | ||
final String price; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's also add doc for how to add additional features in a platform
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done