Skip to content

Commit c8e4792

Browse files
mvanbeusekomfotiDim
authored andcommitted
[in_app_purchase] Implementation of the app facing package (flutter#3877)
* Start with app-facing package * Update to pub version of platform interface * Update CHANGELOG with feedback from PR * Fix some spelling mistakes * Update CHANGELOG with feedback from PR * Update README with new features * Update dependencies and links in documentation * Remove iOS test from example project * Remove test target from Podfile * Remove test from Xcode scheme
1 parent ffb93b8 commit c8e4792

File tree

86 files changed

+556
-11142
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+556
-11142
lines changed

packages/in_app_purchase/in_app_purchase/CHANGELOG.md

+32
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,35 @@
1+
## 0.6.0
2+
3+
As part of implementing federated architecture and making the interface compatible for other platforms this version contains the following **breaking changes**:
4+
5+
* Changes to the platform agnostic interface:
6+
* If you used `InAppPurchaseConnection.instance` to access generic In App Purchase APIs, please use `InAppPurchase.instance` instead;
7+
* The `InAppPurchaseConnection.purchaseUpdatedStream` has been renamed to `InAppPurchase.purchaseStream`;
8+
* The `InAppPurchaseConnection.queryPastPurchases` method has been removed. Instead, you should use `InAppPurchase.restorePurchases`. This method emits each restored purchase on the `InAppPurchase.purchaseStream`, the `PurchaseDetails` object will be marked with a `status` of `PurchaseStatus.restored`;
9+
* The `InAppPurchase.completePurchase` method no longer returns an instance `BillingWrapperResult` class (which was Android specific). Instead it will return a completed `Future` if the method executed successfully, in case of errors it will complete with an `InAppPurchaseException` describing the error.
10+
* Android specific changes:
11+
* The Android specific `InAppPurchaseConnection.consumePurchase` and `InAppPurchaseConnection.enablePendingPurchases` methods have been removed from the platform agnostic interface and moved to the Android specific `InAppPurchaseAndroidPlatformAddition` class:
12+
* `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases` is a static method that should be called when initializing your App. Access the method like this: `InAppPurchaseAndroidPlatformAddition.enablePendingPurchases()` (make sure to add the following import: `import 'package:in_app_purchase_android/in_app_purchase_android.dart';`);
13+
* To use the `InAppPurchaseAndroidPlatformAddition.consumePurchase` method, acquire an instance using the `InAppPurchase.getPlatformAddition` method. For example:
14+
```dart
15+
// Acquire the InAppPurchaseAndroidPlatformAddition instance.
16+
InAppPurchaseAndroidPlatformAddition androidAddition = InAppPurchase.instance.getPlatformAddition<InAppPurchaseAndroidPlatformAddition>();
17+
// Consume an Android purchase.
18+
BillingResultWrapper billingResult = await androidAddition.consumePurchase(purchase);
19+
```
20+
* The [billing_client_wrappers](https://pub.dev/documentation/in_app_purchase_android/latest/billing_client_wrappers/billing_client_wrappers-library.html) have been moved into the [in_app_purchase_android](https://pub.dev/packages/in_app_purchase_android) package. They are still available through the [in_app_purchase](https://pub.dev/packages/in_app_purchase) plugin but to use them it is necessary to import the correct package when using them: `import 'package:in_app_purchase_android/billing_client_wrappers.dart';`;
21+
* iOS specific changes:
22+
* The iOS specific methods `InAppPurchaseConnection.presentCodeRedemptionSheet` and `InAppPurchaseConnection.refreshPurchaseVerificationData` methods have been removed from the platform agnostic interface and moved into the iOS specific `InAppPurchaseIosPlatformAddition` class. To use them acquire an instance through the `InAppPurchase.getPlatformAddition` method like so:
23+
```dart
24+
// Acquire the InAppPurchaseIosPlatformAddition instance.
25+
InAppPurchaseIosPlatformAddition iosAddition = InAppPurchase.instance.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
26+
// Present the code redemption sheet.
27+
await iosAddition.presentCodeRedemptionSheet();
28+
// Refresh purchase verification data.
29+
PurchaseVerificationData? verificationData = await iosAddition.refreshPurchaseVerificationData();
30+
```
31+
* The [store_kit_wrappers](https://pub.dev/documentation/in_app_purchase_ios/latest/store_kit_wrappers/store_kit_wrappers-library.html) have been moved into the [in_app_purchase_ios](https://pub.dev/packages/in_app_purchase_ios) package. They are still available in the [in_app_purchase](https://pub.dev/packages/in_app_purchase) plugin, but to use them it is necessary to import the correct package when using them: `import 'package:in_app_purchase_ios/store_kit_wrappers.dart';`;
32+
133
## 0.5.2
234

335
* Added `rawPrice` and `currencyCode` to the ProductDetails model.

packages/in_app_purchase/in_app_purchase/README.md

+44-52
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ can start using the plugin. Two basic options are available:
4444
1. A generic, idiomatic Flutter API: [in_app_purchase](https://pub.dev/documentation/in_app_purchase/latest/in_app_purchase/in_app_purchase-library.html).
4545
This API supports most use cases for loading and making purchases.
4646

47-
2. Platform-specific Dart APIs: [store_kit_wrappers](https://pub.dev/documentation/in_app_purchase/latest/store_kit_wrappers/store_kit_wrappers-library.html)
48-
and [billing_client_wrappers](https://pub.dev/documentation/in_app_purchase/latest/billing_client_wrappers/billing_client_wrappers-library.html).
47+
2. Platform-specific Dart APIs: [store_kit_wrappers](https://pub.dev/documentation/in_app_purchase_ios/latest/store_kit_wrappers/store_kit_wrappers-library.html)
48+
and [billing_client_wrappers](https://pub.dev/documentation/in_app_purchase_android/latest/billing_client_wrappers/billing_client_wrappers-library.html).
4949
These APIs expose platform-specific behavior and allow for more fine-tuned
5050
control when needed. However, if you use one of these APIs, your
5151
purchase-handling logic is significantly different for the different
@@ -70,13 +70,17 @@ This section has examples of code for the following tasks:
7070
The following initialization code is required for Google Play:
7171

7272
```dart
73+
// Import `in_app_purchase_android.dart` to be able to access the
74+
// `InAppPurchaseAndroidPlatformAddition` class.
75+
import 'package:in_app_purchase_android/in_app_purchase_android.dart';
76+
7377
void main() {
7478
// Inform the plugin that this app supports pending purchases on Android.
7579
// An error will occur on Android if you access the plugin `instance`
7680
// without this call.
77-
//
78-
// On iOS this is a no-op.
79-
InAppPurchaseConnection.enablePendingPurchases();
81+
if (defaultTargetPlatform == TargetPlatform.android) {
82+
InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
83+
}
8084
runApp(MyApp());
8185
}
8286
```
@@ -98,7 +102,7 @@ class _MyAppState extends State<MyApp> {
98102
@override
99103
void initState() {
100104
final Stream purchaseUpdated =
101-
InAppPurchaseConnection.instance.purchaseUpdatedStream;
105+
InAppPurchase.instance.purchaseStream;
102106
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
103107
_listenToPurchaseUpdated(purchaseDetailsList);
104108
}, onDone: () {
@@ -126,7 +130,8 @@ void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
126130
} else {
127131
if (purchaseDetails.status == PurchaseStatus.error) {
128132
_handleError(purchaseDetails.error!);
129-
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
133+
} else if (purchaseDetails.status == PurchaseStatus.purchased ||
134+
purchaseDetails.status == PurchaseStatus.restored) {
130135
bool valid = await _verifyPurchase(purchaseDetails);
131136
if (valid) {
132137
_deliverProduct(purchaseDetails);
@@ -136,7 +141,7 @@ void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
136141
}
137142
}
138143
if (purchaseDetails.pendingCompletePurchase) {
139-
await InAppPurchaseConnection.instance
144+
await InAppPurchase.instance
140145
.completePurchase(purchaseDetails);
141146
}
142147
}
@@ -147,7 +152,7 @@ void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
147152
### Connecting to the underlying store
148153

149154
```dart
150-
final bool available = await InAppPurchaseConnection.instance.isAvailable();
155+
final bool available = await InAppPurchase.instance.isAvailable();
151156
if (!available) {
152157
// The store cannot be reached or accessed. Update the UI accordingly.
153158
}
@@ -160,38 +165,25 @@ if (!available) {
160165
// `Set<String> _kIds = <String>['product1', 'product2'].toSet()`.
161166
const Set<String> _kIds = <String>{'product1', 'product2'};
162167
final ProductDetailsResponse response =
163-
await InAppPurchaseConnection.instance.queryProductDetails(_kIds);
168+
await InAppPurchase.instance.queryProductDetails(_kIds);
164169
if (response.notFoundIDs.isNotEmpty) {
165170
// Handle the error.
166171
}
167172
List<ProductDetails> products = response.productDetails;
168173
```
169174

170-
### Loading previous purchases
175+
### Restoring previous purchases
171176

172-
In the following example, implement `_verifyPurchase` so that it verifies the
173-
purchase following the best practices for each underlying store:
177+
Restored purchases will be emitted on the `InAppPurchase.purchaseStream`, make
178+
sure to validate restored purchases following the best practices for each
179+
underlying store:
174180

175181
* [Verifying App Store purchases](https://developer.apple.com/documentation/storekit/in-app_purchase/validating_receipts_with_the_app_store)
176182
* [Verifying Google Play purchases](https://developer.android.com/google/play/billing/security#verify)
177183

178184

179185
```dart
180-
final QueryPurchaseDetailsResponse response =
181-
await InAppPurchaseConnection.instance.queryPastPurchases();
182-
if (response.error != null) {
183-
// Handle the error.
184-
}
185-
for (PurchaseDetails purchase in response.pastPurchases) {
186-
// Verify the purchase following best practices for each underlying store.
187-
_verifyPurchase(purchase);
188-
// Deliver the purchase to the user in your app.
189-
_deliverPurchase(purchase);
190-
if (purchase.pendingCompletePurchase) {
191-
// Mark that you've delivered the purchase. This is mandatory.
192-
InAppPurchaseConnection.instance.completePurchase(purchase);
193-
}
194-
}
186+
await InAppPurchase.instance.restorePurchases();
195187
```
196188

197189
Note that the App Store does not have any APIs for querying consumable
@@ -203,39 +195,39 @@ that as well.
203195
### Making a purchase
204196

205197
Both underlying stores handle consumable and non-consumable products differently. If
206-
you're using `InAppPurchaseConnection`, you need to make a distinction here and
198+
you're using `InAppPurchase`, you need to make a distinction here and
207199
call the right purchase method for each type.
208200

209201
```dart
210-
final ProductDetails productDetails = ... // Saved earlier from queryPastPurchases().
202+
final ProductDetails productDetails = ... // Saved earlier from queryProductDetails().
211203
final PurchaseParam purchaseParam = PurchaseParam(productDetails: productDetails);
212204
if (_isConsumable(productDetails)) {
213-
InAppPurchaseConnection.instance.buyConsumable(purchaseParam: purchaseParam);
205+
InAppPurchase.instance.buyConsumable(purchaseParam: purchaseParam);
214206
} else {
215-
InAppPurchaseConnection.instance.buyNonConsumable(purchaseParam: purchaseParam);
207+
InAppPurchase.instance.buyNonConsumable(purchaseParam: purchaseParam);
216208
}
217209
// From here the purchase flow will be handled by the underlying store.
218-
// Updates will be delivered to the `InAppPurchaseConnection.instance.purchaseUpdatedStream`.
210+
// Updates will be delivered to the `InAppPurchase.instance.purchaseStream`.
219211
```
220212

221213
### Completing a purchase
222214

223-
The `InAppPurchaseConnection.purchaseUpdatedStream` will send purchase updates after
224-
you initiate the purchase flow using `InAppPurchaseConnection.buyConsumable` or `InAppPurchaseConnection.buyNonConsumable`.
225-
After delivering the content to the user, call
226-
`InAppPurchaseConnection.completePurchase` to tell the App Store and
227-
Google Play that the purchase has been finished.
215+
The `InAppPurchase.purchaseStream` will send purchase updates after
216+
you initiate the purchase flow using `InAppPurchase.buyConsumable`
217+
or `InAppPurchase.buyNonConsumable`. After delivering the content to
218+
the user, call `InAppPurchase.completePurchase` to tell the App Store
219+
and Google Play that the purchase has been finished.
228220

229-
> **Warning:** Failure to call `InAppPurchaseConnection.completePurchase` and
221+
> **Warning:** Failure to call `InAppPurchase.completePurchase` and
230222
> get a successful response within 3 days of the purchase will result a refund.
231223
232224
### Upgrading or downgrading an existing in-app subscription
233225

234226
To upgrade/downgrade an existing in-app subscription in Google Play,
235227
you need to provide an instance of `ChangeSubscriptionParam` with the old
236228
`PurchaseDetails` that the user needs to migrate from, and an optional
237-
`ProrationMode` with the `PurchaseParam` object while calling
238-
`InAppPurchaseConnection.buyNonConsumable`.
229+
`ProrationMode` with the `GooglePlayPurchaseParam` object while calling
230+
`InAppPurchase.buyNonConsumable`.
239231

240232
The App Store does not require this because it provides a subscription
241233
grouping mechanism. Each subscription you offer must be assigned to a
@@ -246,12 +238,12 @@ users from accidentally purchasing multiple subscriptions. Refer to the
246238

247239
```dart
248240
final PurchaseDetails oldPurchaseDetails = ...;
249-
PurchaseParam purchaseParam = PurchaseParam(
241+
PurchaseParam purchaseParam = GooglePlayPurchaseParam(
250242
productDetails: productDetails,
251243
changeSubscriptionParam: ChangeSubscriptionParam(
252244
oldPurchaseDetails: oldPurchaseDetails,
253245
prorationMode: ProrationMode.immediateWithTimeProration));
254-
InAppPurchaseConnection.instance
246+
InAppPurchase.instance
255247
.buyNonConsumable(purchaseParam: purchaseParam);
256248
```
257249

@@ -262,18 +254,18 @@ codes that you've set up in App Store Connect. For more information on
262254
redeeming offer codes, see [Implementing Offer Codes in Your App](https://developer.apple.com/documentation/storekit/in-app_purchase/subscriptions_and_offers/implementing_offer_codes_in_your_app).
263255

264256
```dart
265-
InAppPurchaseConnection.instance.presentCodeRedemptionSheet();
257+
InAppPurchaseIosPlatformAddition iosPlatformAddition =
258+
InAppPurchase.getPlatformAddition<InAppPurchaseIosPlatformAddition>();
259+
iosPlatformAddition.presentCodeRedemptionSheet();
266260
```
267261

268-
## Contributing to this plugin
262+
> **note:** The `InAppPurchaseIosPlatformAddition` is defined in the `in_app_purchase_ios.dart`
263+
> file so you need to import it into the file you will be using `InAppPurchaseIosPlatformAddition`:
264+
> ```dart
265+
> import 'package:in_app_purchase_ios/in_app_purchase_ios.dart';
266+
> ```
269267
270-
This plugin uses
271-
[json_serializable](https://pub.dev/packages/json_serializable) for the
272-
many data structs passed between the underlying platform layers and Dart. After
273-
editing any of the serialized data structs, rebuild the serializers by running
274-
`flutter packages pub run build_runner build --delete-conflicting-outputs`.
275-
`flutter packages pub run build_runner watch --delete-conflicting-outputs` will
276-
watch the filesystem for changes.
268+
## Contributing to this plugin
277269
278270
If you would like to contribute to the plugin, check out our
279271
[contribution guide](https://github.com/flutter/plugins/blob/master/CONTRIBUTING.md).

packages/in_app_purchase/in_app_purchase/android/build.gradle

-48
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/gradle.properties

-1
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/gradle/wrapper/gradle-wrapper.properties

-6
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/settings.gradle

-1
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/src/main/AndroidManifest.xml

-3
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java

-26
This file was deleted.

packages/in_app_purchase/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java

-23
This file was deleted.

0 commit comments

Comments
 (0)