diff --git a/CHANGELOG.md b/CHANGELOG.md index f9f6ff75..db947838 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## 5.7.0 +* update android billing library v7 +* +## 5.6.2 +* On android for inapp products. Fixed NullPointerException: offerToken is required for constructing ProductDetailsParams ## 5.6.1 * Erroneous duplicate item by @deakjahn in https://github.com/dooboolab-community/flutter_inapp_purchase/pull/441 * Fixed consumable products reading on Android by @33-Elephants in https://github.com/dooboolab-community/flutter_inapp_purchase/pull/439 diff --git a/README.md b/README.md index 6efb125c..98146129 100644 --- a/README.md +++ b/README.md @@ -48,26 +48,26 @@ For help on editing plugin code, view the [documentation](https://flutter.io/dev ## Methods -| Func | Param | Return | Description | -| :--------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| initConnection | | `String` | Prepare IAP module. Must be called on Android before any other purchase flow methods. In ios, it will simply call `canMakePayments` method and return value. | -| getProducts | `List` Product IDs/skus | `List` | Get a list of products (consumable and non-consumable items, but not subscriptions). Note: On iOS versions earlier than 11.2 this method _will_ return subscriptions if they are included in your list of SKUs. This is because we cannot differentiate between IAP products and subscriptions prior to 11.2. | -| getSubscriptions | `List` Subscription IDs/skus | `List` | Get a list of subscriptions. Note: On iOS this method has the same output as `getProducts`. Because iOS does not differentiate between IAP products and subscriptions. | -| getPurchaseHistory | | `List` | Gets an invetory of purchases made by the user regardless of consumption status (where possible) | -| getAvailablePurchases | | `List` | (aka restore purchase) Get all purchases made by the user (either non-consumable, or haven't been consumed yet) | -| getAppStoreInitiatedProducts | | `List` | If the user has initiated a purchase directly on the App Store, the products that the user is attempting to purchase will be returned here. (iOS only) Note: On iOS versions earlier than 11.0 this method will always return an empty list, as the functionality was introduced in v11.0. [See Apple Docs for more info](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue) Always returns an empty list on Android. | -| requestSubscription | `String` sku, `String` oldSkuAndroid?, `int` prorationModeAndroid?, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseTokenAndroid? | Null | Create (request) a subscription to a sku. For upgrading/downgrading subscription on Android pass second parameter with current subscription ID, on iOS this is handled automatically by store. `purchaseUpdatedListener` will receive the result. | -| requestPurchase | `String` sku, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseToken? | Null | Request a purchase. `purchaseUpdatedListener` will receive the result. | -| finishTransactionIOS | `String` purchaseTokenAndroid | `PurchaseResult` | Send finishTransaction call to Apple IAP server. Call this function after receipt validation process | -| acknowledgePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Acknowledge a product (on Android) for `non-consumable` and `subscription` purchase. No-op on iOS. | -| consumePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Consume a product (on Android) for `consumable` purchase. No-op on iOS. | -| finishTransaction | `String` purchaseToken, `bool` isConsumable? } | `PurchaseResult` | Send finishTransaction call that abstracts all `acknowledgePurchaseAndroid`, `finishTransactionIOS`, `consumePurchaseAndroid` methods. | -| endConnection | | `String` | End billing connection. | -| consumeAllItems | | `String` | Manually consume all items in android. Do NOT call if you have any non-consumables (one time purchase items). No-op on iOS. | -| validateReceiptIos | `Map` receiptBody, `bool` isTest | `http.Response` | Validate receipt for ios. | -| validateReceiptAndroid | `String` packageName, `String` productId, `String` productToken, `String` accessToken, `bool` isSubscription | `http.Response` | Validate receipt for android. | -| showPromoCodesIOS | | | Show redeem codes in iOS. | -| showInAppMessageAndroid | | | Google Play will show users messaging during grace period and account hold once per day and provide them an opportunity to fix their payment without leaving the app | +| Func | Param | Return | Description | +| :--------------------------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:| :-------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| initConnection | | `String` | Prepare IAP module. Must be called on Android before any other purchase flow methods. In ios, it will simply call `canMakePayments` method and return value. | +| getProducts | `List` Product IDs/skus | `List` | Get a list of products (consumable and non-consumable items, but not subscriptions). Note: On iOS versions earlier than 11.2 this method _will_ return subscriptions if they are included in your list of SKUs. This is because we cannot differentiate between IAP products and subscriptions prior to 11.2. | +| getSubscriptions | `List` Subscription IDs/skus | `List` | Get a list of subscriptions. Note: On iOS this method has the same output as `getProducts`. Because iOS does not differentiate between IAP products and subscriptions. | +| getPurchaseHistory | | `List` | Gets an invetory of purchases made by the user regardless of consumption status (where possible) | +| getAvailablePurchases | | `List` | (aka restore purchase) Get all purchases made by the user (either non-consumable, or haven't been consumed yet) | +| getAppStoreInitiatedProducts | | `List` | If the user has initiated a purchase directly on the App Store, the products that the user is attempting to purchase will be returned here. (iOS only) Note: On iOS versions earlier than 11.0 this method will always return an empty list, as the functionality was introduced in v11.0. [See Apple Docs for more info](https://developer.apple.com/documentation/storekit/skpaymenttransactionobserver/2877502-paymentqueue) Always returns an empty list on Android. | +| requestSubscription | `String` sku, `String` oldSkuAndroid?, `int` replacementModeAndroid?, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseTokenAndroid? | Null | Create (request) a subscription to a sku. For upgrading/downgrading subscription on Android pass second parameter with current subscription ID, on iOS this is handled automatically by store. `purchaseUpdatedListener` will receive the result. | +| requestPurchase | `String` sku, `String` obfuscatedAccountIdAndroid?, `String` obfuscatedProfileIdAndroid?, `String` purchaseToken? | Null | Request a purchase. `purchaseUpdatedListener` will receive the result. | +| finishTransactionIOS | `String` purchaseTokenAndroid | `PurchaseResult` | Send finishTransaction call to Apple IAP server. Call this function after receipt validation process | +| acknowledgePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Acknowledge a product (on Android) for `non-consumable` and `subscription` purchase. No-op on iOS. | +| consumePurchaseAndroid | `String` purchaseToken | `PurchaseResult` | Consume a product (on Android) for `consumable` purchase. No-op on iOS. | +| finishTransaction | `String` purchaseToken, `bool` isConsumable? } | `PurchaseResult` | Send finishTransaction call that abstracts all `acknowledgePurchaseAndroid`, `finishTransactionIOS`, `consumePurchaseAndroid` methods. | +| endConnection | | `String` | End billing connection. | +| consumeAllItems | | `String` | Manually consume all items in android. Do NOT call if you have any non-consumables (one time purchase items). No-op on iOS. | +| validateReceiptIos | `Map` receiptBody, `bool` isTest | `http.Response` | Validate receipt for ios. | +| validateReceiptAndroid | `String` packageName, `String` productId, `String` productToken, `String` accessToken, `bool` isSubscription | `http.Response` | Validate receipt for android. | +| showPromoCodesIOS | | | Show redeem codes in iOS. | +| showInAppMessageAndroid | | | Google Play will show users messaging during grace period and account hold once per day and provide them an opportunity to fix their payment without leaving the app | ## Purchase flow in `flutter_inapp_purchase@2.0.0+ diff --git a/android/build.gradle b/android/build.gradle index c69039c7..63dc991e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -51,7 +51,7 @@ android { } dependencies { - def billing_version = "6.0.1" + def billing_version = "7.0.0" implementation "com.android.billingclient:billing-ktx:$billing_version" implementation files('jars/in-app-purchasing-2.0.76.jar') diff --git a/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/AndroidInappPurchasePlugin.kt b/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/AndroidInappPurchasePlugin.kt index f949209a..6c97ad55 100644 --- a/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/AndroidInappPurchasePlugin.kt +++ b/android/src/main/kotlin/com/dooboolab/flutterinapppurchase/AndroidInappPurchasePlugin.kt @@ -507,7 +507,7 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler, val obfuscatedAccountId = call.argument("obfuscatedAccountId") val obfuscatedProfileId = call.argument("obfuscatedProfileId") val productId = call.argument("productId") - val prorationMode = call.argument("prorationMode")!! + val replacementMode = call.argument("replacementMode") val purchaseToken = call.argument("purchaseToken") val offerTokenIndex = call.argument("offerTokenIndex") val builder = newBuilder() @@ -537,9 +537,10 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler, if (offerToken == null) { offerToken = selectedProductDetails.subscriptionOfferDetails!![0].offerToken } - - productDetailsParamsBuilder.setOfferToken(offerToken) + } else { + offerToken = selectedProductDetails.subscriptionOfferDetails!![0].offerToken } + productDetailsParamsBuilder.setOfferToken(offerToken) val productDetailsParamsList = listOf(productDetailsParamsBuilder.build()) @@ -554,25 +555,8 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler, builder.setObfuscatedProfileId(obfuscatedProfileId) } - when (prorationMode) { - -1 -> {} //ignore - ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE -> { - params.setReplaceProrationMode(ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE) - if (type != BillingClient.ProductType.SUBS) { - safeChannel.error( - TAG, - "buyItemByType", - "IMMEDIATE_AND_CHARGE_PRORATED_PRICE for proration mode only works in subscription purchase." - ) - return - } - } - ProrationMode.IMMEDIATE_WITHOUT_PRORATION, - ProrationMode.DEFERRED, - ProrationMode.IMMEDIATE_WITH_TIME_PRORATION, - ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE -> - params.setReplaceProrationMode(prorationMode) - else -> params.setReplaceProrationMode(ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) + if (replacementMode != null && replacementMode != -1) { + params.setSubscriptionReplacementMode(replacementMode) } if (purchaseToken != null) { @@ -581,7 +565,6 @@ class AndroidInappPurchasePlugin internal constructor() : MethodCallHandler, } if (activity != null) { billingClient!!.launchBillingFlow(activity!!, builder.build()) - } } catch (e: Exception) { safeChannel.error(TAG, "buyItemByType", e.message) diff --git a/lib/flutter_inapp_purchase.dart b/lib/flutter_inapp_purchase.dart index 6b8071bc..61f795eb 100644 --- a/lib/flutter_inapp_purchase.dart +++ b/lib/flutter_inapp_purchase.dart @@ -265,7 +265,7 @@ class FlutterInappPurchase { /// Request a purchase on `Android` or `iOS`. /// Result will be received in `purchaseUpdated` listener or `purchaseError` listener. /// - /// Check [AndroidProrationMode] for valid proration values + /// Check [AndroidReplacementMode] for valid values /// Identical to [requestSubscription] on `iOS`. /// [purchaseTokenAndroid] is used when upgrading subscriptions and sets the old purchase token /// [offerTokenIndex] is now required for billing 5.0, if upgraded from billing 4.0 this will default to 0 @@ -278,7 +278,7 @@ class FlutterInappPurchase { return await _channel.invokeMethod('buyItemByType', { 'type': _TypeInApp.inapp.name, 'productId': productId, - 'prorationMode': -1, + 'replacementMode': -1, 'obfuscatedAccountId': obfuscatedAccountId, 'obfuscatedProfileId': obfuscatedProfileIdAndroid, 'purchaseToken': purchaseTokenAndroid, @@ -299,13 +299,13 @@ class FlutterInappPurchase { /// /// **NOTICE** second parameter is required on `Android`. /// - /// Check [AndroidProrationMode] for valid proration values + /// Check [https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode] for valid values /// Identical to [requestPurchase] on `iOS`. /// [purchaseTokenAndroid] is used when upgrading subscriptions and sets the old purchase token /// [offerTokenIndex] is now required for billing 5.0, if upgraded from billing 4.0 this will default to 0 Future requestSubscription( String productId, { - int? prorationModeAndroid, + int? replacementModeAndroid, String? obfuscatedAccountIdAndroid, String? obfuscatedProfileIdAndroid, String? purchaseTokenAndroid, @@ -315,7 +315,7 @@ class FlutterInappPurchase { return await _channel.invokeMethod('buyItemByType', { 'type': _TypeInApp.subs.name, 'productId': productId, - 'prorationMode': prorationModeAndroid ?? -1, + 'replacementMode': replacementModeAndroid ?? -1, 'obfuscatedAccountId': obfuscatedAccountIdAndroid, 'obfuscatedProfileId': obfuscatedProfileIdAndroid, 'purchaseToken': purchaseTokenAndroid, @@ -691,30 +691,27 @@ class FlutterInappPurchase { code: _platform.operatingSystem, message: "platform not supported"); } } +/// A list of valid values for ReplacementMode parameter +/// Check [https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode] for valid values +class AndroidReplacementMode { -/// A list of valid values for ProrationMode parameter -/// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode -class AndroidProrationMode { - /// Replacement takes effect immediately, and the user is charged full price of new plan and is given a full billing cycle of subscription, plus remaining prorated time from the old plan. - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#IMMEDIATE_AND_CHARGE_FULL_PRICE - static const int IMMEDIATE_AND_CHARGE_FULL_PRICE = 5; + /// The new plan takes effect immediately, and the user is charged the full price of the new plan and is given a full billing cycle of the subscription, plus remaining prorated time from the old plan. + static const int CHARGE_FULL_PRICE = 5; - /// Replacement takes effect when the old plan expires, and the new price will be charged at the same time. - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#DEFERRED - static const int DEFERRED = 4; + /// The new plan takes effect immediately, and the user is charged the prorated price of the new plan. The billing cycle remains the same. + static const int CHARGE_PRORATED_PRICE = 2; - /// Replacement takes effect immediately, and the billing cycle remains the same. The price for the remaining period will be charged. This option is only available for subscription upgrade. - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#immediate_and_charge_prorated_price - static const int IMMEDIATE_AND_CHARGE_PRORATED_PRICE = 2; + /// The new purchase takes effect immediately, but the new plan will take effect when the old item expires. + static const int DEFERRED = 6; - /// Replacement takes effect immediately, and the new price will be charged on next recurrence time. The billing cycle stays the same. - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#immediate_without_proration - static const int IMMEDIATE_WITHOUT_PRORATION = 3; + /// Unknown replacement mode. This is typically used when the mode is not explicitly defined. + static const int UNKNOWN_REPLACEMENT_MODE = 0; - /// Replacement takes effect immediately, and the remaining time will be prorated and credited to the user. This is the current default behavior. - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#immediate_with_time_proration - static const int IMMEDIATE_WITH_TIME_PRORATION = 1; + /// The new plan takes effect immediately, but the new price will be charged at the next recurrence time. The billing cycle stays the same. + static const int WITHOUT_PRORATION = 3; - /// https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode#unknown_subscription_upgrade_downgrade_policy - static const int UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = 0; + /// The new plan takes effect immediately, and the remaining time from the old plan will be prorated and credited to the user. + static const int WITH_TIME_PRORATION = 1; } + + diff --git a/pubspec.yaml b/pubspec.yaml index a711c9e1..8487d2fc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: flutter_inapp_purchase description: In App Purchase plugin for flutter. This project has been forked by react-native-iap and we are willing to share same experience with that on react-native. -version: 5.6.1 +version: 5.7.0 homepage: https://github.com/dooboolab/flutter_inapp_purchase/blob/main/pubspec.yaml environment: sdk: ">=2.15.0 <4.0.0" diff --git a/test/flutter_inapp_purchase_test.dart b/test/flutter_inapp_purchase_test.dart index db69f61f..67b120a8 100644 --- a/test/flutter_inapp_purchase_test.dart +++ b/test/flutter_inapp_purchase_test.dart @@ -1008,7 +1008,7 @@ void main() { arguments: { 'type': 'inapp', 'productId': productId, - 'prorationMode': -1, + 'replacementMode': -1, 'obfuscatedAccountId': null, 'obfuscatedProfileId': null, 'purchaseToken': null, @@ -1068,7 +1068,7 @@ void main() { arguments: { 'type': 'subs', 'productId': productId, - 'prorationMode': -1, + 'replacementMode': -1, 'obfuscatedAccountId': null, 'obfuscatedProfileId': null, 'purchaseToken': null,