From 6c7c2cc1eefb34f2266ee2e7da805132a7d54223 Mon Sep 17 00:00:00 2001 From: Jeroen Weener Date: Thu, 29 Jun 2023 14:58:48 +0200 Subject: [PATCH] [in_app_purchase] 0.3.0 Migration guide (#4137) The API changes introduced in 0.3.0 turned out to be bigger and more complicated than anticipated. This migration guide aims to help developers upgrade to 0.3.0 by giving context to the changes and treating some exemplary use cases. The need for a migration guide was brought to my attention in flutter/flutter#127844. A follow-up PR will update the README in the app-facing package to align with the breaking changes introduced in 0.3.0, as the current one is out-of-date. Fixes flutter/flutter#127844 --- .../in_app_purchase_android/CHANGELOG.md | 4 + .../in_app_purchase_android/README.md | 3 + .../example/build.excerpt.yaml | 15 ++ .../example/lib/migration_guide_examples.dart | 62 +++++++ .../example/pubspec.yaml | 1 + .../migration_guide.md | 157 ++++++++++++++++++ .../in_app_purchase_android/pubspec.yaml | 2 +- 7 files changed, 243 insertions(+), 1 deletion(-) create mode 100644 packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml create mode 100644 packages/in_app_purchase/in_app_purchase_android/example/lib/migration_guide_examples.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/migration_guide.md diff --git a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md index 0974ea26b1..32b558b1af 100644 --- a/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md +++ b/packages/in_app_purchase/in_app_purchase_android/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.3.0+8 + +* Adds a [guide for migrating](migration_guide.md) to [0.3.0](#0.3.0). + ## 0.3.0+7 * Bumps org.mockito:mockito-core from 4.7.0 to 5.3.1. diff --git a/packages/in_app_purchase/in_app_purchase_android/README.md b/packages/in_app_purchase/in_app_purchase_android/README.md index f00cf164e5..d49315b41a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/README.md +++ b/packages/in_app_purchase/in_app_purchase_android/README.md @@ -11,6 +11,9 @@ so you do not need to add it to your `pubspec.yaml`. However, if you `import` this package to use any of its APIs directly, you should [add it to your `pubspec.yaml` as usual][3]. +## Migrating to 0.3.0 +To migrate to version 0.3.0 from 0.2.x, have a look at the [migration guide](migration_guide.md). + ## Contributing This plugin uses diff --git a/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml b/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml new file mode 100644 index 0000000000..e317efa11c --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/example/build.excerpt.yaml @@ -0,0 +1,15 @@ +targets: + $default: + sources: + include: + - lib/** + # Some default includes that aren't really used here but will prevent + # false-negative warnings: + - $package$ + - lib/$lib$ + exclude: + - '**/.*/**' + - '**/build/**' + builders: + code_excerpter|code_excerpter: + enabled: true diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/migration_guide_examples.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/migration_guide_examples.dart new file mode 100644 index 0000000000..05eda2f3bd --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/migration_guide_examples.dart @@ -0,0 +1,62 @@ +// 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_for_file: unused_local_variable + +import 'package:in_app_purchase_android/billing_client_wrappers.dart'; +import 'package:in_app_purchase_android/in_app_purchase_android.dart'; +import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; + +// #docregion one-time-purchase-price +/// Handles the one time purchase price of a product. +void handleOneTimePurchasePrice(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.inapp) { + // Unwrapping is safe because the product is a one time purchase. + final OneTimePurchaseOfferDetailsWrapper offer = + product.oneTimePurchaseOfferDetails!; + final String price = offer.formattedPrice; + } + } +} +// #enddocregion one-time-purchase-price + +// #docregion subscription-free-trial +/// Handles the free trial period of a subscription. +void handleFreeTrialPeriod(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.subs) { + // Unwrapping is safe because the product is a subscription. + final SubscriptionOfferDetailsWrapper offer = + product.subscriptionOfferDetails![productDetails.subscriptionIndex!]; + final List pricingPhases = offer.pricingPhases; + if (pricingPhases.first.priceAmountMicros == 0) { + // Free trial period logic. + } + } + } +} +// #enddocregion subscription-free-trial + +// #docregion subscription-introductory-price +/// Handles the introductory price period of a subscription. +void handleIntroductoryPricePeriod(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.subs) { + // Unwrapping is safe because the product is a subscription. + final SubscriptionOfferDetailsWrapper offer = + product.subscriptionOfferDetails![productDetails.subscriptionIndex!]; + final List pricingPhases = offer.pricingPhases; + if (pricingPhases.length >= 2 && + pricingPhases.first.priceAmountMicros < + pricingPhases[1].priceAmountMicros) { + // Introductory pricing period logic. + } + } + } +} +// #enddocregion subscription-introductory-price diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml index a2b59a3265..5938153ae8 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: shared_preferences: ^2.0.0 dev_dependencies: + build_runner: ^2.4.5 flutter_driver: sdk: flutter flutter_test: diff --git a/packages/in_app_purchase/in_app_purchase_android/migration_guide.md b/packages/in_app_purchase/in_app_purchase_android/migration_guide.md new file mode 100644 index 0000000000..48a2e47c10 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/migration_guide.md @@ -0,0 +1,157 @@ + +# Migration Guide from 0.2.x to 0.3.0 + +Starting November 2023, Android Billing Client V4 is no longer supported, +requiring app developers using the plugin to migrate to 0.3.0. +Version 0.3.0 introduces breaking changes to the API as a result of changes in +the underlying Google Play Billing Library. +For context around the changes see the [recent changes to subscriptions in Play Console][1] +and the [Google Play Billing Library migration guide][2]. + +## SKUs become Products + +SKUs have been replaced with products. For example, the `SkuDetailsWrapper` +class has been replaced by `ProductDetailsWrapper`, and the `SkuType` enum has +been replaced by `ProductType`. + +Products are used to represent both in-app purchases (also referred to as +one-time purchases) and subscriptions. There are a few changes to the data model +that are important to note. + +Previously, an SKU of `SkuType.subs` would contain a `freeTrialPeriod`, +`introductoryPrice` and some related properties. In the new model, this has been +replaced by a more generic approach. +A subscription product can have multiple pricing phases, each with its own +price, subscription period etc. This allows for more flexibility in the pricing +model. + +In the next section we will look at how to migrate code that uses the old model +to the new model. Instead of listing all possible use cases, we will focus on +three: getting the price of a one time purchase, and handling both free trials +and introductory prices for subscriptions. Other use cases can be migrated in a +similar way. + +### Use case: getting the price of a one time purchase + +Below code shows how to get the price of a one time purchase before and after +the migration. Other properties can be obtained in a similar way. + +Code before migration: + +```dart +SkuDetailsWrapper sku; + +if (sku.type == SkuType.inapp) { + String price = sku.price; +} +``` + +Code after migration: + + +```dart +/// Handles the one time purchase price of a product. +void handleOneTimePurchasePrice(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.inapp) { + // Unwrapping is safe because the product is a one time purchase. + final OneTimePurchaseOfferDetailsWrapper offer = + product.oneTimePurchaseOfferDetails!; + final String price = offer.formattedPrice; + } + } +} +``` + +### Use case: free trials + +Below code shows how to handle free trials for subscriptions before and after +the migration. As subscriptions can contain multiple offers, each containing +multiple price phases, the logic is more involved. + +Code before migration: + +```dart +SkuDetailsWrapper sku; + +if (sku.type == SkuType.subs) { + if (sku.freeTrialPeriod.isNotEmpty) { + // Free trial period logic. + } +} +``` + +Code after migration: + + +```dart +/// Handles the free trial period of a subscription. +void handleFreeTrialPeriod(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.subs) { + // Unwrapping is safe because the product is a subscription. + final SubscriptionOfferDetailsWrapper offer = + product.subscriptionOfferDetails![productDetails.subscriptionIndex!]; + final List pricingPhases = offer.pricingPhases; + if (pricingPhases.first.priceAmountMicros == 0) { + // Free trial period logic. + } + } + } +} +``` + +### Use case: introductory prices + +Below code shows how to handle introductory prices for subscriptions before and +after the migration. As subscriptions can contain multiple offers, each +containing multiple price phases, the logic is more involved. + +Code before migration: + +```dart +SkuDetailsWrapper sku; + +if (sku.type == SkuType.subs) { + if (sku.introductoryPriceAmountMicros != 0) { + // Introductory price period logic. + } +} +``` + +Code after migration: + + +```dart +/// Handles the introductory price period of a subscription. +void handleIntroductoryPricePeriod(ProductDetails productDetails) { + if (productDetails is GooglePlayProductDetails) { + final ProductDetailsWrapper product = productDetails.productDetails; + if (product.productType == ProductType.subs) { + // Unwrapping is safe because the product is a subscription. + final SubscriptionOfferDetailsWrapper offer = + product.subscriptionOfferDetails![productDetails.subscriptionIndex!]; + final List pricingPhases = offer.pricingPhases; + if (pricingPhases.length >= 2 && + pricingPhases.first.priceAmountMicros < + pricingPhases[1].priceAmountMicros) { + // Introductory pricing period logic. + } + } + } +} +``` + +## Removal of `launchPriceChangeConfirmationFlow` + +The method `launchPriceChangeConfirmationFlow` has been removed. Price changes +are no longer handled by the application but are instead handled by the Google +Play Store automatically. See [subscription price changes][3] for more +information. + + +[1]: https://support.google.com/googleplay/android-developer/answer/12124625 +[2]: https://developer.android.com/google/play/billing/migrate-gpblv6#5-or-6 +[3]: https://developer.android.com/google/play/billing/subscriptions#price-change diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml index 158f8ea8e2..a68abcfc30 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml +++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase_android description: An implementation for the Android platform of the Flutter `in_app_purchase` plugin. This uses the Android BillingClient APIs. repository: https://github.com/flutter/packages/tree/main/packages/in_app_purchase/in_app_purchase_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+in_app_purchase%22 -version: 0.3.0+7 +version: 0.3.0+8 environment: sdk: ">=2.18.0 <4.0.0"