From e8f1f63bc3b75a5bea33eb6b07226c4b140a24d0 Mon Sep 17 00:00:00 2001 From: Marcin Chudy Date: Mon, 20 Jan 2025 16:32:33 +0100 Subject: [PATCH] [in_app_purchase] Update Play Billing library to 7.1.1 (#8218) Updates Play Billing Library to the latest version 7.1.1. Exposes new APIs as per [release notes](https://developer.android.com/google/play/billing/release-notes): - Adds Dart representation of `ProductDetails.InstallmentPlanDetails` - Adds Dart representation of `PendingPurchasesParams` and removes the deprecated `enablePendingPurchases` method on `BillingClientWrapper` (breaking change) - Adds Dart representation of `Purchase.PendingPurchaseUpdate` - Removes the deprecated `ProrationMode` as it has been removed from the native library (breaking change) This PR introduces breaking changes in `in_app_purchase_android`, but does not introduce any breaking changes on the platform interface level. Fixes https://github.com/flutter/flutter/issues/147394 --- .../in_app_purchase_android/CHANGELOG.md | 11 + .../android/build.gradle | 4 +- .../inapppurchase/BillingClientFactory.java | 5 +- .../BillingClientFactoryImpl.java | 8 +- .../plugins/inapppurchase/Messages.java | 594 +++++++++++++++--- .../inapppurchase/MethodCallHandlerImpl.java | 62 +- .../plugins/inapppurchase/Translator.java | 123 +++- .../BillingClientFactoryImplTest.java | 21 +- .../inapppurchase/MethodCallHandlerTest.java | 229 ++++--- .../plugins/inapppurchase/TranslatorTest.java | 24 +- .../in_app_purchase_android/build.yaml | 8 - .../example/lib/main.dart | 28 +- ...illing_only_reporting_details_wrapper.dart | 27 - ...ling_only_reporting_details_wrapper.g.dart | 17 - .../billing_client_manager.dart | 27 +- .../billing_client_wrapper.dart | 311 ++------- .../billing_client_wrapper.g.dart | 61 -- .../billing_config_wrapper.dart | 26 - .../billing_config_wrapper.g.dart | 15 - .../billing_response_wrapper.dart | 23 - .../billing_response_wrapper.g.dart | 14 - ...e_time_purchase_offer_details_wrapper.dart | 18 - ...time_purchase_offer_details_wrapper.g.dart | 15 - .../pending_purchases_params_wrapper.dart | 39 ++ .../product_details_wrapper.dart | 59 -- .../product_details_wrapper.g.dart | 49 -- .../product_wrapper.dart | 11 - .../product_wrapper.g.dart | 23 - .../purchase_wrapper.dart | 146 ++--- .../purchase_wrapper.g.dart | 75 --- .../subscription_offer_details_wrapper.dart | 91 ++- .../subscription_offer_details_wrapper.g.dart | 37 -- .../user_choice_details_wrapper.dart | 34 - .../user_choice_details_wrapper.g.dart | 45 -- .../src/in_app_purchase_android_platform.dart | 1 - ...pp_purchase_android_platform_addition.dart | 26 - .../lib/src/messages.g.dart | 279 ++++++-- .../lib/src/pigeon_converters.dart | 156 ++++- .../src/types/change_subscription_param.dart | 8 - .../types/google_play_purchase_details.dart | 4 +- .../pigeons/messages.dart | 94 ++- .../in_app_purchase_android/pubspec.yaml | 4 +- .../billing_client_manager_test.dart | 47 +- .../billing_client_wrapper_test.dart | 193 ++---- .../billing_client_wrapper_test.mocks.dart | 8 +- .../product_details_wrapper_test.dart | 212 +------ .../product_wrapper_test.dart | 30 - .../purchase_wrapper_test.dart | 148 ----- ...rchase_android_platform_addition_test.dart | 24 +- ...in_app_purchase_android_platform_test.dart | 48 +- .../test/test_conversion_utils.dart | 3 +- 51 files changed, 1630 insertions(+), 1935 deletions(-) delete mode 100644 packages/in_app_purchase/in_app_purchase_android/build.yaml delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart create mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart delete mode 100644 packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart 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 36b7371e7b2b..93c8c9401085 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,14 @@ +## 0.4.0 + +* Updates Google Play Billing Library from 6.2.0 to 7.1.1. +* **BREAKING CHANGES**: + * Removes the deprecated `ProrationMode` enum. `ReplacementMode` should be used instead. + * Removes the deprecated `BillingClientWrapper.enablePendingPurchases` method. + * Removes JSON serialization from Dart wrapper classes. + * Removes `subscriptionsOnVR` and `inAppItemsOnVR` from `BillingClientFeature`. +* Adds `installmentPlanDetails` to `SubscriptionOfferDetailsWrapper`. +* Adds APIs to support pending transactions for subscription prepaid plans (`PendingPurchasesParams`). + ## 0.3.6+13 * Updates androidx.annotation:annotation to 1.9.1. diff --git a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle index 29e12ba9d8ad..4539ca78454a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/build.gradle +++ b/packages/in_app_purchase/in_app_purchase_android/android/build.gradle @@ -31,7 +31,7 @@ android { compileSdk 34 defaultConfig { - minSdk 19 + minSdk 21 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } lintOptions { @@ -60,7 +60,7 @@ android { dependencies { implementation 'androidx.annotation:annotation:1.9.1' - implementation 'com.android.billingclient:billing:6.2.0' + implementation 'com.android.billingclient:billing:7.1.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.json:json:20250107' testImplementation 'org.mockito:mockito-core:5.4.0' diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index 979e54a37bbf..7ce006273513 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -19,10 +19,13 @@ interface BillingClientFactory { * @param callbackApi The callback API to be used by the {@link BillingClient}. * @param billingChoiceMode Enables the ability to offer alternative billing or Google Play * billing. + * @param pendingPurchasesParams Parameters to enable pending purchases. See {@link + * com.android.billingclient.api.PendingPurchasesParams}. * @return The {@link BillingClient} object that is created. */ BillingClient createBillingClient( @NonNull Context context, @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode); + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java index 460414b2e5ba..5b7d6bbe403d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java @@ -5,6 +5,7 @@ package io.flutter.plugins.inapppurchase; import static io.flutter.plugins.inapppurchase.Translator.fromUserChoiceDetails; +import static io.flutter.plugins.inapppurchase.Translator.toPendingPurchasesParams; import android.content.Context; import androidx.annotation.NonNull; @@ -21,8 +22,11 @@ final class BillingClientFactoryImpl implements BillingClientFactory { public BillingClient createBillingClient( @NonNull Context context, @NonNull Messages.InAppPurchaseCallbackApi callbackApi, - PlatformBillingChoiceMode billingChoiceMode) { - BillingClient.Builder builder = BillingClient.newBuilder(context).enablePendingPurchases(); + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams) { + BillingClient.Builder builder = + BillingClient.newBuilder(context) + .enablePendingPurchases(toPendingPurchasesParams(pendingPurchasesParams)); switch (billingChoiceMode) { case ALTERNATIVE_BILLING_ONLY: // https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java index cef3e316c3d6..2738d4ffaf11 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Messages.java @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.1), do not edit directly. // See also: https://pub.dev/packages/pigeon package io.flutter.plugins.inapppurchase; @@ -72,6 +72,44 @@ protected static FlutterError createConnectionError(@NonNull String channelName) @Retention(CLASS) @interface CanIgnoreReturnValue {} + /** Pigeon version of Java BillingClient.BillingResponseCode. */ + public enum PlatformBillingResponse { + SERVICE_TIMEOUT(0), + FEATURE_NOT_SUPPORTED(1), + SERVICE_DISCONNECTED(2), + OK(3), + USER_CANCELED(4), + SERVICE_UNAVAILABLE(5), + BILLING_UNAVAILABLE(6), + ITEM_UNAVAILABLE(7), + DEVELOPER_ERROR(8), + ERROR(9), + ITEM_ALREADY_OWNED(10), + ITEM_NOT_OWNED(11), + NETWORK_ERROR(12); + + final int index; + + PlatformBillingResponse(final int index) { + this.index = index; + } + } + + public enum PlatformReplacementMode { + UNKNOWN_REPLACEMENT_MODE(0), + WITH_TIME_PRORATION(1), + CHARGE_PRORATED_PRICE(2), + WITHOUT_PRORATION(3), + DEFERRED(4), + CHARGE_FULL_PRICE(5); + + final int index; + + PlatformReplacementMode(final int index) { + this.index = index; + } + } + /** Pigeon version of Java BillingClient.ProductType. */ public enum PlatformProductType { INAPP(0), @@ -104,6 +142,23 @@ public enum PlatformBillingChoiceMode { } } + public enum PlatformBillingClientFeature { + ALTERNATIVE_BILLING_ONLY(0), + BILLING_CONFIG(1), + EXTERNAL_OFFER(2), + IN_APP_MESSAGING(3), + PRICE_CHANGE_CONFIRMATION(4), + PRODUCT_DETAILS(5), + SUBSCRIPTIONS(6), + SUBSCRIPTIONS_UPDATE(7); + + final int index; + + PlatformBillingClientFeature(final int index) { + this.index = index; + } + } + /** Pigeon version of Java Purchase.PurchaseState. */ public enum PlatformPurchaseState { UNSPECIFIED(0), @@ -320,13 +375,13 @@ ArrayList toList() { *

Generated class from Pigeon that represents data sent in messages. */ public static final class PlatformBillingResult { - private @NonNull Long responseCode; + private @NonNull PlatformBillingResponse responseCode; - public @NonNull Long getResponseCode() { + public @NonNull PlatformBillingResponse getResponseCode() { return responseCode; } - public void setResponseCode(@NonNull Long setterArg) { + public void setResponseCode(@NonNull PlatformBillingResponse setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"responseCode\" is null."); } @@ -368,10 +423,10 @@ public int hashCode() { public static final class Builder { - private @Nullable Long responseCode; + private @Nullable PlatformBillingResponse responseCode; @CanIgnoreReturnValue - public @NonNull Builder setResponseCode(@NonNull Long setterArg) { + public @NonNull Builder setResponseCode(@NonNull PlatformBillingResponse setterArg) { this.responseCode = setterArg; return this; } @@ -403,7 +458,7 @@ ArrayList toList() { static @NonNull PlatformBillingResult fromList(@NonNull ArrayList pigeonVar_list) { PlatformBillingResult pigeonResult = new PlatformBillingResult(); Object responseCode = pigeonVar_list.get(0); - pigeonResult.setResponseCode((Long) responseCode); + pigeonResult.setResponseCode((PlatformBillingResponse) responseCode); Object debugMessage = pigeonVar_list.get(1); pigeonResult.setDebugMessage((String) debugMessage); return pigeonResult; @@ -1086,26 +1141,13 @@ public void setProduct(@NonNull String setterArg) { this.product = setterArg; } - private @NonNull Long prorationMode; - - public @NonNull Long getProrationMode() { - return prorationMode; - } - - public void setProrationMode(@NonNull Long setterArg) { - if (setterArg == null) { - throw new IllegalStateException("Nonnull field \"prorationMode\" is null."); - } - this.prorationMode = setterArg; - } - - private @NonNull Long replacementMode; + private @NonNull PlatformReplacementMode replacementMode; - public @NonNull Long getReplacementMode() { + public @NonNull PlatformReplacementMode getReplacementMode() { return replacementMode; } - public void setReplacementMode(@NonNull Long setterArg) { + public void setReplacementMode(@NonNull PlatformReplacementMode setterArg) { if (setterArg == null) { throw new IllegalStateException("Nonnull field \"replacementMode\" is null."); } @@ -1175,7 +1217,6 @@ public boolean equals(Object o) { } PlatformBillingFlowParams that = (PlatformBillingFlowParams) o; return product.equals(that.product) - && prorationMode.equals(that.prorationMode) && replacementMode.equals(that.replacementMode) && Objects.equals(offerToken, that.offerToken) && Objects.equals(accountId, that.accountId) @@ -1188,7 +1229,6 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash( product, - prorationMode, replacementMode, offerToken, accountId, @@ -1207,18 +1247,10 @@ public static final class Builder { return this; } - private @Nullable Long prorationMode; - - @CanIgnoreReturnValue - public @NonNull Builder setProrationMode(@NonNull Long setterArg) { - this.prorationMode = setterArg; - return this; - } - - private @Nullable Long replacementMode; + private @Nullable PlatformReplacementMode replacementMode; @CanIgnoreReturnValue - public @NonNull Builder setReplacementMode(@NonNull Long setterArg) { + public @NonNull Builder setReplacementMode(@NonNull PlatformReplacementMode setterArg) { this.replacementMode = setterArg; return this; } @@ -1266,7 +1298,6 @@ public static final class Builder { public @NonNull PlatformBillingFlowParams build() { PlatformBillingFlowParams pigeonReturn = new PlatformBillingFlowParams(); pigeonReturn.setProduct(product); - pigeonReturn.setProrationMode(prorationMode); pigeonReturn.setReplacementMode(replacementMode); pigeonReturn.setOfferToken(offerToken); pigeonReturn.setAccountId(accountId); @@ -1279,9 +1310,8 @@ public static final class Builder { @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(8); + ArrayList toListResult = new ArrayList<>(7); toListResult.add(product); - toListResult.add(prorationMode); toListResult.add(replacementMode); toListResult.add(offerToken); toListResult.add(accountId); @@ -1295,19 +1325,17 @@ ArrayList toList() { PlatformBillingFlowParams pigeonResult = new PlatformBillingFlowParams(); Object product = pigeonVar_list.get(0); pigeonResult.setProduct((String) product); - Object prorationMode = pigeonVar_list.get(1); - pigeonResult.setProrationMode((Long) prorationMode); - Object replacementMode = pigeonVar_list.get(2); - pigeonResult.setReplacementMode((Long) replacementMode); - Object offerToken = pigeonVar_list.get(3); + Object replacementMode = pigeonVar_list.get(1); + pigeonResult.setReplacementMode((PlatformReplacementMode) replacementMode); + Object offerToken = pigeonVar_list.get(2); pigeonResult.setOfferToken((String) offerToken); - Object accountId = pigeonVar_list.get(4); + Object accountId = pigeonVar_list.get(3); pigeonResult.setAccountId((String) accountId); - Object obfuscatedProfileId = pigeonVar_list.get(5); + Object obfuscatedProfileId = pigeonVar_list.get(4); pigeonResult.setObfuscatedProfileId((String) obfuscatedProfileId); - Object oldProduct = pigeonVar_list.get(6); + Object oldProduct = pigeonVar_list.get(5); pigeonResult.setOldProduct((String) oldProduct); - Object purchaseToken = pigeonVar_list.get(7); + Object purchaseToken = pigeonVar_list.get(6); pigeonResult.setPurchaseToken((String) purchaseToken); return pigeonResult; } @@ -1691,6 +1719,16 @@ public void setAccountIdentifiers(@Nullable PlatformAccountIdentifiers setterArg this.accountIdentifiers = setterArg; } + private @Nullable PlatformPendingPurchaseUpdate pendingPurchaseUpdate; + + public @Nullable PlatformPendingPurchaseUpdate getPendingPurchaseUpdate() { + return pendingPurchaseUpdate; + } + + public void setPendingPurchaseUpdate(@Nullable PlatformPendingPurchaseUpdate setterArg) { + this.pendingPurchaseUpdate = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ PlatformPurchase() {} @@ -1715,7 +1753,8 @@ public boolean equals(Object o) { && isAcknowledged.equals(that.isAcknowledged) && quantity.equals(that.quantity) && purchaseState.equals(that.purchaseState) - && Objects.equals(accountIdentifiers, that.accountIdentifiers); + && Objects.equals(accountIdentifiers, that.accountIdentifiers) + && Objects.equals(pendingPurchaseUpdate, that.pendingPurchaseUpdate); } @Override @@ -1733,7 +1772,8 @@ public int hashCode() { isAcknowledged, quantity, purchaseState, - accountIdentifiers); + accountIdentifiers, + pendingPurchaseUpdate); } public static final class Builder { @@ -1843,6 +1883,15 @@ public static final class Builder { return this; } + private @Nullable PlatformPendingPurchaseUpdate pendingPurchaseUpdate; + + @CanIgnoreReturnValue + public @NonNull Builder setPendingPurchaseUpdate( + @Nullable PlatformPendingPurchaseUpdate setterArg) { + this.pendingPurchaseUpdate = setterArg; + return this; + } + public @NonNull PlatformPurchase build() { PlatformPurchase pigeonReturn = new PlatformPurchase(); pigeonReturn.setOrderId(orderId); @@ -1858,13 +1907,14 @@ public static final class Builder { pigeonReturn.setQuantity(quantity); pigeonReturn.setPurchaseState(purchaseState); pigeonReturn.setAccountIdentifiers(accountIdentifiers); + pigeonReturn.setPendingPurchaseUpdate(pendingPurchaseUpdate); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(13); + ArrayList toListResult = new ArrayList<>(14); toListResult.add(orderId); toListResult.add(packageName); toListResult.add(purchaseTime); @@ -1878,6 +1928,7 @@ ArrayList toList() { toListResult.add(quantity); toListResult.add(purchaseState); toListResult.add(accountIdentifiers); + toListResult.add(pendingPurchaseUpdate); return toListResult; } @@ -1909,6 +1960,107 @@ ArrayList toList() { pigeonResult.setPurchaseState((PlatformPurchaseState) purchaseState); Object accountIdentifiers = pigeonVar_list.get(12); pigeonResult.setAccountIdentifiers((PlatformAccountIdentifiers) accountIdentifiers); + Object pendingPurchaseUpdate = pigeonVar_list.get(13); + pigeonResult.setPendingPurchaseUpdate((PlatformPendingPurchaseUpdate) pendingPurchaseUpdate); + return pigeonResult; + } + } + + /** + * Pigeon version of Java Purchase. + * + *

See also PendingPurchaseUpdateWrapper on the Dart side. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPendingPurchaseUpdate { + private @NonNull List products; + + public @NonNull List getProducts() { + return products; + } + + public void setProducts(@NonNull List setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"products\" is null."); + } + this.products = setterArg; + } + + private @NonNull String purchaseToken; + + public @NonNull String getPurchaseToken() { + return purchaseToken; + } + + public void setPurchaseToken(@NonNull String setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"purchaseToken\" is null."); + } + this.purchaseToken = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPendingPurchaseUpdate() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformPendingPurchaseUpdate that = (PlatformPendingPurchaseUpdate) o; + return products.equals(that.products) && purchaseToken.equals(that.purchaseToken); + } + + @Override + public int hashCode() { + return Objects.hash(products, purchaseToken); + } + + public static final class Builder { + + private @Nullable List products; + + @CanIgnoreReturnValue + public @NonNull Builder setProducts(@NonNull List setterArg) { + this.products = setterArg; + return this; + } + + private @Nullable String purchaseToken; + + @CanIgnoreReturnValue + public @NonNull Builder setPurchaseToken(@NonNull String setterArg) { + this.purchaseToken = setterArg; + return this; + } + + public @NonNull PlatformPendingPurchaseUpdate build() { + PlatformPendingPurchaseUpdate pigeonReturn = new PlatformPendingPurchaseUpdate(); + pigeonReturn.setProducts(products); + pigeonReturn.setPurchaseToken(purchaseToken); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(products); + toListResult.add(purchaseToken); + return toListResult; + } + + static @NonNull PlatformPendingPurchaseUpdate fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformPendingPurchaseUpdate pigeonResult = new PlatformPendingPurchaseUpdate(); + Object products = pigeonVar_list.get(0); + pigeonResult.setProducts((List) products); + Object purchaseToken = pigeonVar_list.get(1); + pigeonResult.setPurchaseToken((String) purchaseToken); return pigeonResult; } } @@ -2410,6 +2562,16 @@ public void setPricingPhases(@NonNull List setterArg) { this.pricingPhases = setterArg; } + private @Nullable PlatformInstallmentPlanDetails installmentPlanDetails; + + public @Nullable PlatformInstallmentPlanDetails getInstallmentPlanDetails() { + return installmentPlanDetails; + } + + public void setInstallmentPlanDetails(@Nullable PlatformInstallmentPlanDetails setterArg) { + this.installmentPlanDetails = setterArg; + } + /** Constructor is non-public to enforce null safety; use Builder. */ PlatformSubscriptionOfferDetails() {} @@ -2426,12 +2588,14 @@ public boolean equals(Object o) { && Objects.equals(offerId, that.offerId) && offerToken.equals(that.offerToken) && offerTags.equals(that.offerTags) - && pricingPhases.equals(that.pricingPhases); + && pricingPhases.equals(that.pricingPhases) + && Objects.equals(installmentPlanDetails, that.installmentPlanDetails); } @Override public int hashCode() { - return Objects.hash(basePlanId, offerId, offerToken, offerTags, pricingPhases); + return Objects.hash( + basePlanId, offerId, offerToken, offerTags, pricingPhases, installmentPlanDetails); } public static final class Builder { @@ -2476,6 +2640,15 @@ public static final class Builder { return this; } + private @Nullable PlatformInstallmentPlanDetails installmentPlanDetails; + + @CanIgnoreReturnValue + public @NonNull Builder setInstallmentPlanDetails( + @Nullable PlatformInstallmentPlanDetails setterArg) { + this.installmentPlanDetails = setterArg; + return this; + } + public @NonNull PlatformSubscriptionOfferDetails build() { PlatformSubscriptionOfferDetails pigeonReturn = new PlatformSubscriptionOfferDetails(); pigeonReturn.setBasePlanId(basePlanId); @@ -2483,18 +2656,20 @@ public static final class Builder { pigeonReturn.setOfferToken(offerToken); pigeonReturn.setOfferTags(offerTags); pigeonReturn.setPricingPhases(pricingPhases); + pigeonReturn.setInstallmentPlanDetails(installmentPlanDetails); return pigeonReturn; } } @NonNull ArrayList toList() { - ArrayList toListResult = new ArrayList<>(5); + ArrayList toListResult = new ArrayList<>(6); toListResult.add(basePlanId); toListResult.add(offerId); toListResult.add(offerToken); toListResult.add(offerTags); toListResult.add(pricingPhases); + toListResult.add(installmentPlanDetails); return toListResult; } @@ -2511,6 +2686,9 @@ ArrayList toList() { pigeonResult.setOfferTags((List) offerTags); Object pricingPhases = pigeonVar_list.get(4); pigeonResult.setPricingPhases((List) pricingPhases); + Object installmentPlanDetails = pigeonVar_list.get(5); + pigeonResult.setInstallmentPlanDetails( + (PlatformInstallmentPlanDetails) installmentPlanDetails); return pigeonResult; } } @@ -2755,6 +2933,178 @@ ArrayList toList() { } } + /** + * Pigeon version of ProductDetails.InstallmentPlanDetails. + * https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformInstallmentPlanDetails { + private @NonNull Long commitmentPaymentsCount; + + public @NonNull Long getCommitmentPaymentsCount() { + return commitmentPaymentsCount; + } + + public void setCommitmentPaymentsCount(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"commitmentPaymentsCount\" is null."); + } + this.commitmentPaymentsCount = setterArg; + } + + private @NonNull Long subsequentCommitmentPaymentsCount; + + public @NonNull Long getSubsequentCommitmentPaymentsCount() { + return subsequentCommitmentPaymentsCount; + } + + public void setSubsequentCommitmentPaymentsCount(@NonNull Long setterArg) { + if (setterArg == null) { + throw new IllegalStateException( + "Nonnull field \"subsequentCommitmentPaymentsCount\" is null."); + } + this.subsequentCommitmentPaymentsCount = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformInstallmentPlanDetails() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformInstallmentPlanDetails that = (PlatformInstallmentPlanDetails) o; + return commitmentPaymentsCount.equals(that.commitmentPaymentsCount) + && subsequentCommitmentPaymentsCount.equals(that.subsequentCommitmentPaymentsCount); + } + + @Override + public int hashCode() { + return Objects.hash(commitmentPaymentsCount, subsequentCommitmentPaymentsCount); + } + + public static final class Builder { + + private @Nullable Long commitmentPaymentsCount; + + @CanIgnoreReturnValue + public @NonNull Builder setCommitmentPaymentsCount(@NonNull Long setterArg) { + this.commitmentPaymentsCount = setterArg; + return this; + } + + private @Nullable Long subsequentCommitmentPaymentsCount; + + @CanIgnoreReturnValue + public @NonNull Builder setSubsequentCommitmentPaymentsCount(@NonNull Long setterArg) { + this.subsequentCommitmentPaymentsCount = setterArg; + return this; + } + + public @NonNull PlatformInstallmentPlanDetails build() { + PlatformInstallmentPlanDetails pigeonReturn = new PlatformInstallmentPlanDetails(); + pigeonReturn.setCommitmentPaymentsCount(commitmentPaymentsCount); + pigeonReturn.setSubsequentCommitmentPaymentsCount(subsequentCommitmentPaymentsCount); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(2); + toListResult.add(commitmentPaymentsCount); + toListResult.add(subsequentCommitmentPaymentsCount); + return toListResult; + } + + static @NonNull PlatformInstallmentPlanDetails fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformInstallmentPlanDetails pigeonResult = new PlatformInstallmentPlanDetails(); + Object commitmentPaymentsCount = pigeonVar_list.get(0); + pigeonResult.setCommitmentPaymentsCount((Long) commitmentPaymentsCount); + Object subsequentCommitmentPaymentsCount = pigeonVar_list.get(1); + pigeonResult.setSubsequentCommitmentPaymentsCount((Long) subsequentCommitmentPaymentsCount); + return pigeonResult; + } + } + + /** + * Pigeon version of Java PendingPurchasesParams. + * + *

Generated class from Pigeon that represents data sent in messages. + */ + public static final class PlatformPendingPurchasesParams { + private @NonNull Boolean enablePrepaidPlans; + + public @NonNull Boolean getEnablePrepaidPlans() { + return enablePrepaidPlans; + } + + public void setEnablePrepaidPlans(@NonNull Boolean setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"enablePrepaidPlans\" is null."); + } + this.enablePrepaidPlans = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + PlatformPendingPurchasesParams() {} + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PlatformPendingPurchasesParams that = (PlatformPendingPurchasesParams) o; + return enablePrepaidPlans.equals(that.enablePrepaidPlans); + } + + @Override + public int hashCode() { + return Objects.hash(enablePrepaidPlans); + } + + public static final class Builder { + + private @Nullable Boolean enablePrepaidPlans; + + @CanIgnoreReturnValue + public @NonNull Builder setEnablePrepaidPlans(@NonNull Boolean setterArg) { + this.enablePrepaidPlans = setterArg; + return this; + } + + public @NonNull PlatformPendingPurchasesParams build() { + PlatformPendingPurchasesParams pigeonReturn = new PlatformPendingPurchasesParams(); + pigeonReturn.setEnablePrepaidPlans(enablePrepaidPlans); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList<>(1); + toListResult.add(enablePrepaidPlans); + return toListResult; + } + + static @NonNull PlatformPendingPurchasesParams fromList( + @NonNull ArrayList pigeonVar_list) { + PlatformPendingPurchasesParams pigeonResult = new PlatformPendingPurchasesParams(); + Object enablePrepaidPlans = pigeonVar_list.get(0); + pigeonResult.setEnablePrepaidPlans((Boolean) enablePrepaidPlans); + return pigeonResult; + } + } + private static class PigeonCodec extends StandardMessageCodec { public static final PigeonCodec INSTANCE = new PigeonCodec(); @@ -2766,63 +3116,90 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { case (byte) 129: { Object value = readValue(buffer); - return value == null ? null : PlatformProductType.values()[((Long) value).intValue()]; + return value == null + ? null + : PlatformBillingResponse.values()[((Long) value).intValue()]; } case (byte) 130: { Object value = readValue(buffer); return value == null ? null - : PlatformBillingChoiceMode.values()[((Long) value).intValue()]; + : PlatformReplacementMode.values()[((Long) value).intValue()]; } case (byte) 131: { Object value = readValue(buffer); - return value == null ? null : PlatformPurchaseState.values()[((Long) value).intValue()]; + return value == null ? null : PlatformProductType.values()[((Long) value).intValue()]; } case (byte) 132: { Object value = readValue(buffer); return value == null ? null - : PlatformRecurrenceMode.values()[((Long) value).intValue()]; + : PlatformBillingChoiceMode.values()[((Long) value).intValue()]; } case (byte) 133: - return PlatformQueryProduct.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformBillingClientFeature.values()[((Long) value).intValue()]; + } case (byte) 134: - return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null ? null : PlatformPurchaseState.values()[((Long) value).intValue()]; + } case (byte) 135: - return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); + { + Object value = readValue(buffer); + return value == null + ? null + : PlatformRecurrenceMode.values()[((Long) value).intValue()]; + } case (byte) 136: + return PlatformQueryProduct.fromList((ArrayList) readValue(buffer)); + case (byte) 137: + return PlatformAccountIdentifiers.fromList((ArrayList) readValue(buffer)); + case (byte) 138: + return PlatformBillingResult.fromList((ArrayList) readValue(buffer)); + case (byte) 139: return PlatformOneTimePurchaseOfferDetails.fromList( (ArrayList) readValue(buffer)); - case (byte) 137: + case (byte) 140: return PlatformProductDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 138: + case (byte) 141: return PlatformProductDetailsResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 139: + case (byte) 142: return PlatformAlternativeBillingOnlyReportingDetailsResponse.fromList( (ArrayList) readValue(buffer)); - case (byte) 140: + case (byte) 143: return PlatformBillingConfigResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 141: + case (byte) 144: return PlatformBillingFlowParams.fromList((ArrayList) readValue(buffer)); - case (byte) 142: + case (byte) 145: return PlatformPricingPhase.fromList((ArrayList) readValue(buffer)); - case (byte) 143: + case (byte) 146: return PlatformPurchase.fromList((ArrayList) readValue(buffer)); - case (byte) 144: + case (byte) 147: + return PlatformPendingPurchaseUpdate.fromList((ArrayList) readValue(buffer)); + case (byte) 148: return PlatformPurchaseHistoryRecord.fromList((ArrayList) readValue(buffer)); - case (byte) 145: + case (byte) 149: return PlatformPurchaseHistoryResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 146: + case (byte) 150: return PlatformPurchasesResponse.fromList((ArrayList) readValue(buffer)); - case (byte) 147: + case (byte) 151: return PlatformSubscriptionOfferDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 148: + case (byte) 152: return PlatformUserChoiceDetails.fromList((ArrayList) readValue(buffer)); - case (byte) 149: + case (byte) 153: return PlatformUserChoiceProduct.fromList((ArrayList) readValue(buffer)); + case (byte) 154: + return PlatformInstallmentPlanDetails.fromList((ArrayList) readValue(buffer)); + case (byte) 155: + return PlatformPendingPurchasesParams.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); } @@ -2830,70 +3207,88 @@ protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof PlatformProductType) { + if (value instanceof PlatformBillingResponse) { stream.write(129); + writeValue(stream, value == null ? null : ((PlatformBillingResponse) value).index); + } else if (value instanceof PlatformReplacementMode) { + stream.write(130); + writeValue(stream, value == null ? null : ((PlatformReplacementMode) value).index); + } else if (value instanceof PlatformProductType) { + stream.write(131); writeValue(stream, value == null ? null : ((PlatformProductType) value).index); } else if (value instanceof PlatformBillingChoiceMode) { - stream.write(130); + stream.write(132); writeValue(stream, value == null ? null : ((PlatformBillingChoiceMode) value).index); + } else if (value instanceof PlatformBillingClientFeature) { + stream.write(133); + writeValue(stream, value == null ? null : ((PlatformBillingClientFeature) value).index); } else if (value instanceof PlatformPurchaseState) { - stream.write(131); + stream.write(134); writeValue(stream, value == null ? null : ((PlatformPurchaseState) value).index); } else if (value instanceof PlatformRecurrenceMode) { - stream.write(132); + stream.write(135); writeValue(stream, value == null ? null : ((PlatformRecurrenceMode) value).index); } else if (value instanceof PlatformQueryProduct) { - stream.write(133); + stream.write(136); writeValue(stream, ((PlatformQueryProduct) value).toList()); } else if (value instanceof PlatformAccountIdentifiers) { - stream.write(134); + stream.write(137); writeValue(stream, ((PlatformAccountIdentifiers) value).toList()); } else if (value instanceof PlatformBillingResult) { - stream.write(135); + stream.write(138); writeValue(stream, ((PlatformBillingResult) value).toList()); } else if (value instanceof PlatformOneTimePurchaseOfferDetails) { - stream.write(136); + stream.write(139); writeValue(stream, ((PlatformOneTimePurchaseOfferDetails) value).toList()); } else if (value instanceof PlatformProductDetails) { - stream.write(137); + stream.write(140); writeValue(stream, ((PlatformProductDetails) value).toList()); } else if (value instanceof PlatformProductDetailsResponse) { - stream.write(138); + stream.write(141); writeValue(stream, ((PlatformProductDetailsResponse) value).toList()); } else if (value instanceof PlatformAlternativeBillingOnlyReportingDetailsResponse) { - stream.write(139); + stream.write(142); writeValue( stream, ((PlatformAlternativeBillingOnlyReportingDetailsResponse) value).toList()); } else if (value instanceof PlatformBillingConfigResponse) { - stream.write(140); + stream.write(143); writeValue(stream, ((PlatformBillingConfigResponse) value).toList()); } else if (value instanceof PlatformBillingFlowParams) { - stream.write(141); + stream.write(144); writeValue(stream, ((PlatformBillingFlowParams) value).toList()); } else if (value instanceof PlatformPricingPhase) { - stream.write(142); + stream.write(145); writeValue(stream, ((PlatformPricingPhase) value).toList()); } else if (value instanceof PlatformPurchase) { - stream.write(143); + stream.write(146); writeValue(stream, ((PlatformPurchase) value).toList()); + } else if (value instanceof PlatformPendingPurchaseUpdate) { + stream.write(147); + writeValue(stream, ((PlatformPendingPurchaseUpdate) value).toList()); } else if (value instanceof PlatformPurchaseHistoryRecord) { - stream.write(144); + stream.write(148); writeValue(stream, ((PlatformPurchaseHistoryRecord) value).toList()); } else if (value instanceof PlatformPurchaseHistoryResponse) { - stream.write(145); + stream.write(149); writeValue(stream, ((PlatformPurchaseHistoryResponse) value).toList()); } else if (value instanceof PlatformPurchasesResponse) { - stream.write(146); + stream.write(150); writeValue(stream, ((PlatformPurchasesResponse) value).toList()); } else if (value instanceof PlatformSubscriptionOfferDetails) { - stream.write(147); + stream.write(151); writeValue(stream, ((PlatformSubscriptionOfferDetails) value).toList()); } else if (value instanceof PlatformUserChoiceDetails) { - stream.write(148); + stream.write(152); writeValue(stream, ((PlatformUserChoiceDetails) value).toList()); } else if (value instanceof PlatformUserChoiceProduct) { - stream.write(149); + stream.write(153); writeValue(stream, ((PlatformUserChoiceProduct) value).toList()); + } else if (value instanceof PlatformInstallmentPlanDetails) { + stream.write(154); + writeValue(stream, ((PlatformInstallmentPlanDetails) value).toList()); + } else if (value instanceof PlatformPendingPurchasesParams) { + stream.write(155); + writeValue(stream, ((PlatformPendingPurchasesParams) value).toList()); } else { super.writeValue(stream, value); } @@ -2933,6 +3328,7 @@ public interface InAppPurchaseApi { void startConnection( @NonNull Long callbackHandle, @NonNull PlatformBillingChoiceMode billingMode, + @NonNull PlatformPendingPurchasesParams pendingPurchasesParams, @NonNull Result result); /** Wraps BillingClient#endConnection(BillingClientStateListener). */ void endConnection(); @@ -2972,7 +3368,7 @@ void queryProductDetailsAsync( @NonNull Result result); /** Wraps BillingClient#isFeatureSupported(String). */ @NonNull - Boolean isFeatureSupported(@NonNull String feature); + Boolean isFeatureSupported(@NonNull PlatformBillingClientFeature feature); /** Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). */ void isAlternativeBillingOnlyAvailableAsync(@NonNull Result result); /** Wraps BillingClient#showAlternativeBillingOnlyInformationDialog(). */ @@ -3037,6 +3433,8 @@ static void setUp( ArrayList args = (ArrayList) message; Long callbackHandleArg = (Long) args.get(0); PlatformBillingChoiceMode billingModeArg = (PlatformBillingChoiceMode) args.get(1); + PlatformPendingPurchasesParams pendingPurchasesParamsArg = + (PlatformPendingPurchasesParams) args.get(2); Result resultCallback = new Result() { public void success(PlatformBillingResult result) { @@ -3050,7 +3448,8 @@ public void error(Throwable error) { } }; - api.startConnection(callbackHandleArg, billingModeArg, resultCallback); + api.startConnection( + callbackHandleArg, billingModeArg, pendingPurchasesParamsArg, resultCallback); }); } else { channel.setMessageHandler(null); @@ -3306,7 +3705,8 @@ public void error(Throwable error) { (message, reply) -> { ArrayList wrapped = new ArrayList<>(); ArrayList args = (ArrayList) message; - String featureArg = (String) args.get(0); + PlatformBillingClientFeature featureArg = + (PlatformBillingClientFeature) args.get(0); try { Boolean output = api.isFeatureSupported(featureArg); wrapped.add(0, output); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java index c7305a699382..9949977dccb2 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -10,8 +10,10 @@ import static io.flutter.plugins.inapppurchase.Translator.fromProductDetailsList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchaseHistoryRecordList; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; +import static io.flutter.plugins.inapppurchase.Translator.toBillingClientFeature; import static io.flutter.plugins.inapppurchase.Translator.toProductList; import static io.flutter.plugins.inapppurchase.Translator.toProductTypeString; +import static io.flutter.plugins.inapppurchase.Translator.toReplacementMode; import android.app.Activity; import android.app.Application; @@ -37,6 +39,7 @@ import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseApi; import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetailsResponse; @@ -44,6 +47,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import io.flutter.plugins.inapppurchase.Messages.Result; import java.util.ArrayList; import java.util.HashMap; @@ -51,19 +55,10 @@ /** Handles method channel for the plugin. */ class MethodCallHandlerImpl implements Application.ActivityLifecycleCallbacks, InAppPurchaseApi { - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. - @SuppressWarnings(value = "deprecation") @VisibleForTesting - static final int PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = - com.android.billingclient.api.BillingFlowParams.ProrationMode - .UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; - - @VisibleForTesting - static final int REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = - com.android.billingclient.api.BillingFlowParams.SubscriptionUpdateParams.ReplacementMode - .UNKNOWN_REPLACEMENT_MODE; + static final PlatformReplacementMode + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY = + PlatformReplacementMode.UNKNOWN_REPLACEMENT_MODE; private static final String TAG = "InAppPurchasePlugin"; private static final String LOAD_PRODUCT_DOC_URL = @@ -290,23 +285,12 @@ public void queryProductDetailsAsync( } } - if (params.getProrationMode() != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY - && params.getReplacementMode() - != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - throw new FlutterError( - "IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", - "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.", - null); - } - if (params.getOldProduct() == null - && (params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY - || params.getReplacementMode() - != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY)) { + && (params.getReplacementMode() + != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY)) { throw new FlutterError( "IN_APP_PURCHASE_REQUIRE_OLD_PRODUCT", - "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a proration mode.", + "launchBillingFlow failed because oldProduct is null. You must provide a valid oldProduct in order to use a replacement mode.", null); } else if (params.getOldProduct() != null && !cachedProducts.containsKey(params.getOldProduct())) { @@ -352,31 +336,16 @@ public void queryProductDetailsAsync( && !params.getOldProduct().isEmpty() && params.getPurchaseToken() != null) { subscriptionUpdateParamsBuilder.setOldPurchaseToken(params.getPurchaseToken()); - if (params.getProrationMode() - != PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - setReplaceProrationMode( - subscriptionUpdateParamsBuilder, params.getProrationMode().intValue()); - } if (params.getReplacementMode() != REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { subscriptionUpdateParamsBuilder.setSubscriptionReplacementMode( - params.getReplacementMode().intValue()); + toReplacementMode(params.getReplacementMode())); } paramsBuilder.setSubscriptionUpdateParams(subscriptionUpdateParamsBuilder.build()); } return fromBillingResult(billingClient.launchBillingFlow(activity, paramsBuilder.build())); } - // TODO(gmackall): Replace uses of deprecated setReplaceProrationMode. - // https://github.com/flutter/flutter/issues/128957. - @SuppressWarnings(value = "deprecation") - private void setReplaceProrationMode( - BillingFlowParams.SubscriptionUpdateParams.Builder builder, int prorationMode) { - // The proration mode value has to match one of the following declared in - // https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode - builder.setReplaceProrationMode(prorationMode); - } - @Override public void consumeAsync( @NonNull String purchaseToken, @NonNull Result result) { @@ -428,6 +397,7 @@ public void queryPurchasesAsync( } @Override + @Deprecated public void queryPurchaseHistoryAsync( @NonNull PlatformProductType productType, @NonNull Result result) { @@ -457,10 +427,12 @@ public void queryPurchaseHistoryAsync( public void startConnection( @NonNull Long handle, @NonNull PlatformBillingChoiceMode billingMode, + @NonNull Messages.PlatformPendingPurchasesParams pendingPurchasesParams, @NonNull Result result) { if (billingClient == null) { billingClient = - billingClientFactory.createBillingClient(applicationContext, callbackApi, billingMode); + billingClientFactory.createBillingClient( + applicationContext, callbackApi, billingMode, pendingPurchasesParams); } try { @@ -534,11 +506,11 @@ protected void updateCachedProducts(@Nullable List productDetail } @Override - public @NonNull Boolean isFeatureSupported(@NonNull String feature) { + public @NonNull Boolean isFeatureSupported(@NonNull PlatformBillingClientFeature feature) { if (billingClient == null) { throw getNullBillingClientError(); } - BillingResult billingResult = billingClient.isFeatureSupported(feature); + BillingResult billingResult = billingClient.isFeatureSupported(toBillingClientFeature(feature)); return billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK; } } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java index c06b3acfb503..4fc1b115ec15 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/Translator.java @@ -10,7 +10,9 @@ import com.android.billingclient.api.AlternativeBillingOnlyReportingDetails; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingConfig; +import com.android.billingclient.api.BillingFlowParams; import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.PendingPurchasesParams; import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchaseHistoryRecord; @@ -19,9 +21,12 @@ import io.flutter.plugins.inapppurchase.Messages.FlutterError; import io.flutter.plugins.inapppurchase.Messages.PlatformAccountIdentifiers; import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; import io.flutter.plugins.inapppurchase.Messages.PlatformOneTimePurchaseOfferDetails; +import io.flutter.plugins.inapppurchase.Messages.PlatformPendingPurchaseUpdate; import io.flutter.plugins.inapppurchase.Messages.PlatformPricingPhase; import io.flutter.plugins.inapppurchase.Messages.PlatformProductDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformProductType; @@ -30,6 +35,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseState; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; import io.flutter.plugins.inapppurchase.Messages.PlatformRecurrenceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import io.flutter.plugins.inapppurchase.Messages.PlatformSubscriptionOfferDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceDetails; import io.flutter.plugins.inapppurchase.Messages.PlatformUserChoiceProduct; @@ -146,6 +152,8 @@ static PlatformProductType toPlatformProductType(@NonNull String typeString) { .setOfferTags(subscriptionOfferDetails.getOfferTags()) .setOfferToken(subscriptionOfferDetails.getOfferToken()) .setPricingPhases(fromPricingPhases(subscriptionOfferDetails.getPricingPhases())) + .setInstallmentPlanDetails( + fromInstallmentPlanDetails(subscriptionOfferDetails.getInstallmentPlanDetails())) .build(); } @@ -170,6 +178,20 @@ static PlatformProductType toPlatformProductType(@NonNull String typeString) { .build(); } + static @Nullable Messages.PlatformInstallmentPlanDetails fromInstallmentPlanDetails( + @Nullable ProductDetails.InstallmentPlanDetails installmentPlanDetails) { + if (installmentPlanDetails == null) { + return null; + } + + return new Messages.PlatformInstallmentPlanDetails.Builder() + .setCommitmentPaymentsCount( + (long) installmentPlanDetails.getInstallmentPlanCommitmentPaymentsCount()) + .setSubsequentCommitmentPaymentsCount( + (long) installmentPlanDetails.getSubsequentInstallmentPlanCommitmentPaymentsCount()) + .build(); + } + static PlatformRecurrenceMode toPlatformRecurrenceMode(int mode) { switch (mode) { case ProductDetails.RecurrenceMode.FINITE_RECURRING: @@ -217,9 +239,27 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { .setObfuscatedProfileId(accountIdentifiers.getObfuscatedProfileId()) .build()); } + + Purchase.PendingPurchaseUpdate pendingPurchaseUpdate = purchase.getPendingPurchaseUpdate(); + if (pendingPurchaseUpdate != null) { + builder.setPendingPurchaseUpdate(fromPendingPurchaseUpdate(pendingPurchaseUpdate)); + } + return builder.build(); } + static @Nullable PlatformPendingPurchaseUpdate fromPendingPurchaseUpdate( + @Nullable Purchase.PendingPurchaseUpdate pendingPurchaseUpdate) { + if (pendingPurchaseUpdate == null) { + return null; + } + + return new Messages.PlatformPendingPurchaseUpdate.Builder() + .setPurchaseToken(pendingPurchaseUpdate.getPurchaseToken()) + .setProducts(pendingPurchaseUpdate.getProducts()) + .build(); + } + static @NonNull PlatformPurchaseHistoryRecord fromPurchaseHistoryRecord( @NonNull PurchaseHistoryRecord purchaseHistoryRecord) { return new PlatformPurchaseHistoryRecord.Builder() @@ -260,11 +300,41 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { static @NonNull PlatformBillingResult fromBillingResult(@NonNull BillingResult billingResult) { return new PlatformBillingResult.Builder() - .setResponseCode((long) billingResult.getResponseCode()) + .setResponseCode(fromBillingResponseCode(billingResult.getResponseCode())) .setDebugMessage(billingResult.getDebugMessage()) .build(); } + static @NonNull PlatformBillingResponse fromBillingResponseCode(int billingResponseCode) { + switch (billingResponseCode) { + case BillingClient.BillingResponseCode.FEATURE_NOT_SUPPORTED: + return PlatformBillingResponse.FEATURE_NOT_SUPPORTED; + case BillingClient.BillingResponseCode.SERVICE_DISCONNECTED: + return PlatformBillingResponse.SERVICE_DISCONNECTED; + case BillingClient.BillingResponseCode.OK: + return PlatformBillingResponse.OK; + case BillingClient.BillingResponseCode.USER_CANCELED: + return PlatformBillingResponse.USER_CANCELED; + case BillingClient.BillingResponseCode.SERVICE_UNAVAILABLE: + return PlatformBillingResponse.SERVICE_UNAVAILABLE; + case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: + return PlatformBillingResponse.BILLING_UNAVAILABLE; + case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: + return PlatformBillingResponse.ITEM_UNAVAILABLE; + case BillingClient.BillingResponseCode.DEVELOPER_ERROR: + return PlatformBillingResponse.DEVELOPER_ERROR; + case BillingClient.BillingResponseCode.ERROR: + return PlatformBillingResponse.ERROR; + case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: + return PlatformBillingResponse.ITEM_ALREADY_OWNED; + case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: + return PlatformBillingResponse.ITEM_NOT_OWNED; + case BillingClient.BillingResponseCode.NETWORK_ERROR: + return PlatformBillingResponse.NETWORK_ERROR; + } + return PlatformBillingResponse.ERROR; + } + static @NonNull PlatformUserChoiceDetails fromUserChoiceDetails( @NonNull UserChoiceDetails userChoiceDetails) { return new PlatformUserChoiceDetails.Builder() @@ -317,6 +387,57 @@ static PlatformPurchaseState toPlatformPurchaseState(int state) { .build(); } + static @NonNull PendingPurchasesParams toPendingPurchasesParams( + @Nullable Messages.PlatformPendingPurchasesParams platformPendingPurchasesParams) { + PendingPurchasesParams.Builder pendingPurchasesBuilder = + PendingPurchasesParams.newBuilder().enableOneTimeProducts(); + if (platformPendingPurchasesParams != null + && platformPendingPurchasesParams.getEnablePrepaidPlans()) { + pendingPurchasesBuilder.enablePrepaidPlans(); + } + return pendingPurchasesBuilder.build(); + } + + static @NonNull String toBillingClientFeature(@NonNull PlatformBillingClientFeature feature) { + switch (feature) { + case ALTERNATIVE_BILLING_ONLY: + return BillingClient.FeatureType.ALTERNATIVE_BILLING_ONLY; + case BILLING_CONFIG: + return BillingClient.FeatureType.BILLING_CONFIG; + case EXTERNAL_OFFER: + return BillingClient.FeatureType.EXTERNAL_OFFER; + case IN_APP_MESSAGING: + return BillingClient.FeatureType.IN_APP_MESSAGING; + case PRICE_CHANGE_CONFIRMATION: + return BillingClient.FeatureType.PRICE_CHANGE_CONFIRMATION; + case PRODUCT_DETAILS: + return BillingClient.FeatureType.PRODUCT_DETAILS; + case SUBSCRIPTIONS: + return BillingClient.FeatureType.SUBSCRIPTIONS; + case SUBSCRIPTIONS_UPDATE: + return BillingClient.FeatureType.SUBSCRIPTIONS_UPDATE; + } + throw new FlutterError("UNKNOWN_FEATURE", "Unknown client feature: " + feature, null); + } + + static int toReplacementMode(@NonNull PlatformReplacementMode replacementMode) { + switch (replacementMode) { + case CHARGE_FULL_PRICE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_FULL_PRICE; + case CHARGE_PRORATED_PRICE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + case DEFERRED: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.DEFERRED; + case WITHOUT_PRORATION: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITHOUT_PRORATION; + case WITH_TIME_PRORATION: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.WITH_TIME_PRORATION; + case UNKNOWN_REPLACEMENT_MODE: + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE; + } + return BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.UNKNOWN_REPLACEMENT_MODE; + } + /** * Gets the symbol of for the given currency code for the default {@link Locale.Category#DISPLAY * DISPLAY} locale. For example, for the US Dollar, the symbol is "$" if the default locale is the diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java index d1c91fb8219c..bc08ff67d286 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImplTest.java @@ -52,19 +52,19 @@ public void setUp() { @Test public void playBillingOnly() { - // No logic to verify just ensure creation works. + // No logic to verify, just ensure creation works. BillingClient client = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, null); assertNotNull(client); } @Test public void alternativeBillingOnly() { - // No logic to verify just ensure creation works. + // No logic to verify, just ensure creation works. BillingClient client = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, null); assertNotNull(client); } @@ -78,7 +78,7 @@ public void userChoiceBilling() { final BillingClient billingClient = factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.USER_CHOICE_BILLING); + context, mockCallbackApi, PlatformBillingChoiceMode.USER_CHOICE_BILLING, null); UserChoiceDetails details = mock(UserChoiceDetails.class); final String externalTransactionToken = "someLongTokenId1234"; @@ -98,6 +98,17 @@ public void userChoiceBilling() { assertTrue(callbackCaptor.getValue().getProducts().isEmpty()); } + @Test + public void pendingPurchasesForPrepaidPlans() { + // No logic to verify, just ensure creation works. + Messages.PlatformPendingPurchasesParams params = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(true).build(); + BillingClient client = + factory.createBillingClient( + context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, params); + assertNotNull(client); + } + @After public void tearDown() throws Exception { openMocks.close(); diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java index 58b1c42a0e41..33e6a0fa8276 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/MethodCallHandlerTest.java @@ -5,8 +5,8 @@ package io.flutter.plugins.inapppurchase; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.ACTIVITY_UNAVAILABLE; -import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; import static io.flutter.plugins.inapppurchase.MethodCallHandlerImpl.REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY; +import static io.flutter.plugins.inapppurchase.Translator.fromBillingResponseCode; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.unmodifiableList; @@ -59,6 +59,7 @@ import io.flutter.plugins.inapppurchase.Messages.InAppPurchaseCallbackApi; import io.flutter.plugins.inapppurchase.Messages.PlatformAlternativeBillingOnlyReportingDetailsResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingChoiceMode; +import io.flutter.plugins.inapppurchase.Messages.PlatformBillingClientFeature; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingConfigResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingFlowParams; import io.flutter.plugins.inapppurchase.Messages.PlatformBillingResult; @@ -67,6 +68,7 @@ import io.flutter.plugins.inapppurchase.Messages.PlatformPurchaseHistoryResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformPurchasesResponse; import io.flutter.plugins.inapppurchase.Messages.PlatformQueryProduct; +import io.flutter.plugins.inapppurchase.Messages.PlatformReplacementMode; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -104,22 +106,33 @@ public class MethodCallHandlerTest { @Mock Context context; @Mock ActivityPluginBinding mockActivityPluginBinding; + private final Messages.PlatformPendingPurchasesParams defaultPendingPurchasesParams = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(false).build(); + private final Long DEFAULT_HANDLE = 1L; @Before public void setUp() { openMocks = MockitoAnnotations.openMocks(this); + // Use the same client no matter if alternative billing is enabled or not. when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY)) + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams)) .thenReturn(mockBillingClient); when(factory.createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY)) + context, + mockCallbackApi, + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, + defaultPendingPurchasesParams)) .thenReturn(mockBillingClient); when(factory.createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING))) + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + any(Messages.PlatformPendingPurchasesParams.class))) .thenReturn(mockBillingClient); methodChannelHandler = new MethodCallHandlerImpl(activity, context, mockCallbackApi, factory); when(mockActivityPluginBinding.getActivity()).thenReturn(activity); @@ -159,10 +172,15 @@ public void isReady_clientDisconnected() { @Test public void startConnection() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult = buildBillingResult(); captor.getValue().onBillingSetupFinished(billingResult); @@ -177,11 +195,15 @@ public void startConnection() { @Test public void startConnectionAlternativeBillingOnly() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( - context, mockCallbackApi, PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY); + context, + mockCallbackApi, + PlatformBillingChoiceMode.ALTERNATIVE_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult = buildBillingResult(); captor.getValue().onBillingSetupFinished(billingResult); @@ -193,17 +215,41 @@ public void startConnectionAlternativeBillingOnly() { verify(platformBillingResult, never()).error(any()); } + @Test + public void startConnectionPendingPurchasesPrepaidPlans() { + Messages.PlatformPendingPurchasesParams pendingPurchasesParams = + new Messages.PlatformPendingPurchasesParams.Builder().setEnablePrepaidPlans(true).build(); + ArgumentCaptor captor = + mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING, pendingPurchasesParams); + verify(platformBillingResult, never()).success(any()); + verify(factory, times(1)) + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.USER_CHOICE_BILLING, + pendingPurchasesParams); + + BillingResult billingResult = buildBillingResult(); + captor.getValue().onBillingSetupFinished(billingResult); + + ArgumentCaptor resultCaptor = + ArgumentCaptor.forClass(PlatformBillingResult.class); + verify(platformBillingResult, times(1)).success(resultCaptor.capture()); + } + @Test public void startConnectionUserChoiceBilling() { ArgumentCaptor captor = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); + mockStartConnection( + PlatformBillingChoiceMode.USER_CHOICE_BILLING, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + any(Messages.PlatformPendingPurchasesParams.class)); BillingResult billingResult = BillingResult.newBuilder() @@ -221,10 +267,15 @@ public void startConnectionUserChoiceBilling() { public void userChoiceBillingOnSecondConnection() { // First connection. ArgumentCaptor captor1 = - mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) - .createBillingClient(context, mockCallbackApi, PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + .createBillingClient( + context, + mockCallbackApi, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams); BillingResult billingResult1 = BillingResult.newBuilder() @@ -248,13 +299,15 @@ public void userChoiceBillingOnSecondConnection() { // Second connection. ArgumentCaptor captor2 = - mockStartConnection(PlatformBillingChoiceMode.USER_CHOICE_BILLING); + mockStartConnection( + PlatformBillingChoiceMode.USER_CHOICE_BILLING, defaultPendingPurchasesParams); verify(platformBillingResult, never()).success(any()); verify(factory, times(1)) .createBillingClient( any(Context.class), any(InAppPurchaseCallbackApi.class), - eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING)); + eq(PlatformBillingChoiceMode.USER_CHOICE_BILLING), + eq(defaultPendingPurchasesParams)); BillingResult billingResult2 = BillingResult.newBuilder() @@ -273,7 +326,10 @@ public void startConnection_multipleCalls() { doNothing().when(mockBillingClient).startConnection(captor.capture()); methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, platformBillingResult); + DEFAULT_HANDLE, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + platformBillingResult); verify(platformBillingResult, never()).success(any()); BillingResult billingResult1 = buildBillingResult(); BillingResult billingResult2 = @@ -295,7 +351,8 @@ public void startConnection_multipleCalls() { ArgumentCaptor.forClass(PlatformBillingResult.class); verify(platformBillingResult, times(1)).success(resultCaptor.capture()); assertEquals( - resultCaptor.getValue().getResponseCode().longValue(), billingResult1.getResponseCode()); + resultCaptor.getValue().getResponseCode(), + fromBillingResponseCode(billingResult1.getResponseCode())); assertEquals(resultCaptor.getValue().getDebugMessage(), billingResult1.getDebugMessage()); verify(platformBillingResult, never()).error(any()); } @@ -480,7 +537,10 @@ public void endConnection() { @SuppressWarnings("unchecked") final Messages.Result mockResult = mock(Messages.Result.class); methodChannelHandler.startConnection( - disconnectCallbackHandle, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + disconnectCallbackHandle, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + mockResult); final BillingClientStateListener stateListener = captor.getValue(); // Disconnect the connected client @@ -555,10 +615,8 @@ public void launchBillingFlow_null_AccountId_do_not_crash() { queryForProducts(singletonList(productId)); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -582,10 +640,8 @@ public void launchBillingFlow_ok_null_OldProduct() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -613,10 +669,8 @@ public void launchBillingFlow_ok_null_Activity() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -638,10 +692,8 @@ public void launchBillingFlow_ok_oldProduct() { paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -667,10 +719,8 @@ public void launchBillingFlow_ok_AccountId() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -687,27 +737,21 @@ public void launchBillingFlow_ok_AccountId() { assertResultsMatch(platformResult, billingResult); } - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Proration() { // Fetch the product details first and query the method call String productId = "foo"; String oldProductId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -724,25 +768,19 @@ public void launchBillingFlow_ok_Proration() { assertResultsMatch(platformResult, billingResult); } - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Proration_with_null_OldProduct() { // Fetch the product details first and query the method call String productId = "foo"; String accountId = "account"; String queryOldProductId = "oldFoo"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(null); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -766,16 +804,13 @@ public void launchBillingFlow_ok_Replacement_with_null_OldProduct() { String productId = "foo"; String accountId = "account"; String queryOldProductId = "oldFoo"; - int replacementMode = - BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_PRORATED_PRICE; queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(null); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - paramsBuilder.setReplacementMode((long) replacementMode); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -793,60 +828,20 @@ public void launchBillingFlow_ok_Replacement_with_null_OldProduct() { } @Test - @SuppressWarnings(value = "deprecation") - public void launchBillingFlow_ok_Proration_and_Replacement_conflict() { - // Fetch the product details first and query the method call - String productId = "foo"; - String accountId = "account"; - String queryOldProductId = "oldFoo"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_PRORATED_PRICE; - int replacementMode = - BillingFlowParams.SubscriptionUpdateParams.ReplacementMode.CHARGE_PRORATED_PRICE; - queryForProducts(unmodifiableList(asList(productId, queryOldProductId))); - PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); - paramsBuilder.setProduct(productId); - paramsBuilder.setAccountId(accountId); - paramsBuilder.setOldProduct(queryOldProductId); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode((long) replacementMode); - - // Launch the billing flow - BillingResult billingResult = buildBillingResult(); - when(mockBillingClient.launchBillingFlow(any(), any())).thenReturn(billingResult); - - // Assert that the synchronous call throws an exception. - FlutterError exception = - assertThrows( - FlutterError.class, - () -> methodChannelHandler.launchBillingFlow(paramsBuilder.build())); - assertEquals("IN_APP_PURCHASE_CONFLICT_PRORATION_MODE_REPLACEMENT_MODE", exception.code); - assertTrue( - Objects.requireNonNull(exception.getMessage()) - .contains( - "launchBillingFlow failed because you provided both prorationMode and replacementMode. You can only provide one of them.")); - } - - // TODO(gmackall): Replace uses of deprecated ProrationMode enum values with new - // ReplacementMode enum values. - // https://github.com/flutter/flutter/issues/128957. - @Test - @SuppressWarnings(value = "deprecation") public void launchBillingFlow_ok_Full() { // Fetch the product details first and query the method call String productId = "foo"; String oldProductId = "oldFoo"; String purchaseToken = "purchaseTokenFoo"; String accountId = "account"; - int prorationMode = BillingFlowParams.ProrationMode.IMMEDIATE_AND_CHARGE_FULL_PRICE; + PlatformReplacementMode replacementMode = PlatformReplacementMode.CHARGE_FULL_PRICE; queryForProducts(unmodifiableList(asList(productId, oldProductId))); PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); paramsBuilder.setPurchaseToken(purchaseToken); - paramsBuilder.setProrationMode((long) prorationMode); - paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + paramsBuilder.setReplacementMode(replacementMode); // Launch the billing flow BillingResult billingResult = buildBillingResult(); @@ -872,10 +867,8 @@ public void launchBillingFlow_clientDisconnected() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -895,10 +888,8 @@ public void launchBillingFlow_productNotFound() { PlatformBillingFlowParams.Builder paramsBuilder = new PlatformBillingFlowParams.Builder(); paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -921,10 +912,8 @@ public void launchBillingFlow_oldProductNotFound() { paramsBuilder.setProduct(productId); paramsBuilder.setAccountId(accountId); paramsBuilder.setOldProduct(oldProductId); - paramsBuilder.setProrationMode( - (long) PRORATION_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); paramsBuilder.setReplacementMode( - (long) REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + REPLACEMENT_MODE_UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); // Assert that the synchronous call throws an exception. FlutterError exception = @@ -982,12 +971,13 @@ public void queryPurchases_returns_success() { PlatformPurchasesResponse purchasesResponse = resultCaptor.getValue(); assertEquals( - purchasesResponse.getBillingResult().getResponseCode().longValue(), - BillingClient.BillingResponseCode.OK); + purchasesResponse.getBillingResult().getResponseCode(), + Messages.PlatformBillingResponse.OK); assertTrue(purchasesResponse.getPurchases().isEmpty()); } @Test + @SuppressWarnings(value = "deprecation") public void queryPurchaseHistoryAsync() { // Set up an established billing client and all our mocked responses establishConnectedBillingClient(); @@ -1015,6 +1005,7 @@ public void queryPurchaseHistoryAsync() { } @Test + @SuppressWarnings(value = "deprecation") public void queryPurchaseHistoryAsync_clientDisconnected() { methodChannelHandler.endConnection(); @@ -1116,7 +1107,7 @@ public void isFutureSupported_true() { BillingResult billingResult = buildBillingResult(BillingClient.BillingResponseCode.OK); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); - assertTrue(methodChannelHandler.isFeatureSupported(feature)); + assertTrue(methodChannelHandler.isFeatureSupported(PlatformBillingClientFeature.SUBSCRIPTIONS)); } @Test @@ -1127,30 +1118,34 @@ public void isFutureSupported_false() { BillingResult billingResult = buildBillingResult(BillingResponseCode.FEATURE_NOT_SUPPORTED); when(mockBillingClient.isFeatureSupported(feature)).thenReturn(billingResult); - assertFalse(methodChannelHandler.isFeatureSupported(feature)); + assertFalse( + methodChannelHandler.isFeatureSupported(PlatformBillingClientFeature.SUBSCRIPTIONS)); } /** * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. + * Messages.PlatformPendingPurchasesParams, Messages.Result)} with startup params. * *

Defaults to play billing only which is the default. */ private ArgumentCaptor mockStartConnection() { - return mockStartConnection(PlatformBillingChoiceMode.PLAY_BILLING_ONLY); + return mockStartConnection( + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, defaultPendingPurchasesParams); } /** * Call {@link MethodCallHandlerImpl#startConnection(Long, PlatformBillingChoiceMode, - * Messages.Result)} with startup params. + * Messages.PlatformPendingPurchasesParams, Messages.Result)} with startup params. */ private ArgumentCaptor mockStartConnection( - PlatformBillingChoiceMode billingChoiceMode) { + PlatformBillingChoiceMode billingChoiceMode, + Messages.PlatformPendingPurchasesParams pendingPurchasesParams) { ArgumentCaptor captor = ArgumentCaptor.forClass(BillingClientStateListener.class); doNothing().when(mockBillingClient).startConnection(captor.capture()); - methodChannelHandler.startConnection(DEFAULT_HANDLE, billingChoiceMode, platformBillingResult); + methodChannelHandler.startConnection( + DEFAULT_HANDLE, billingChoiceMode, pendingPurchasesParams, platformBillingResult); return captor; } @@ -1158,7 +1153,10 @@ private void establishConnectedBillingClient() { @SuppressWarnings("unchecked") final Messages.Result mockResult = mock(Messages.Result.class); methodChannelHandler.startConnection( - DEFAULT_HANDLE, PlatformBillingChoiceMode.PLAY_BILLING_ONLY, mockResult); + DEFAULT_HANDLE, + PlatformBillingChoiceMode.PLAY_BILLING_ONLY, + defaultPendingPurchasesParams, + mockResult); } private void queryForProducts(List productIdList) { @@ -1255,7 +1253,8 @@ private BillingResult buildBillingResult(int responseCode) { } private void assertResultsMatch(PlatformBillingResult pigeonResult, BillingResult nativeResult) { - assertEquals(pigeonResult.getResponseCode().longValue(), nativeResult.getResponseCode()); + assertEquals( + pigeonResult.getResponseCode(), fromBillingResponseCode(nativeResult.getResponseCode())); assertEquals(pigeonResult.getDebugMessage(), nativeResult.getDebugMessage()); } diff --git a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java index fff2ef2ffbc1..a3235f923c87 100644 --- a/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java +++ b/packages/in_app_purchase/in_app_purchase_android/android/src/test/java/io/flutter/plugins/inapppurchase/TranslatorTest.java @@ -165,7 +165,7 @@ public void fromBillingResult() { .build(); Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); + assertEquals(Messages.PlatformBillingResponse.OK, platformResult.getResponseCode()); assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); } @@ -175,7 +175,7 @@ public void fromBillingResult_debugMessageNull() { BillingResult.newBuilder().setResponseCode(BillingClient.BillingResponseCode.OK).build(); Messages.PlatformBillingResult platformResult = Translator.fromBillingResult(newBillingResult); - assertEquals(platformResult.getResponseCode().longValue(), newBillingResult.getResponseCode()); + assertEquals(Messages.PlatformBillingResponse.OK, platformResult.getResponseCode()); assertEquals(platformResult.getDebugMessage(), newBillingResult.getDebugMessage()); } @@ -242,6 +242,15 @@ private void assertSerialized( assertEquals(expected.getOfferTags(), serialized.getOfferTags()); assertEquals(expected.getOfferToken(), serialized.getOfferToken()); assertSerialized(expected.getPricingPhases(), serialized.getPricingPhases()); + + ProductDetails.InstallmentPlanDetails expectedInstallmentPlanDetails = + expected.getInstallmentPlanDetails(); + Messages.PlatformInstallmentPlanDetails serializedInstallmentPlanDetails = + serialized.getInstallmentPlanDetails(); + assertEquals(expectedInstallmentPlanDetails == null, serializedInstallmentPlanDetails == null); + if (expectedInstallmentPlanDetails != null && serializedInstallmentPlanDetails != null) { + assertSerialized(expectedInstallmentPlanDetails, serializedInstallmentPlanDetails); + } } private void assertSerialized( @@ -287,6 +296,17 @@ private void assertSerialized(Purchase expected, Messages.PlatformPurchase seria Objects.requireNonNull(serialized.getAccountIdentifiers()).getObfuscatedProfileId()); } + private void assertSerialized( + ProductDetails.InstallmentPlanDetails expected, + Messages.PlatformInstallmentPlanDetails serialized) { + assertEquals( + expected.getInstallmentPlanCommitmentPaymentsCount(), + serialized.getCommitmentPaymentsCount().intValue()); + assertEquals( + expected.getSubsequentInstallmentPlanCommitmentPaymentsCount(), + serialized.getSubsequentCommitmentPaymentsCount().intValue()); + } + private String productTypeFromPlatform(Messages.PlatformProductType type) { switch (type) { case INAPP: diff --git a/packages/in_app_purchase/in_app_purchase_android/build.yaml b/packages/in_app_purchase/in_app_purchase_android/build.yaml deleted file mode 100644 index 651a557fc1ca..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/build.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# See https://pub.dev/packages/build_config -targets: - $default: - builders: - json_serializable: - options: - any_map: true - create_to_json: false diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart index 4773446cdeeb..04e8e23d262a 100644 --- a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart +++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart @@ -424,8 +424,8 @@ class _MyAppState extends State<_MyApp> { changeSubscriptionParam: oldSubscription != null ? ChangeSubscriptionParam( oldPurchaseDetails: oldSubscription, - prorationMode: ProrationMode - .immediateWithTimeProration) + replacementMode: + ReplacementMode.withTimeProration) : null); if (productDetails.id == _kConsumableId) { _inAppPurchasePlatform.buyConsumable( @@ -680,19 +680,15 @@ class _FeatureCard extends StatelessWidget { } String _featureToString(BillingClientFeature feature) { - switch (feature) { - case BillingClientFeature.inAppItemsOnVR: - return 'inAppItemsOnVR'; - case BillingClientFeature.priceChangeConfirmation: - return 'priceChangeConfirmation'; - case BillingClientFeature.productDetails: - return 'productDetails'; - case BillingClientFeature.subscriptions: - return 'subscriptions'; - case BillingClientFeature.subscriptionsOnVR: - return 'subscriptionsOnVR'; - case BillingClientFeature.subscriptionsUpdate: - return 'subscriptionsUpdate'; - } + return switch (feature) { + BillingClientFeature.alternativeBillingOnly => 'alternativeBillingOnly', + BillingClientFeature.priceChangeConfirmation => 'priceChangeConfirmation', + BillingClientFeature.productDetails => 'productDetails', + BillingClientFeature.subscriptions => 'subscriptions', + BillingClientFeature.subscriptionsUpdate => 'subscriptionsUpdate', + BillingClientFeature.billingConfig => 'billingConfig', + BillingClientFeature.externalOffer => 'externalOffer', + BillingClientFeature.inAppMessaging => 'inAppMessaging', + }; } } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart index bbabd1477a28..50162f624661 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'alternative_billing_only_reporting_details_wrapper.g.dart'; - /// The error message shown when the map representing details is invalid from method channel. /// /// This usually indicates a serious underlying code issue in the plugin. @@ -20,8 +14,6 @@ const String kInvalidAlternativeBillingReportingDetailsErrorMessage = 'Invalid AlternativeBillingReportingDetails map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class AlternativeBillingOnlyReportingDetailsWrapper implements HasBillingResponse { @@ -31,23 +23,6 @@ class AlternativeBillingOnlyReportingDetailsWrapper this.debugMessage, this.externalTransactionToken = ''}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory AlternativeBillingOnlyReportingDetailsWrapper.fromJson( - Map? map) { - if (map == null || map.isEmpty) { - return const AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidAlternativeBillingReportingDetailsErrorMessage, - ); - } - return _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; @@ -56,11 +31,9 @@ class AlternativeBillingOnlyReportingDetailsWrapper /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') final String? debugMessage; /// https://developer.android.com/reference/com/android/billingclient/api/AlternativeBillingOnlyReportingDetails#getExternalTransactionToken() - @JsonKey(defaultValue: '') final String externalTransactionToken; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart deleted file mode 100644 index 04578fb4a45b..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/alternative_billing_only_reporting_details_wrapper.g.dart +++ /dev/null @@ -1,17 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'alternative_billing_only_reporting_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -AlternativeBillingOnlyReportingDetailsWrapper - _$AlternativeBillingOnlyReportingDetailsWrapperFromJson(Map json) => - AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart index 8e265dbdcee8..0c63080686a4 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_manager.dart @@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'billing_client_wrapper.dart'; +import 'pending_purchases_params_wrapper.dart'; import 'purchase_wrapper.dart'; import 'user_choice_details_wrapper.dart'; @@ -44,6 +45,8 @@ class BillingClientManager { BillingClientManager( {@visibleForTesting BillingClientFactory? billingClientFactory}) : _billingChoiceMode = BillingChoiceMode.playBillingOnly, + _pendingPurchasesParams = + const PendingPurchasesParamsWrapper(enablePrepaidPlans: false), _billingClientFactory = billingClientFactory ?? _createBillingClient { _connect(); } @@ -85,6 +88,7 @@ class BillingClientManager { BillingChoiceMode _billingChoiceMode; final BillingClientFactory _billingClientFactory; + PendingPurchasesParamsWrapper _pendingPurchasesParams; bool _isConnecting = false; bool _isDisposed = false; @@ -161,9 +165,14 @@ class BillingClientManager { Future reconnectWithBillingChoiceMode( BillingChoiceMode billingChoiceMode) async { _billingChoiceMode = billingChoiceMode; - // Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect. - await client.endConnection(); - await _connect(); + await _reconnect(); + } + + /// Ends connection to [BillingClient] and reconnects with [pendingPurchasesParams]. + Future reconnectWithPendingPurchasesParams( + PendingPurchasesParamsWrapper pendingPurchasesParams) async { + _pendingPurchasesParams = pendingPurchasesParams; + await _reconnect(); } // If disposed, does nothing. @@ -179,13 +188,21 @@ class BillingClientManager { _isConnecting = true; _readyFuture = Future.sync(() async { await client.startConnection( - onBillingServiceDisconnected: _connect, - billingChoiceMode: _billingChoiceMode); + onBillingServiceDisconnected: _connect, + billingChoiceMode: _billingChoiceMode, + pendingPurchasesParams: _pendingPurchasesParams, + ); _isConnecting = false; }); return _readyFuture; } + Future _reconnect() async { + // Ends connection and triggers OnBillingServiceDisconnected, which causes reconnect. + await client.endConnection(); + await _connect(); + } + void _onPurchasesUpdated(PurchasesResultWrapper event) { if (_isDisposed) { return; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart index 1f46c3f1de20..17dfbf29c194 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.dart @@ -5,14 +5,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; import '../messages.g.dart'; import '../pigeon_converters.dart'; import 'billing_config_wrapper.dart'; - -part 'billing_client_wrapper.g.dart'; +import 'pending_purchases_params_wrapper.dart'; /// Callback triggered by Play in response to purchase activity. /// @@ -81,18 +79,6 @@ class BillingClient { return _hostApi.isReady(); } - /// Enable the [BillingClientWrapper] to handle pending purchases. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - void enablePendingPurchases() { - // No-op, until it is time to completely remove this method from the API. - } - /// Calls /// [`BillingClient#startConnection(BillingClientStateListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.html#startconnection) /// to create and connect a `BillingClient` instance. @@ -103,14 +89,23 @@ class BillingClient { /// /// This triggers the creation of a new `BillingClient` instance in Java if /// one doesn't already exist. - Future startConnection( - {required OnBillingServiceDisconnected onBillingServiceDisconnected, - BillingChoiceMode billingChoiceMode = - BillingChoiceMode.playBillingOnly}) async { + Future startConnection({ + required OnBillingServiceDisconnected onBillingServiceDisconnected, + BillingChoiceMode billingChoiceMode = BillingChoiceMode.playBillingOnly, + PendingPurchasesParamsWrapper? pendingPurchasesParams, + }) async { hostCallbackHandler.disconnectCallbacks.add(onBillingServiceDisconnected); - return resultWrapperFromPlatform(await _hostApi.startConnection( + return resultWrapperFromPlatform( + await _hostApi.startConnection( hostCallbackHandler.disconnectCallbacks.length - 1, - platformBillingChoiceMode(billingChoiceMode))); + platformBillingChoiceMode(billingChoiceMode), + switch (pendingPurchasesParams) { + final PendingPurchasesParamsWrapper params => + pendingPurchasesParamsFromWrapper(params), + null => PlatformPendingPurchasesParams(enablePrepaidPlans: false) + }, + ), + ); } /// Calls @@ -181,7 +176,7 @@ class BillingClient { /// existing subscription. /// The [oldProduct](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setOldPurchaseToken(java.lang.String)) and [purchaseToken] are the product id and purchase token that the user is upgrading or downgrading from. /// [purchaseToken] must not be `null` if [oldProduct] is not `null`. - /// The [prorationMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setReplaceProrationMode(int)) is the mode of proration during subscription upgrade/downgrade. + /// The [replacementMode](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.Builder#setSubscriptionReplacementMode(int)) is the mode of replacement during subscription upgrade/downgrade. /// This value will only be effective if the `oldProduct` is also set. Future launchBillingFlow( {required String product, @@ -190,17 +185,15 @@ class BillingClient { String? obfuscatedProfileId, String? oldProduct, String? purchaseToken, - ProrationMode? prorationMode, ReplacementMode? replacementMode}) async { assert((oldProduct == null) == (purchaseToken == null), 'oldProduct and purchaseToken must both be set, or both be null.'); return resultWrapperFromPlatform( await _hostApi.launchBillingFlow(PlatformBillingFlowParams( product: product, - prorationMode: const ProrationModeConverter().toJson(prorationMode ?? - ProrationMode.unknownSubscriptionUpgradeDowngradePolicy), - replacementMode: const ReplacementModeConverter() - .toJson(replacementMode ?? ReplacementMode.unknownReplacementMode), + replacementMode: replacementModeFromWrapper( + replacementMode ?? ReplacementMode.unknownReplacementMode, + ), offerToken: offerToken, accountId: accountId, obfuscatedProfileId: obfuscatedProfileId, @@ -253,6 +246,7 @@ class BillingClient { /// /// This wraps /// [`BillingClient#queryPurchaseHistoryAsync(QueryPurchaseHistoryParams, PurchaseHistoryResponseListener)`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient#queryPurchaseHistoryAsync(com.android.billingclient.api.QueryPurchaseHistoryParams,%20com.android.billingclient.api.PurchaseHistoryResponseListener)). + @Deprecated('Use queryPurchases') Future queryPurchaseHistory( ProductType productType) async { return purchaseHistoryResultFromPlatform( @@ -298,8 +292,8 @@ class BillingClient { /// Checks if the specified feature or capability is supported by the Play Store. /// Call this to check if a [BillingClientFeature] is supported by the device. Future isFeatureSupported(BillingClientFeature feature) async { - return _hostApi.isFeatureSupported( - const BillingClientFeatureConverter().toJson(feature)); + return _hostApi + .isFeatureSupported(billingClientFeatureFromWrapper(feature)); } /// Fetches billing config info into a [BillingConfigWrapper] object. @@ -382,62 +376,44 @@ typedef OnBillingServiceDisconnected = void Function(); /// [`BillingClient.BillingResponse`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponse). /// See the `BillingResponse` docs for more explanation of the different /// constants. -@JsonEnum(alwaysCreate: true) enum BillingResponse { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - /// The request has reached the maximum timeout before Google Play responds. - @JsonValue(-3) serviceTimeout, /// The requested feature is not supported by Play Store on the current device. - @JsonValue(-2) featureNotSupported, /// The Play Store service is not connected now - potentially transient state. - @JsonValue(-1) serviceDisconnected, /// Success. - @JsonValue(0) ok, /// The user pressed back or canceled a dialog. - @JsonValue(1) userCanceled, /// The network connection is down. - @JsonValue(2) serviceUnavailable, /// The billing API version is not supported for the type requested. - @JsonValue(3) billingUnavailable, /// The requested product is not available for purchase. - @JsonValue(4) itemUnavailable, /// Invalid arguments provided to the API. - @JsonValue(5) developerError, /// Fatal error during the API action. - @JsonValue(6) error, /// Failure to purchase since item is already owned. - @JsonValue(7) itemAlreadyOwned, /// Failure to consume since item is not owned. - @JsonValue(8) itemNotOwned, /// Network connection failure between the device and Play systems. - @JsonValue(12) networkError, } @@ -445,186 +421,30 @@ enum BillingResponse { /// /// [playBillingOnly] (google Play billing only). /// [alternativeBillingOnly] (app provided billing with reporting to Play). -@JsonEnum(alwaysCreate: true) enum BillingChoiceMode { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - // Values must match what is used in - // in_app_purchase_android/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java - /// Billing through google Play. Default state. - @JsonValue(0) playBillingOnly, /// Billing through app provided flow. - @JsonValue(1) alternativeBillingOnly, /// Users can choose Play billing or alternative billing. - @JsonValue(2) userChoiceBilling, } -/// Serializer for [BillingChoiceMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingChoiceModeConverter()`. -class BillingChoiceModeConverter - implements JsonConverter { - /// Default const constructor. - const BillingChoiceModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingChoiceMode fromJson(int? json) { - if (json == null) { - return BillingChoiceMode.playBillingOnly; - } - return $enumDecode(_$BillingChoiceModeEnumMap, json); - } - - @override - int toJson(BillingChoiceMode object) => _$BillingChoiceModeEnumMap[object]!; -} - -/// Serializer for [BillingResponse]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingResponseConverter()`. -class BillingResponseConverter implements JsonConverter { - /// Default const constructor. - const BillingResponseConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingResponse fromJson(int? json) { - if (json == null) { - return BillingResponse.error; - } - return $enumDecode(_$BillingResponseEnumMap, json); - } - - @override - int toJson(BillingResponse object) => _$BillingResponseEnumMap[object]!; -} - /// Enum representing potential [ProductDetailsWrapper.productType]s. /// /// Wraps /// [`BillingClient.ProductType`](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.ProductType) /// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) enum ProductType { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - /// A one time product. Acquired in a single transaction. - @JsonValue('inapp') inapp, /// A product requiring a recurring charge over time. - @JsonValue('subs') subs, } -/// Serializer for [ProductType]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ProductTypeConverter()`. -class ProductTypeConverter implements JsonConverter { - /// Default const constructor. - const ProductTypeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - ProductType fromJson(String? json) { - if (json == null) { - return ProductType.inapp; - } - return $enumDecode(_$ProductTypeEnumMap, json); - } - - @override - String toJson(ProductType object) => _$ProductTypeEnumMap[object]!; -} - -/// Enum representing the proration mode. -/// -/// When upgrading or downgrading a subscription, set this mode to provide details -/// about the proration that will be applied when the subscription changes. -/// -/// Wraps [`BillingFlowParams.ProrationMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.ProrationMode) -/// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) -enum ProrationMode { -// WARNING: Changes to this class need to be reflected in our generated code. -// Run `flutter packages pub run build_runner watch` to rebuild and watch for -// further changes. - - /// Unknown upgrade or downgrade policy. - @JsonValue(0) - unknownSubscriptionUpgradeDowngradePolicy, - - /// Replacement takes effect immediately, and the remaining time will be prorated - /// and credited to the user. - /// - /// This is the current default behavior. - @JsonValue(1) - immediateWithTimeProration, - - /// 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. - @JsonValue(2) - immediateAndChargeProratedPrice, - - /// Replacement takes effect immediately, and the new price will be charged on next - /// recurrence time. - /// - /// The billing cycle stays the same. - @JsonValue(3) - immediateWithoutProration, - - /// Replacement takes effect when the old plan expires, and the new price will - /// be charged at the same time. - @JsonValue(4) - deferred, - - /// 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. - @JsonValue(5) - immediateAndChargeFullPrice, -} - -/// Serializer for [ProrationMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ProrationModeConverter()`. -class ProrationModeConverter implements JsonConverter { - /// Default const constructor. - const ProrationModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - ProrationMode fromJson(int? json) { - if (json == null) { - return ProrationMode.unknownSubscriptionUpgradeDowngradePolicy; - } - return $enumDecode(_$ProrationModeEnumMap, json); - } - - @override - int toJson(ProrationMode object) => _$ProrationModeEnumMap[object]!; -} - /// Enum representing the replacement mode. /// /// When upgrading or downgrading a subscription, set this mode to provide details @@ -632,122 +452,61 @@ class ProrationModeConverter implements JsonConverter { /// /// Wraps [`BillingFlowParams.SubscriptionUpdateParams.ReplacementMode`](https://developer.android.com/reference/com/android/billingclient/api/BillingFlowParams.SubscriptionUpdateParams.ReplacementMode) /// See the linked documentation for an explanation of the different constants. -@JsonEnum(alwaysCreate: true) enum ReplacementMode { -// WARNING: Changes to this class need to be reflected in our generated code. -// Run `flutter packages pub run build_runner watch` to rebuild and watch for -// further changes. - /// Unknown upgrade or downgrade policy. - @JsonValue(0) unknownReplacementMode, /// Replacement takes effect immediately, and the remaining time will be prorated /// and credited to the user. /// /// This is the current default behavior. - @JsonValue(1) withTimeProration, /// 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. - @JsonValue(2) chargeProratedPrice, /// Replacement takes effect immediately, and the new price will be charged on next /// recurrence time. /// /// The billing cycle stays the same. - @JsonValue(3) withoutProration, /// Replacement takes effect when the old plan expires, and the new price will /// be charged at the same time. - @JsonValue(6) deferred, /// 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. - @JsonValue(5) chargeFullPrice, } -/// Serializer for [ReplacementMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@ReplacementModeConverter()`. -class ReplacementModeConverter implements JsonConverter { - /// Default const constructor. - const ReplacementModeConverter(); - - @override - ReplacementMode fromJson(int? json) { - if (json == null) { - return ReplacementMode.unknownReplacementMode; - } - return $enumDecode(_$ReplacementModeEnumMap, json); - } - - @override - int toJson(ReplacementMode object) => _$ReplacementModeEnumMap[object]!; -} - /// Features/capabilities supported by [BillingClient.isFeatureSupported()](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType). -@JsonEnum(alwaysCreate: true) enum BillingClientFeature { - // WARNING: Changes to this class need to be reflected in our generated code. - // Run `flutter packages pub run build_runner watch` to rebuild and watch for - // further changes. - // - // JsonValues need to match constant values defined in https://developer.android.com/reference/com/android/billingclient/api/BillingClient.FeatureType#summary + /// Alternative billing only. + alternativeBillingOnly, - /// Purchase/query for in-app items on VR. - @JsonValue('inAppItemsOnVr') - inAppItemsOnVR, + /// Get billing config. + billingConfig, + + /// Play billing library support for external offer. + externalOffer, + + /// Show in-app messages. + inAppMessaging, /// Launch a price change confirmation flow. - @JsonValue('priceChangeConfirmation') priceChangeConfirmation, - /// Play billing library support for querying and purchasing with ProductDetails. - @JsonValue('fff') + /// Play billing library support for querying and purchasing. productDetails, /// Purchase/query for subscriptions. - @JsonValue('subscriptions') subscriptions, - /// Purchase/query for subscriptions on VR. - @JsonValue('subscriptionsOnVr') - subscriptionsOnVR, - /// Subscriptions update/replace. - @JsonValue('subscriptionsUpdate') - subscriptionsUpdate -} - -/// Serializer for [BillingClientFeature]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@BillingClientFeatureConverter()`. -class BillingClientFeatureConverter - implements JsonConverter { - /// Default const constructor. - const BillingClientFeatureConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - BillingClientFeature fromJson(String json) { - return $enumDecode( - _$BillingClientFeatureEnumMap.cast(), - json); - } - - @override - String toJson(BillingClientFeature object) => - _$BillingClientFeatureEnumMap[object]!; + subscriptionsUpdate, } diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart deleted file mode 100644 index eb0033193d95..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_client_wrapper.g.dart +++ /dev/null @@ -1,61 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_client_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -const _$BillingResponseEnumMap = { - BillingResponse.serviceTimeout: -3, - BillingResponse.featureNotSupported: -2, - BillingResponse.serviceDisconnected: -1, - BillingResponse.ok: 0, - BillingResponse.userCanceled: 1, - BillingResponse.serviceUnavailable: 2, - BillingResponse.billingUnavailable: 3, - BillingResponse.itemUnavailable: 4, - BillingResponse.developerError: 5, - BillingResponse.error: 6, - BillingResponse.itemAlreadyOwned: 7, - BillingResponse.itemNotOwned: 8, - BillingResponse.networkError: 12, -}; - -const _$BillingChoiceModeEnumMap = { - BillingChoiceMode.playBillingOnly: 0, - BillingChoiceMode.alternativeBillingOnly: 1, - BillingChoiceMode.userChoiceBilling: 2, -}; - -const _$ProductTypeEnumMap = { - ProductType.inapp: 'inapp', - ProductType.subs: 'subs', -}; - -const _$ProrationModeEnumMap = { - ProrationMode.unknownSubscriptionUpgradeDowngradePolicy: 0, - ProrationMode.immediateWithTimeProration: 1, - ProrationMode.immediateAndChargeProratedPrice: 2, - ProrationMode.immediateWithoutProration: 3, - ProrationMode.deferred: 4, - ProrationMode.immediateAndChargeFullPrice: 5, -}; - -const _$ReplacementModeEnumMap = { - ReplacementMode.unknownReplacementMode: 0, - ReplacementMode.withTimeProration: 1, - ReplacementMode.chargeProratedPrice: 2, - ReplacementMode.withoutProration: 3, - ReplacementMode.deferred: 6, - ReplacementMode.chargeFullPrice: 5, -}; - -const _$BillingClientFeatureEnumMap = { - BillingClientFeature.inAppItemsOnVR: 'inAppItemsOnVr', - BillingClientFeature.priceChangeConfirmation: 'priceChangeConfirmation', - BillingClientFeature.productDetails: 'fff', - BillingClientFeature.subscriptions: 'subscriptions', - BillingClientFeature.subscriptionsOnVR: 'subscriptionsOnVr', - BillingClientFeature.subscriptionsUpdate: 'subscriptionsUpdate', -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart index 3020a120e26d..1062466932e1 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'billing_config_wrapper.g.dart'; - /// The error message shown when the map represents billing config is invalid from method channel. /// /// This usually indicates a serious underlining code issue in the plugin. @@ -20,30 +14,12 @@ const String kInvalidBillingConfigErrorMessage = 'Invalid billing config map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class BillingConfigWrapper implements HasBillingResponse { /// Constructs the object with [responseCode] and [debugMessage]. const BillingConfigWrapper( {required this.responseCode, this.debugMessage, this.countryCode = ''}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory BillingConfigWrapper.fromJson(Map? map) { - if (map == null || map.isEmpty) { - return const BillingConfigWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingConfigErrorMessage, - ); - } - return _$BillingConfigWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; @@ -52,11 +28,9 @@ class BillingConfigWrapper implements HasBillingResponse { /// /// Defaults to `null`. /// This message uses an en-US locale and should not be shown to users. - @JsonKey(defaultValue: '') final String? debugMessage; /// https://developer.android.com/reference/com/android/billingclient/api/BillingConfig#getCountryCode() - @JsonKey(defaultValue: '') final String countryCode; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart deleted file mode 100644 index fc8328aec176..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_config_wrapper.g.dart +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_config_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BillingConfigWrapper _$BillingConfigWrapperFromJson(Map json) => - BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String? ?? '', - countryCode: json['countryCode'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart index 5faf19764f83..eb93fa6c068d 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.dart @@ -3,15 +3,9 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'billing_response_wrapper.g.dart'; - /// The error message shown when the map represents billing result is invalid from method channel. /// /// This usually indicates a serious underlining code issue in the plugin. @@ -20,28 +14,11 @@ const String kInvalidBillingResultErrorMessage = 'Invalid billing result map from method channel.'; /// Params containing the response code and the debug message from the Play Billing API response. -@JsonSerializable() -@BillingResponseConverter() @immutable class BillingResultWrapper implements HasBillingResponse { /// Constructs the object with [responseCode] and [debugMessage]. const BillingResultWrapper({required this.responseCode, this.debugMessage}); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory BillingResultWrapper.fromJson(Map? map) { - if (map == null || map.isEmpty) { - return const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage); - } - return _$BillingResultWrapperFromJson(map); - } - /// Response code returned in the Play Billing API calls. @override final BillingResponse responseCode; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart deleted file mode 100644 index a3b28403983e..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/billing_response_wrapper.g.dart +++ /dev/null @@ -1,14 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'billing_response_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -BillingResultWrapper _$BillingResultWrapperFromJson(Map json) => - BillingResultWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - debugMessage: json['debugMessage'] as String?, - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart index f22184958830..8dfebeee73ef 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.dart @@ -3,17 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; - -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'one_time_purchase_offer_details_wrapper.g.dart'; /// Dart wrapper around [`com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.OneTimePurchaseOfferDetails). /// /// Represents the offer details to buy a one-time purchase product. -@JsonSerializable() @immutable class OneTimePurchaseOfferDetailsWrapper { /// Creates a [OneTimePurchaseOfferDetailsWrapper]. @@ -23,18 +16,9 @@ class OneTimePurchaseOfferDetailsWrapper { required this.priceCurrencyCode, }); - /// Factory for creating a [OneTimePurchaseOfferDetailsWrapper] from a [Map] - /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory OneTimePurchaseOfferDetailsWrapper.fromJson( - Map map) => - _$OneTimePurchaseOfferDetailsWrapperFromJson(map); - /// Formatted price for the payment, including its currency sign. /// /// For tax exclusive countries, the price doesn't include tax. - @JsonKey(defaultValue: '') final String formattedPrice; /// The price for the payment in micro-units, where 1,000,000 micro-units @@ -42,14 +26,12 @@ class OneTimePurchaseOfferDetailsWrapper { /// /// For example, if price is "€7.99", price_amount_micros is "7990000". This /// value represents the localized, rounded price for a particular currency. - @JsonKey(defaultValue: 0) final int priceAmountMicros; /// The ISO 4217 currency code for price. /// /// For example, if price is specified in British pounds sterling, currency /// code is "GBP". - @JsonKey(defaultValue: '') final String priceCurrencyCode; @override diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart deleted file mode 100644 index f26a38162fcd..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/one_time_purchase_offer_details_wrapper.g.dart +++ /dev/null @@ -1,15 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'one_time_purchase_offer_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -OneTimePurchaseOfferDetailsWrapper _$OneTimePurchaseOfferDetailsWrapperFromJson( - Map json) => - OneTimePurchaseOfferDetailsWrapper( - formattedPrice: json['formattedPrice'] as String? ?? '', - priceAmountMicros: (json['priceAmountMicros'] as num?)?.toInt() ?? 0, - priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart new file mode 100644 index 000000000000..d702cae27bf4 --- /dev/null +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/pending_purchases_params_wrapper.dart @@ -0,0 +1,39 @@ +// 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:flutter/foundation.dart'; + +/// Dart wrapper around [`com.android.billingclient.api.PendingPurchasesParams`](https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams). +/// +/// Represents the parameters to enable pending purchases. +@immutable +class PendingPurchasesParamsWrapper { + /// Creates a [PendingPurchasesParamsWrapper]. + const PendingPurchasesParamsWrapper({ + required this.enablePrepaidPlans, + }); + + /// Enables pending purchase for prepaid plans. + /// + /// Handling pending purchases for prepaid plans is different from one-time products. + /// Your application will need to be updated to ensure subscription entitlements are + /// managed correctly with pending transactions. + /// To learn more see https://developer.android.com/google/play/billing/subscriptions#pending. + final bool enablePrepaidPlans; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is PendingPurchasesParamsWrapper && + other.enablePrepaidPlans == enablePrepaidPlans; + } + + @override + int get hashCode { + return enablePrepaidPlans.hashCode; + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart index 43303d4d2378..7059d496a163 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.dart @@ -3,21 +3,13 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'product_details_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.ProductDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails). /// /// Contains the details of an available product in Google Play Billing. /// Represents the details of a one-time or subscription product. -@JsonSerializable() -@ProductTypeConverter() @immutable class ProductDetailsWrapper { /// Creates a [ProductDetailsWrapper] with the given purchase details. @@ -31,51 +23,37 @@ class ProductDetailsWrapper { required this.title, }); - /// Factory for creating a [ProductDetailsWrapper] from a [Map] with the - /// product details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory ProductDetailsWrapper.fromJson(Map map) => - _$ProductDetailsWrapperFromJson(map); - /// Textual description of the product. - @JsonKey(defaultValue: '') final String description; /// The name of the product being sold. /// /// Similar to [title], but does not include the name of the app which owns /// the product. Example: 100 Gold Coins. - @JsonKey(defaultValue: '') final String name; /// The offer details of a one-time purchase product. /// /// [oneTimePurchaseOfferDetails] is only set for [ProductType.inapp]. Returns /// null for [ProductType.subs]. - @JsonKey(defaultValue: null) final OneTimePurchaseOfferDetailsWrapper? oneTimePurchaseOfferDetails; /// The product's id. - @JsonKey(defaultValue: '') final String productId; /// The [ProductType] of the product. - @JsonKey(defaultValue: ProductType.subs) final ProductType productType; /// A list containing all available offers to purchase a subscription product. /// /// [subscriptionOfferDetails] is only set for [ProductType.subs]. Returns /// null for [ProductType.inapp]. - @JsonKey(defaultValue: null) final List? subscriptionOfferDetails; /// The title of the product being sold. /// /// Similar to [name], but includes the name of the app which owns the /// product. Example: 100 Gold Coins (Coin selling app). - @JsonKey(defaultValue: '') final String title; @override @@ -111,7 +89,6 @@ class ProductDetailsWrapper { /// Translation of [`com.android.billingclient.api.ProductDetailsResponseListener`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetailsResponseListener.html). /// /// Returned by [BillingClient.queryProductDetails]. -@JsonSerializable() @immutable class ProductDetailsResponseWrapper implements HasBillingResponse { /// Creates a [ProductDetailsResponseWrapper] with the given purchase details. @@ -120,20 +97,10 @@ class ProductDetailsResponseWrapper implements HasBillingResponse { required this.productDetailsList, }); - /// Constructs an instance of this from a key value map of data. - /// - /// The map needs to have named string keys with values matching the names and - /// types of all of the members on this class. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory ProductDetailsResponseWrapper.fromJson(Map map) => - _$ProductDetailsResponseWrapperFromJson(map); - /// The final result of the [BillingClient.queryProductDetails] call. final BillingResultWrapper billingResult; /// A list of [ProductDetailsWrapper] matching the query to [BillingClient.queryProductDetails]. - @JsonKey(defaultValue: []) final List productDetailsList; @override @@ -155,41 +122,15 @@ class ProductDetailsResponseWrapper implements HasBillingResponse { } /// Recurrence mode of the pricing phase. -@JsonEnum(alwaysCreate: true) enum RecurrenceMode { /// The billing plan payment recurs for a fixed number of billing period set /// in billingCycleCount. - @JsonValue(2) finiteRecurring, /// The billing plan payment recurs for infinite billing periods unless /// cancelled. - @JsonValue(1) infiniteRecurring, /// The billing plan payment is a one time charge that does not repeat. - @JsonValue(3) nonRecurring, } - -/// Serializer for [RecurrenceMode]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@RecurrenceModeConverter()`. -class RecurrenceModeConverter implements JsonConverter { - /// Default const constructor. - const RecurrenceModeConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - RecurrenceMode fromJson(int? json) { - if (json == null) { - return RecurrenceMode.nonRecurring; - } - return $enumDecode(_$RecurrenceModeEnumMap, json); - } - - @override - int toJson(RecurrenceMode object) => _$RecurrenceModeEnumMap[object]!; -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart deleted file mode 100644 index de8079d81571..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_details_wrapper.g.dart +++ /dev/null @@ -1,49 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'product_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ProductDetailsWrapper _$ProductDetailsWrapperFromJson(Map json) => - ProductDetailsWrapper( - description: json['description'] as String? ?? '', - name: json['name'] as String? ?? '', - oneTimePurchaseOfferDetails: json['oneTimePurchaseOfferDetails'] == null - ? null - : OneTimePurchaseOfferDetailsWrapper.fromJson( - Map.from( - json['oneTimePurchaseOfferDetails'] as Map)), - productId: json['productId'] as String? ?? '', - productType: json['productType'] == null - ? ProductType.subs - : const ProductTypeConverter() - .fromJson(json['productType'] as String?), - subscriptionOfferDetails: - (json['subscriptionOfferDetails'] as List?) - ?.map((e) => SubscriptionOfferDetailsWrapper.fromJson( - Map.from(e as Map))) - .toList(), - title: json['title'] as String? ?? '', - ); - -ProductDetailsResponseWrapper _$ProductDetailsResponseWrapperFromJson( - Map json) => - ProductDetailsResponseWrapper( - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - productDetailsList: (json['productDetailsList'] as List?) - ?.map((e) => ProductDetailsWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -const _$RecurrenceModeEnumMap = { - RecurrenceMode.finiteRecurring: 2, - RecurrenceMode.infiniteRecurring: 1, - RecurrenceMode.nonRecurring: 3, -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart index 48cd9ee738ec..65833bcb61c0 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.dart @@ -3,17 +3,10 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'product_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.Product`](https://developer.android.com/reference/com/android/billingclient/api/QueryProductDetailsParams.Product). -@JsonSerializable(createToJson: true) @immutable class ProductWrapper { /// Creates a new [ProductWrapper]. @@ -22,11 +15,7 @@ class ProductWrapper { required this.productType, }); - /// Creates a JSON representation of this product. - Map toJson() => _$ProductWrapperToJson(this); - /// The product identifier. - @JsonKey(defaultValue: '') final String productId; /// The product type. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart deleted file mode 100644 index c3ba8f4a82ec..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/product_wrapper.g.dart +++ /dev/null @@ -1,23 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'product_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -ProductWrapper _$ProductWrapperFromJson(Map json) => ProductWrapper( - productId: json['productId'] as String? ?? '', - productType: $enumDecode(_$ProductTypeEnumMap, json['productType']), - ); - -Map _$ProductWrapperToJson(ProductWrapper instance) => - { - 'productId': instance.productId, - 'productType': _$ProductTypeEnumMap[instance.productType]!, - }; - -const _$ProductTypeEnumMap = { - ProductType.inapp: 'inapp', - ProductType.subs: 'subs', -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart index 8d17d81c9a49..1572be847cf0 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.dart @@ -4,15 +4,9 @@ import 'package:flutter/foundation.dart'; import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'purchase_wrapper.g.dart'; - /// Data structure representing a successful purchase. /// /// All purchase information should also be verified manually, with your @@ -20,8 +14,6 @@ part 'purchase_wrapper.g.dart'; /// purchase"](https://developer.android.com/google/play/billing/billing_library_overview#Verify). /// /// This wraps [`com.android.billlingclient.api.Purchase`](https://developer.android.com/reference/com/android/billingclient/api/Purchase) -@JsonSerializable() -@PurchaseStateConverter() @immutable class PurchaseWrapper { /// Creates a purchase wrapper with the given purchase details. @@ -39,14 +31,9 @@ class PurchaseWrapper { required this.purchaseState, this.obfuscatedAccountId, this.obfuscatedProfileId, + this.pendingPurchaseUpdate, }); - /// Factory for creating a [PurchaseWrapper] from a [Map] with the purchase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchaseWrapper.fromJson(Map map) => - _$PurchaseWrapperFromJson(map); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -65,7 +52,8 @@ class PurchaseWrapper { other.isAutoRenewing == isAutoRenewing && other.originalJson == originalJson && other.isAcknowledged == isAcknowledged && - other.purchaseState == purchaseState; + other.purchaseState == purchaseState && + other.pendingPurchaseUpdate == pendingPurchaseUpdate; } @override @@ -79,32 +67,27 @@ class PurchaseWrapper { isAutoRenewing, originalJson, isAcknowledged, - purchaseState); + purchaseState, + pendingPurchaseUpdate); /// The unique ID for this purchase. Corresponds to the Google Payments order /// ID. - @JsonKey(defaultValue: '') final String orderId; /// The package name the purchase was made from. - @JsonKey(defaultValue: '') final String packageName; /// When the purchase was made, as an epoch timestamp. - @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [ProductDetailsWrapper], user, and purchase. - @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. - @JsonKey(defaultValue: '') final String signature; /// The product IDs of this purchase. - @JsonKey(defaultValue: []) final List products; /// True for subscriptions that renew automatically. Does not apply to @@ -122,7 +105,6 @@ class PurchaseWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). - @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. @@ -136,7 +118,6 @@ class PurchaseWrapper { /// /// A successful purchase has to be acknowledged within 3 days after the purchase via [BillingClient.acknowledgePurchase]. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. - @JsonKey(defaultValue: false) final bool isAcknowledged; /// Determines the current state of the purchase. @@ -158,6 +139,51 @@ class PurchaseWrapper { /// directly calling [BillingClient.launchBillingFlow] and is not available /// on the generic [InAppPurchasePlatform]. final String? obfuscatedProfileId; + + /// The [PendingPurchaseUpdateWrapper] for an uncommitted transaction. + /// + /// A PendingPurchaseUpdate is normally generated from a pending transaction + /// upgrading/downgrading an existing subscription. + /// Returns null if this purchase does not have a pending transaction. + final PendingPurchaseUpdateWrapper? pendingPurchaseUpdate; +} + +@immutable + +/// Represents a pending change/update to the existing purchase. +/// +/// This wraps [`com.android.billingclient.api.Purchase.PendingPurchaseUpdate`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PendingPurchaseUpdate). +class PendingPurchaseUpdateWrapper { + /// Creates a pending purchase wrapper update wrapper with the given purchase details. + const PendingPurchaseUpdateWrapper({ + required this.purchaseToken, + required this.products, + }); + + /// A token that uniquely identifies this pending transaction. + final String purchaseToken; + + /// The product IDs of this pending purchase update. + final List products; + + @override + bool operator ==(Object other) { + if (identical(other, this)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is PendingPurchaseUpdateWrapper && + other.purchaseToken == purchaseToken && + listEquals(other.products, products); + } + + @override + int get hashCode => Object.hash( + purchaseToken, + products.hashCode, + ); } /// Data structure representing a purchase history record. @@ -169,7 +195,6 @@ class PurchaseWrapper { /// * See also: [BillingClient.queryPurchaseHistory] for obtaining a [PurchaseHistoryRecordWrapper]. // We can optionally make [PurchaseWrapper] extend or implement [PurchaseHistoryRecordWrapper]. // For now, we keep them separated classes to be consistent with Android's BillingClient implementation. -@JsonSerializable() @immutable class PurchaseHistoryRecordWrapper { /// Creates a [PurchaseHistoryRecordWrapper] with the given record details. @@ -182,27 +207,17 @@ class PurchaseHistoryRecordWrapper { required this.developerPayload, }); - /// Factory for creating a [PurchaseHistoryRecordWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchaseHistoryRecordWrapper.fromJson(Map map) => - _$PurchaseHistoryRecordWrapperFromJson(map); - /// When the purchase was made, as an epoch timestamp. - @JsonKey(defaultValue: 0) final int purchaseTime; /// A unique ID for a given [ProductDetailsWrapper], user, and purchase. - @JsonKey(defaultValue: '') final String purchaseToken; /// Signature of purchase data, signed with the developer's private key. Uses /// RSASSA-PKCS1-v1_5. - @JsonKey(defaultValue: '') final String signature; /// The product ID of this purchase. - @JsonKey(defaultValue: []) final List products; /// Details about this purchase, in JSON. @@ -211,7 +226,6 @@ class PurchaseHistoryRecordWrapper { /// device"](https://developer.android.com/google/play/billing/billing_library_overview#Verify-purchase-device). /// Note though that verifying a purchase locally is inherently insecure (see /// the article for more details). - @JsonKey(defaultValue: '') final String originalJson; /// The payload specified by the developer when the purchase was acknowledged or consumed. @@ -254,8 +268,6 @@ class PurchaseHistoryRecordWrapper { /// [BillingResponse] to signify the overall state of the transaction. /// /// Wraps [`com.android.billingclient.api.Purchase.PurchasesResult`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult). -@JsonSerializable() -@BillingResponseConverter() @immutable class PurchasesResultWrapper implements HasBillingResponse { /// Creates a [PurchasesResultWrapper] with the given purchase result details. @@ -264,12 +276,6 @@ class PurchasesResultWrapper implements HasBillingResponse { required this.billingResult, required this.purchasesList}); - /// Factory for creating a [PurchaseResultWrapper] from a [Map] with the result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchasesResultWrapper.fromJson(Map map) => - _$PurchasesResultWrapperFromJson(map); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -300,7 +306,6 @@ class PurchasesResultWrapper implements HasBillingResponse { /// The list of successful purchases made in this transaction. /// /// May be empty, especially if [responseCode] is not [BillingResponse.ok]. - @JsonKey(defaultValue: []) final List purchasesList; } @@ -308,20 +313,12 @@ class PurchasesResultWrapper implements HasBillingResponse { /// /// Contains a potentially empty list of [PurchaseHistoryRecordWrapper]s and a [BillingResultWrapper] /// that contains a detailed description of the status. -@JsonSerializable() -@BillingResponseConverter() @immutable class PurchasesHistoryResult implements HasBillingResponse { /// Creates a [PurchasesHistoryResult] with the provided history. const PurchasesHistoryResult( {required this.billingResult, required this.purchaseHistoryRecordList}); - /// Factory for creating a [PurchasesHistoryResult] from a [Map] with the history result details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PurchasesHistoryResult.fromJson(Map map) => - _$PurchasesHistoryResultFromJson(map); - @override BillingResponse get responseCode => billingResult.responseCode; @@ -347,7 +344,6 @@ class PurchasesHistoryResult implements HasBillingResponse { /// The list of queried purchase history records. /// /// May be empty, especially if [billingResult.responseCode] is not [BillingResponse.ok]. - @JsonKey(defaultValue: []) final List purchaseHistoryRecordList; } @@ -356,20 +352,17 @@ class PurchasesHistoryResult implements HasBillingResponse { /// Wraps /// [`BillingClient.api.Purchase.PurchaseState`](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchaseState.html). /// * See also: [PurchaseWrapper]. -@JsonEnum(alwaysCreate: true) enum PurchaseStateWrapper { /// The state is unspecified. /// /// No actions on the [PurchaseWrapper] should be performed on this state. /// This is a catch-all. It should never be returned by the Play Billing Library. - @JsonValue(0) unspecified_state, /// The user has completed the purchase process. /// /// The production should be delivered and then the purchase should be acknowledged. /// * See also [BillingClient.acknowledgePurchase] for more details on acknowledging purchases. - @JsonValue(1) purchased, /// The user has started the purchase process. @@ -379,44 +372,5 @@ enum PurchaseStateWrapper { /// /// You can also choose to remind the user to complete the purchase if you detected a /// [PurchaseWrapper] is still in the `pending` state in the future while calling [BillingClient.queryPurchases]. - @JsonValue(2) pending, } - -/// Serializer for [PurchaseStateWrapper]. -/// -/// Use these in `@JsonSerializable()` classes by annotating them with -/// `@PurchaseStateConverter()`. -class PurchaseStateConverter - implements JsonConverter { - /// Default const constructor. - const PurchaseStateConverter(); - - @override - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - PurchaseStateWrapper fromJson(int? json) { - if (json == null) { - return PurchaseStateWrapper.unspecified_state; - } - return $enumDecode(_$PurchaseStateWrapperEnumMap, json); - } - - @override - int toJson(PurchaseStateWrapper object) => - _$PurchaseStateWrapperEnumMap[object]!; - - /// Converts the purchase state stored in `object` to a [PurchaseStatus]. - /// - /// [PurchaseStateWrapper.unspecified_state] is mapped to [PurchaseStatus.error]. - PurchaseStatus toPurchaseStatus(PurchaseStateWrapper object) { - switch (object) { - case PurchaseStateWrapper.pending: - return PurchaseStatus.pending; - case PurchaseStateWrapper.purchased: - return PurchaseStatus.purchased; - case PurchaseStateWrapper.unspecified_state: - return PurchaseStatus.error; - } - } -} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart deleted file mode 100644 index b97afce0f628..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/purchase_wrapper.g.dart +++ /dev/null @@ -1,75 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'purchase_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -PurchaseWrapper _$PurchaseWrapperFromJson(Map json) => PurchaseWrapper( - orderId: json['orderId'] as String? ?? '', - packageName: json['packageName'] as String? ?? '', - purchaseTime: (json['purchaseTime'] as num?)?.toInt() ?? 0, - purchaseToken: json['purchaseToken'] as String? ?? '', - signature: json['signature'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - isAutoRenewing: json['isAutoRenewing'] as bool, - originalJson: json['originalJson'] as String? ?? '', - developerPayload: json['developerPayload'] as String?, - isAcknowledged: json['isAcknowledged'] as bool? ?? false, - purchaseState: const PurchaseStateConverter() - .fromJson((json['purchaseState'] as num?)?.toInt()), - obfuscatedAccountId: json['obfuscatedAccountId'] as String?, - obfuscatedProfileId: json['obfuscatedProfileId'] as String?, - ); - -PurchaseHistoryRecordWrapper _$PurchaseHistoryRecordWrapperFromJson(Map json) => - PurchaseHistoryRecordWrapper( - purchaseTime: (json['purchaseTime'] as num?)?.toInt() ?? 0, - purchaseToken: json['purchaseToken'] as String? ?? '', - signature: json['signature'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - originalJson: json['originalJson'] as String? ?? '', - developerPayload: json['developerPayload'] as String?, - ); - -PurchasesResultWrapper _$PurchasesResultWrapperFromJson(Map json) => - PurchasesResultWrapper( - responseCode: const BillingResponseConverter() - .fromJson((json['responseCode'] as num?)?.toInt()), - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - purchasesList: (json['purchasesList'] as List?) - ?.map((e) => - PurchaseWrapper.fromJson(Map.from(e as Map))) - .toList() ?? - [], - ); - -PurchasesHistoryResult _$PurchasesHistoryResultFromJson(Map json) => - PurchasesHistoryResult( - billingResult: - BillingResultWrapper.fromJson((json['billingResult'] as Map?)?.map( - (k, e) => MapEntry(k as String, e), - )), - purchaseHistoryRecordList: - (json['purchaseHistoryRecordList'] as List?) - ?.map((e) => PurchaseHistoryRecordWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -const _$PurchaseStateWrapperEnumMap = { - PurchaseStateWrapper.unspecified_state: 0, - PurchaseStateWrapper.purchased: 1, - PurchaseStateWrapper.pending: 2, -}; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart index 4b5642ed0431..f31757e922f3 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.dart @@ -3,20 +3,13 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import 'billing_client_wrapper.dart'; import 'product_details_wrapper.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'subscription_offer_details_wrapper.g.dart'; - /// Dart wrapper around [`com.android.billingclient.api.ProductDetails.SubscriptionOfferDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.SubscriptionOfferDetails). /// /// Represents the available purchase plans to buy a subscription product. -@JsonSerializable() @immutable class SubscriptionOfferDetailsWrapper { /// Creates a [SubscriptionOfferDetailsWrapper]. @@ -26,39 +19,31 @@ class SubscriptionOfferDetailsWrapper { required this.offerTags, required this.offerIdToken, required this.pricingPhases, + this.installmentPlanDetails, }); - /// Factory for creating a [SubscriptionOfferDetailsWrapper] from a [Map] - /// with the offer details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory SubscriptionOfferDetailsWrapper.fromJson(Map map) => - _$SubscriptionOfferDetailsWrapperFromJson(map); - /// The base plan id associated with the subscription product. - @JsonKey(defaultValue: '') final String basePlanId; /// The offer id associated with the subscription product. /// /// This field is only set for a discounted offer. Returns null for a regular /// base plan. - @JsonKey(defaultValue: null) final String? offerId; /// The offer tags associated with this Subscription Offer. - @JsonKey(defaultValue: []) final List offerTags; /// The offer token required to pass in [BillingClient.launchBillingFlow] to /// purchase the subscription product with these [pricingPhases]. - @JsonKey(defaultValue: '') final String offerIdToken; /// The pricing phases for the subscription product. - @JsonKey(defaultValue: []) final List pricingPhases; + /// Represents additional details of an installment subscription plan. + final InstallmentPlanDetailsWrapper? installmentPlanDetails; + @override bool operator ==(Object other) { if (other.runtimeType != runtimeType) { @@ -70,7 +55,8 @@ class SubscriptionOfferDetailsWrapper { other.offerId == offerId && listEquals(other.offerTags, offerTags) && other.offerIdToken == offerIdToken && - listEquals(other.pricingPhases, pricingPhases); + listEquals(other.pricingPhases, pricingPhases) && + installmentPlanDetails == other.installmentPlanDetails; } @override @@ -81,13 +67,12 @@ class SubscriptionOfferDetailsWrapper { offerTags.hashCode, offerIdToken.hashCode, pricingPhases.hashCode, + installmentPlanDetails.hashCode, ); } } /// Represents a pricing phase, describing how a user pays at a point in time. -@JsonSerializable() -@RecurrenceModeConverter() @immutable class PricingPhaseWrapper { /// Creates a new [PricingPhaseWrapper] from the supplied info. @@ -100,37 +85,25 @@ class PricingPhaseWrapper { required this.recurrenceMode, }); - /// Factory for creating a [PricingPhaseWrapper] from a [Map] with the phase details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory PricingPhaseWrapper.fromJson(Map map) => - _$PricingPhaseWrapperFromJson(map); - /// Represents a pricing phase, describing how a user pays at a point in time. - @JsonKey(defaultValue: 0) final int billingCycleCount; /// Billing period for which the given price applies, specified in ISO 8601 /// format. - @JsonKey(defaultValue: '') final String billingPeriod; /// Returns formatted price for the payment cycle, including its currency /// sign. - @JsonKey(defaultValue: '') final String formattedPrice; /// Returns the price for the payment cycle in micro-units, where 1,000,000 /// micro-units equal one unit of the currency. - @JsonKey(defaultValue: 0) final int priceAmountMicros; /// Returns ISO 4217 currency code for price. - @JsonKey(defaultValue: '') final String priceCurrencyCode; /// Returns [RecurrenceMode] for the pricing phase. - @JsonKey(defaultValue: RecurrenceMode.nonRecurring) final RecurrenceMode recurrenceMode; @override @@ -158,3 +131,53 @@ class PricingPhaseWrapper { recurrenceMode, ); } + +/// Represents additional details of an installment subscription plan. +/// +/// This wraps [`com.android.billingclient.api.ProductDetails.InstallmentPlanDetails`](https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.InstallmentPlanDetails). +@immutable +class InstallmentPlanDetailsWrapper { + /// Creates a [InstallmentPlanDetailsWrapper]. + const InstallmentPlanDetailsWrapper({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + /// Committed payments count after a user signs up for this subscription plan. + /// + /// For example, for a monthly subscription plan with commitmentPaymentsCount + /// as 12, users will be charged monthly for 12 month after initial signup. + /// User cancellation won't take effect until all 12 committed payments are finished. + final int commitmentPaymentsCount; + + /// Subsequent committed payments count after this subscription plan renews. + /// + /// For example, for a monthly subscription plan with subsequentCommitmentPaymentsCount + /// as 12, when the subscription plan renews, users will be committed to another fresh + /// 12 monthly payments. + /// + /// Note: Returns 0 if the installment plan doesn't have any subsequent committment, + /// which means this subscription plan will fall back to a normal non-installment + /// monthly plan when the plan renews. + final int subsequentCommitmentPaymentsCount; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + + return other is InstallmentPlanDetailsWrapper && + other.commitmentPaymentsCount == commitmentPaymentsCount && + other.subsequentCommitmentPaymentsCount == + subsequentCommitmentPaymentsCount; + } + + @override + int get hashCode { + return Object.hash( + commitmentPaymentsCount.hashCode, + subsequentCommitmentPaymentsCount.hashCode, + ); + } +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart deleted file mode 100644 index 46973ed7d427..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/subscription_offer_details_wrapper.g.dart +++ /dev/null @@ -1,37 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'subscription_offer_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SubscriptionOfferDetailsWrapper _$SubscriptionOfferDetailsWrapperFromJson( - Map json) => - SubscriptionOfferDetailsWrapper( - basePlanId: json['basePlanId'] as String? ?? '', - offerId: json['offerId'] as String?, - offerTags: (json['offerTags'] as List?) - ?.map((e) => e as String) - .toList() ?? - [], - offerIdToken: json['offerIdToken'] as String? ?? '', - pricingPhases: (json['pricingPhases'] as List?) - ?.map((e) => PricingPhaseWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -PricingPhaseWrapper _$PricingPhaseWrapperFromJson(Map json) => - PricingPhaseWrapper( - billingCycleCount: (json['billingCycleCount'] as num?)?.toInt() ?? 0, - billingPeriod: json['billingPeriod'] as String? ?? '', - formattedPrice: json['formattedPrice'] as String? ?? '', - priceAmountMicros: (json['priceAmountMicros'] as num?)?.toInt() ?? 0, - priceCurrencyCode: json['priceCurrencyCode'] as String? ?? '', - recurrenceMode: json['recurrenceMode'] == null - ? RecurrenceMode.nonRecurring - : const RecurrenceModeConverter() - .fromJson((json['recurrenceMode'] as num?)?.toInt()), - ); diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart index 8b23058c195e..01654519f648 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.dart @@ -3,19 +3,12 @@ // found in the LICENSE file. import 'package:flutter/foundation.dart'; -import 'package:json_annotation/json_annotation.dart'; import '../../billing_client_wrappers.dart'; -// WARNING: Changes to `@JsonSerializable` classes need to be reflected in the -// below generated file. Run `flutter packages pub run build_runner watch` to -// rebuild and watch for further changes. -part 'user_choice_details_wrapper.g.dart'; - /// This wraps [`com.android.billingclient.api.UserChoiceDetails`](https://developer.android.com/reference/com/android/billingclient/api/UserChoiceDetails) // See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes // for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) @immutable class UserChoiceDetailsWrapper { /// Creates a purchase wrapper with the given purchase details. @@ -25,16 +18,6 @@ class UserChoiceDetailsWrapper { required this.products, }); - /// Factory for creating a [UserChoiceDetailsWrapper] from a [Map] with - /// the user choice details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsWrapper.fromJson(Map map) => - _$UserChoiceDetailsWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => _$UserChoiceDetailsWrapperToJson(this); - @override bool operator ==(Object other) { if (identical(other, this)) { @@ -58,17 +41,14 @@ class UserChoiceDetailsWrapper { /// Returns the external transaction Id of the originating subscription, if /// the purchase is a subscription upgrade/downgrade. - @JsonKey(defaultValue: '') final String originalExternalTransactionId; /// Returns a token that represents the user's prospective purchase via /// user choice alternative billing. - @JsonKey(defaultValue: '') final String externalTransactionToken; /// Returns a list of [UserChoiceDetailsProductWrapper] to be purchased in /// the user choice alternative billing flow. - @JsonKey(defaultValue: []) final List products; } @@ -78,8 +58,6 @@ class UserChoiceDetailsWrapper { // // See https://docs.flutter.dev/data-and-backend/serialization/json#generating-code-for-nested-classes // for explination for why this uses explicitToJson. -@JsonSerializable(createToJson: true, explicitToJson: true) -@ProductTypeConverter() @immutable class UserChoiceDetailsProductWrapper { /// Creates a [UserChoiceDetailsProductWrapper] with the given record details. @@ -89,22 +67,10 @@ class UserChoiceDetailsProductWrapper { required this.productType, }); - /// Factory for creating a [UserChoiceDetailsProductWrapper] from a [Map] with the record details. - @Deprecated('JSON serialization is not intended for public use, and will ' - 'be removed in a future version.') - factory UserChoiceDetailsProductWrapper.fromJson(Map map) => - _$UserChoiceDetailsProductWrapperFromJson(map); - - /// Creates a JSON representation of this product. - Map toJson() => - _$UserChoiceDetailsProductWrapperToJson(this); - /// Returns the id of the product being purchased. - @JsonKey(defaultValue: '') final String id; /// Returns the offer token that was passed in launchBillingFlow to purchase the product. - @JsonKey(defaultValue: '') final String offerToken; /// Returns the [ProductType] of the product being purchased. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart deleted file mode 100644 index 669d263588d6..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/billing_client_wrappers/user_choice_details_wrapper.g.dart +++ /dev/null @@ -1,45 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'user_choice_details_wrapper.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -UserChoiceDetailsWrapper _$UserChoiceDetailsWrapperFromJson(Map json) => - UserChoiceDetailsWrapper( - originalExternalTransactionId: - json['originalExternalTransactionId'] as String? ?? '', - externalTransactionToken: - json['externalTransactionToken'] as String? ?? '', - products: (json['products'] as List?) - ?.map((e) => UserChoiceDetailsProductWrapper.fromJson( - Map.from(e as Map))) - .toList() ?? - [], - ); - -Map _$UserChoiceDetailsWrapperToJson( - UserChoiceDetailsWrapper instance) => - { - 'originalExternalTransactionId': instance.originalExternalTransactionId, - 'externalTransactionToken': instance.externalTransactionToken, - 'products': instance.products.map((e) => e.toJson()).toList(), - }; - -UserChoiceDetailsProductWrapper _$UserChoiceDetailsProductWrapperFromJson( - Map json) => - UserChoiceDetailsProductWrapper( - id: json['id'] as String? ?? '', - offerToken: json['offerToken'] as String? ?? '', - productType: - const ProductTypeConverter().fromJson(json['productType'] as String?), - ); - -Map _$UserChoiceDetailsProductWrapperToJson( - UserChoiceDetailsProductWrapper instance) => - { - 'id': instance.id, - 'offerToken': instance.offerToken, - 'productType': const ProductTypeConverter().toJson(instance.productType), - }; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart index 333ccc83bc25..f39d58173674 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform.dart @@ -162,7 +162,6 @@ class InAppPurchaseAndroidPlatform extends InAppPurchasePlatform { oldProduct: changeSubscriptionParam?.oldPurchaseDetails.productID, purchaseToken: changeSubscriptionParam ?.oldPurchaseDetails.verificationData.serverVerificationData, - prorationMode: changeSubscriptionParam?.prorationMode, replacementMode: changeSubscriptionParam?.replacementMode), ); return billingResultWrapper.responseCode == BillingResponse.ok; diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart index aa2f473d2638..cc94285eb98f 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/in_app_purchase_android_platform_addition.dart @@ -31,32 +31,6 @@ class InAppPurchaseAndroidPlatformAddition late final Stream userChoiceDetailsStream = _userChoiceDetailsStreamController.stream; - /// Whether pending purchase is enabled. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. From now on - /// this is handled internally and the [enablePendingPurchase] property will - /// always return `true`. - /// - /// See also [enablePendingPurchases] for more on pending purchases. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - static bool get enablePendingPurchase => true; - - /// Enable the [InAppPurchaseConnection] to handle pending purchases. - /// - /// **Deprecation warning:** it is no longer required to call - /// [enablePendingPurchases] when initializing your application. - @Deprecated( - 'The requirement to call `enablePendingPurchases()` has become obsolete ' - "since Google Play no longer accepts app submissions that don't support " - 'pending purchases.') - static void enablePendingPurchases() { - // No-op, until it is time to completely remove this method from the API. - } - final BillingClientManager _billingClientManager; /// Mark that the user has consumed a product. diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart index 1e6cca2ccbd3..72e3ab5c16ca 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/messages.g.dart @@ -1,7 +1,7 @@ // 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. -// Autogenerated from Pigeon (v22.4.2), do not edit directly. +// Autogenerated from Pigeon (v22.6.1), do not edit directly. // See also: https://pub.dev/packages/pigeon // ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers @@ -29,6 +29,32 @@ List wrapResponse( return [error.code, error.message, error.details]; } +/// Pigeon version of Java BillingClient.BillingResponseCode. +enum PlatformBillingResponse { + serviceTimeout, + featureNotSupported, + serviceDisconnected, + ok, + userCanceled, + serviceUnavailable, + billingUnavailable, + itemUnavailable, + developerError, + error, + itemAlreadyOwned, + itemNotOwned, + networkError, +} + +enum PlatformReplacementMode { + unknownReplacementMode, + withTimeProration, + chargeProratedPrice, + withoutProration, + deferred, + chargeFullPrice, +} + /// Pigeon version of Java BillingClient.ProductType. enum PlatformProductType { inapp, @@ -49,6 +75,17 @@ enum PlatformBillingChoiceMode { userChoiceBilling, } +enum PlatformBillingClientFeature { + alternativeBillingOnly, + billingConfig, + externalOffer, + inAppMessaging, + priceChangeConfirmation, + productDetails, + subscriptions, + subscriptionsUpdate, +} + /// Pigeon version of Java Purchase.PurchaseState. enum PlatformPurchaseState { unspecified, @@ -124,7 +161,7 @@ class PlatformBillingResult { required this.debugMessage, }); - int responseCode; + PlatformBillingResponse responseCode; String debugMessage; @@ -138,7 +175,7 @@ class PlatformBillingResult { static PlatformBillingResult decode(Object result) { result as List; return PlatformBillingResult( - responseCode: result[0]! as int, + responseCode: result[0]! as PlatformBillingResponse, debugMessage: result[1]! as String, ); } @@ -321,7 +358,6 @@ class PlatformBillingConfigResponse { class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, - required this.prorationMode, required this.replacementMode, this.offerToken, this.accountId, @@ -332,9 +368,7 @@ class PlatformBillingFlowParams { String product; - int prorationMode; - - int replacementMode; + PlatformReplacementMode replacementMode; String? offerToken; @@ -349,7 +383,6 @@ class PlatformBillingFlowParams { Object encode() { return [ product, - prorationMode, replacementMode, offerToken, accountId, @@ -363,13 +396,12 @@ class PlatformBillingFlowParams { result as List; return PlatformBillingFlowParams( product: result[0]! as String, - prorationMode: result[1]! as int, - replacementMode: result[2]! as int, - offerToken: result[3] as String?, - accountId: result[4] as String?, - obfuscatedProfileId: result[5] as String?, - oldProduct: result[6] as String?, - purchaseToken: result[7] as String?, + replacementMode: result[1]! as PlatformReplacementMode, + offerToken: result[2] as String?, + accountId: result[3] as String?, + obfuscatedProfileId: result[4] as String?, + oldProduct: result[5] as String?, + purchaseToken: result[6] as String?, ); } } @@ -439,6 +471,7 @@ class PlatformPurchase { required this.quantity, required this.purchaseState, this.accountIdentifiers, + this.pendingPurchaseUpdate, }); String? orderId; @@ -467,6 +500,8 @@ class PlatformPurchase { PlatformAccountIdentifiers? accountIdentifiers; + PlatformPendingPurchaseUpdate? pendingPurchaseUpdate; + Object encode() { return [ orderId, @@ -482,6 +517,7 @@ class PlatformPurchase { quantity, purchaseState, accountIdentifiers, + pendingPurchaseUpdate, ]; } @@ -501,6 +537,36 @@ class PlatformPurchase { quantity: result[10]! as int, purchaseState: result[11]! as PlatformPurchaseState, accountIdentifiers: result[12] as PlatformAccountIdentifiers?, + pendingPurchaseUpdate: result[13] as PlatformPendingPurchaseUpdate?, + ); + } +} + +/// Pigeon version of Java Purchase. +/// +/// See also PendingPurchaseUpdateWrapper on the Dart side. +class PlatformPendingPurchaseUpdate { + PlatformPendingPurchaseUpdate({ + required this.products, + required this.purchaseToken, + }); + + List products; + + String purchaseToken; + + Object encode() { + return [ + products, + purchaseToken, + ]; + } + + static PlatformPendingPurchaseUpdate decode(Object result) { + result as List; + return PlatformPendingPurchaseUpdate( + products: (result[0] as List?)!.cast(), + purchaseToken: result[1]! as String, ); } } @@ -624,6 +690,7 @@ class PlatformSubscriptionOfferDetails { required this.offerToken, required this.offerTags, required this.pricingPhases, + this.installmentPlanDetails, }); String basePlanId; @@ -636,6 +703,8 @@ class PlatformSubscriptionOfferDetails { List pricingPhases; + PlatformInstallmentPlanDetails? installmentPlanDetails; + Object encode() { return [ basePlanId, @@ -643,6 +712,7 @@ class PlatformSubscriptionOfferDetails { offerToken, offerTags, pricingPhases, + installmentPlanDetails, ]; } @@ -655,6 +725,7 @@ class PlatformSubscriptionOfferDetails { offerTags: (result[3] as List?)!.cast(), pricingPhases: (result[4] as List?)!.cast(), + installmentPlanDetails: result[5] as PlatformInstallmentPlanDetails?, ); } } @@ -724,6 +795,56 @@ class PlatformUserChoiceProduct { } } +/// Pigeon version of ProductDetails.InstallmentPlanDetails. +/// https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() +class PlatformInstallmentPlanDetails { + PlatformInstallmentPlanDetails({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + int commitmentPaymentsCount; + + int subsequentCommitmentPaymentsCount; + + Object encode() { + return [ + commitmentPaymentsCount, + subsequentCommitmentPaymentsCount, + ]; + } + + static PlatformInstallmentPlanDetails decode(Object result) { + result as List; + return PlatformInstallmentPlanDetails( + commitmentPaymentsCount: result[0]! as int, + subsequentCommitmentPaymentsCount: result[1]! as int, + ); + } +} + +/// Pigeon version of Java PendingPurchasesParams. +class PlatformPendingPurchasesParams { + PlatformPendingPurchasesParams({ + required this.enablePrepaidPlans, + }); + + bool enablePrepaidPlans; + + Object encode() { + return [ + enablePrepaidPlans, + ]; + } + + static PlatformPendingPurchasesParams decode(Object result) { + result as List; + return PlatformPendingPurchasesParams( + enablePrepaidPlans: result[0]! as bool, + ); + } +} + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -731,69 +852,87 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); - } else if (value is PlatformProductType) { + } else if (value is PlatformBillingResponse) { buffer.putUint8(129); writeValue(buffer, value.index); - } else if (value is PlatformBillingChoiceMode) { + } else if (value is PlatformReplacementMode) { buffer.putUint8(130); writeValue(buffer, value.index); - } else if (value is PlatformPurchaseState) { + } else if (value is PlatformProductType) { buffer.putUint8(131); writeValue(buffer, value.index); - } else if (value is PlatformRecurrenceMode) { + } else if (value is PlatformBillingChoiceMode) { buffer.putUint8(132); writeValue(buffer, value.index); - } else if (value is PlatformQueryProduct) { + } else if (value is PlatformBillingClientFeature) { buffer.putUint8(133); + writeValue(buffer, value.index); + } else if (value is PlatformPurchaseState) { + buffer.putUint8(134); + writeValue(buffer, value.index); + } else if (value is PlatformRecurrenceMode) { + buffer.putUint8(135); + writeValue(buffer, value.index); + } else if (value is PlatformQueryProduct) { + buffer.putUint8(136); writeValue(buffer, value.encode()); } else if (value is PlatformAccountIdentifiers) { - buffer.putUint8(134); + buffer.putUint8(137); writeValue(buffer, value.encode()); } else if (value is PlatformBillingResult) { - buffer.putUint8(135); + buffer.putUint8(138); writeValue(buffer, value.encode()); } else if (value is PlatformOneTimePurchaseOfferDetails) { - buffer.putUint8(136); + buffer.putUint8(139); writeValue(buffer, value.encode()); } else if (value is PlatformProductDetails) { - buffer.putUint8(137); + buffer.putUint8(140); writeValue(buffer, value.encode()); } else if (value is PlatformProductDetailsResponse) { - buffer.putUint8(138); + buffer.putUint8(141); writeValue(buffer, value.encode()); } else if (value is PlatformAlternativeBillingOnlyReportingDetailsResponse) { - buffer.putUint8(139); + buffer.putUint8(142); writeValue(buffer, value.encode()); } else if (value is PlatformBillingConfigResponse) { - buffer.putUint8(140); + buffer.putUint8(143); writeValue(buffer, value.encode()); } else if (value is PlatformBillingFlowParams) { - buffer.putUint8(141); + buffer.putUint8(144); writeValue(buffer, value.encode()); } else if (value is PlatformPricingPhase) { - buffer.putUint8(142); + buffer.putUint8(145); writeValue(buffer, value.encode()); } else if (value is PlatformPurchase) { - buffer.putUint8(143); + buffer.putUint8(146); + writeValue(buffer, value.encode()); + } else if (value is PlatformPendingPurchaseUpdate) { + buffer.putUint8(147); writeValue(buffer, value.encode()); } else if (value is PlatformPurchaseHistoryRecord) { - buffer.putUint8(144); + buffer.putUint8(148); writeValue(buffer, value.encode()); } else if (value is PlatformPurchaseHistoryResponse) { - buffer.putUint8(145); + buffer.putUint8(149); writeValue(buffer, value.encode()); } else if (value is PlatformPurchasesResponse) { - buffer.putUint8(146); + buffer.putUint8(150); writeValue(buffer, value.encode()); } else if (value is PlatformSubscriptionOfferDetails) { - buffer.putUint8(147); + buffer.putUint8(151); writeValue(buffer, value.encode()); } else if (value is PlatformUserChoiceDetails) { - buffer.putUint8(148); + buffer.putUint8(152); writeValue(buffer, value.encode()); } else if (value is PlatformUserChoiceProduct) { - buffer.putUint8(149); + buffer.putUint8(153); + writeValue(buffer, value.encode()); + } else if (value is PlatformInstallmentPlanDetails) { + buffer.putUint8(154); + writeValue(buffer, value.encode()); + } else if (value is PlatformPendingPurchasesParams) { + buffer.putUint8(155); writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); @@ -805,51 +944,68 @@ class _PigeonCodec extends StandardMessageCodec { switch (type) { case 129: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformProductType.values[value]; + return value == null ? null : PlatformBillingResponse.values[value]; case 130: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformBillingChoiceMode.values[value]; + return value == null ? null : PlatformReplacementMode.values[value]; case 131: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformPurchaseState.values[value]; + return value == null ? null : PlatformProductType.values[value]; case 132: final int? value = readValue(buffer) as int?; - return value == null ? null : PlatformRecurrenceMode.values[value]; + return value == null ? null : PlatformBillingChoiceMode.values[value]; case 133: - return PlatformQueryProduct.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null + ? null + : PlatformBillingClientFeature.values[value]; case 134: - return PlatformAccountIdentifiers.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformPurchaseState.values[value]; case 135: - return PlatformBillingResult.decode(readValue(buffer)!); + final int? value = readValue(buffer) as int?; + return value == null ? null : PlatformRecurrenceMode.values[value]; case 136: - return PlatformOneTimePurchaseOfferDetails.decode(readValue(buffer)!); + return PlatformQueryProduct.decode(readValue(buffer)!); case 137: - return PlatformProductDetails.decode(readValue(buffer)!); + return PlatformAccountIdentifiers.decode(readValue(buffer)!); case 138: - return PlatformProductDetailsResponse.decode(readValue(buffer)!); + return PlatformBillingResult.decode(readValue(buffer)!); case 139: + return PlatformOneTimePurchaseOfferDetails.decode(readValue(buffer)!); + case 140: + return PlatformProductDetails.decode(readValue(buffer)!); + case 141: + return PlatformProductDetailsResponse.decode(readValue(buffer)!); + case 142: return PlatformAlternativeBillingOnlyReportingDetailsResponse.decode( readValue(buffer)!); - case 140: + case 143: return PlatformBillingConfigResponse.decode(readValue(buffer)!); - case 141: + case 144: return PlatformBillingFlowParams.decode(readValue(buffer)!); - case 142: + case 145: return PlatformPricingPhase.decode(readValue(buffer)!); - case 143: + case 146: return PlatformPurchase.decode(readValue(buffer)!); - case 144: + case 147: + return PlatformPendingPurchaseUpdate.decode(readValue(buffer)!); + case 148: return PlatformPurchaseHistoryRecord.decode(readValue(buffer)!); - case 145: + case 149: return PlatformPurchaseHistoryResponse.decode(readValue(buffer)!); - case 146: + case 150: return PlatformPurchasesResponse.decode(readValue(buffer)!); - case 147: + case 151: return PlatformSubscriptionOfferDetails.decode(readValue(buffer)!); - case 148: + case 152: return PlatformUserChoiceDetails.decode(readValue(buffer)!); - case 149: + case 153: return PlatformUserChoiceProduct.decode(readValue(buffer)!); + case 154: + return PlatformInstallmentPlanDetails.decode(readValue(buffer)!); + case 155: + return PlatformPendingPurchasesParams.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); } @@ -903,7 +1059,9 @@ class InAppPurchaseApi { /// Wraps BillingClient#startConnection(BillingClientStateListener). Future startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode) async { + int callbackHandle, + PlatformBillingChoiceMode billingMode, + PlatformPendingPurchasesParams pendingPurchasesParams) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.startConnection$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = @@ -912,8 +1070,9 @@ class InAppPurchaseApi { pigeonChannelCodec, binaryMessenger: pigeonVar_binaryMessenger, ); - final List? pigeonVar_replyList = await pigeonVar_channel - .send([callbackHandle, billingMode]) as List?; + final List? pigeonVar_replyList = await pigeonVar_channel.send( + [callbackHandle, billingMode, pendingPurchasesParams]) + as List?; if (pigeonVar_replyList == null) { throw _createConnectionError(pigeonVar_channelName); } else if (pigeonVar_replyList.length > 1) { @@ -1173,7 +1332,7 @@ class InAppPurchaseApi { } /// Wraps BillingClient#isFeatureSupported(String). - Future isFeatureSupported(String feature) async { + Future isFeatureSupported(PlatformBillingClientFeature feature) async { final String pigeonVar_channelName = 'dev.flutter.pigeon.in_app_purchase_android.InAppPurchaseApi.isFeatureSupported$pigeonVar_messageChannelSuffix'; final BasicMessageChannel pigeonVar_channel = diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart index bc93f614704f..78393f94c200 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/pigeon_converters.dart @@ -2,8 +2,11 @@ // 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/in_app_purchase_platform_interface.dart'; + import '../billing_client_wrappers.dart'; import 'billing_client_wrappers/billing_config_wrapper.dart'; +import 'billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'messages.g.dart'; /// Converts a [BillingChoiceMode] to the Pigeon equivalent. @@ -21,8 +24,7 @@ PlatformBillingChoiceMode platformBillingChoiceMode(BillingChoiceMode mode) { /// Creates a [BillingResultWrapper] from the Pigeon equivalent. BillingResultWrapper resultWrapperFromPlatform(PlatformBillingResult result) { return BillingResultWrapper( - responseCode: - const BillingResponseConverter().fromJson(result.responseCode), + responseCode: billingResponseFromPlatform(result.responseCode), debugMessage: result.debugMessage); } @@ -100,8 +102,7 @@ PurchasesResultWrapper purchasesResultWrapperFromPlatform( purchasesList: response.purchases.map(purchaseWrapperFromPlatform).toList(), responseCode: forceOkResponseCode ? BillingResponse.ok - : const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + : billingResponseFromPlatform(response.billingResult.responseCode), ); } @@ -111,8 +112,9 @@ AlternativeBillingOnlyReportingDetailsWrapper alternativeBillingOnlyReportingDetailsWrapperFromPlatform( PlatformAlternativeBillingOnlyReportingDetailsResponse response) { return AlternativeBillingOnlyReportingDetailsWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + responseCode: billingResponseFromPlatform( + response.billingResult.responseCode, + ), debugMessage: response.billingResult.debugMessage, externalTransactionToken: response.externalTransactionToken, ); @@ -122,8 +124,9 @@ AlternativeBillingOnlyReportingDetailsWrapper BillingConfigWrapper billingConfigWrapperFromPlatform( PlatformBillingConfigResponse response) { return BillingConfigWrapper( - responseCode: const BillingResponseConverter() - .fromJson(response.billingResult.responseCode), + responseCode: billingResponseFromPlatform( + response.billingResult.responseCode, + ), debugMessage: response.billingResult.debugMessage, countryCode: response.countryCode, ); @@ -182,9 +185,23 @@ PurchaseWrapper purchaseWrapperFromPlatform(PlatformPurchase purchase) { developerPayload: purchase.developerPayload, obfuscatedAccountId: purchase.accountIdentifiers?.obfuscatedAccountId, obfuscatedProfileId: purchase.accountIdentifiers?.obfuscatedProfileId, + pendingPurchaseUpdate: + pendingPurchaseUpdateFromPlatform(purchase.pendingPurchaseUpdate), ); } +/// Creates a [PendingPurchaseUpdateWrapper] from the Pigeon equivalent. +PendingPurchaseUpdateWrapper? pendingPurchaseUpdateFromPlatform( + PlatformPendingPurchaseUpdate? pendingPurchaseUpdate) { + if (pendingPurchaseUpdate == null) { + return null; + } + + return PendingPurchaseUpdateWrapper( + purchaseToken: pendingPurchaseUpdate.purchaseToken, + products: pendingPurchaseUpdate.products); +} + /// Creates a [PurchaseStateWrapper] from the Pigeon equivalent. PurchaseStateWrapper purchaseStateWrapperFromPlatform( PlatformPurchaseState state) { @@ -195,6 +212,15 @@ PurchaseStateWrapper purchaseStateWrapperFromPlatform( }; } +/// Converts [PurchaseStateWrapper] to [PurchaseStatus]. +PurchaseStatus purchaseStatusFromWrapper(PurchaseStateWrapper purchaseState) { + return switch (purchaseState) { + PurchaseStateWrapper.unspecified_state => PurchaseStatus.error, + PurchaseStateWrapper.purchased => PurchaseStatus.purchased, + PurchaseStateWrapper.pending => PurchaseStatus.pending, + }; +} + /// Creates a [RecurrenceMode] from the Pigeon equivalent. RecurrenceMode recurrenceModeFromPlatform(PlatformRecurrenceMode mode) { return switch (mode) { @@ -215,6 +241,8 @@ SubscriptionOfferDetailsWrapper subscriptionOfferDetailsWrapperFromPlatform( offerIdToken: offer.offerToken, pricingPhases: offer.pricingPhases.map(pricingPhaseWrapperFromPlatform).toList(), + installmentPlanDetails: + installmentPlanDetailsFromPlatform(offer.installmentPlanDetails), ); } @@ -238,3 +266,115 @@ UserChoiceDetailsProductWrapper userChoiceDetailsProductFromPlatform( productType: productTypeFromPlatform(product.type), ); } + +/// Creates a [InstallmentPlanDetailsWrapper] from the Pigeon equivalent. +InstallmentPlanDetailsWrapper? installmentPlanDetailsFromPlatform( + PlatformInstallmentPlanDetails? details) { + if (details == null) { + return null; + } + + return InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: details.commitmentPaymentsCount, + subsequentCommitmentPaymentsCount: + details.subsequentCommitmentPaymentsCount, + ); +} + +/// Converts a [PendingPurchasesParamsWrapper] to its Pigeon equivalent. +PlatformPendingPurchasesParams pendingPurchasesParamsFromWrapper( + PendingPurchasesParamsWrapper params) { + return PlatformPendingPurchasesParams( + enablePrepaidPlans: params.enablePrepaidPlans, + ); +} + +/// Converts [PlatformBillingResponse] to its public API enum equivalent. +BillingResponse billingResponseFromPlatform( + PlatformBillingResponse responseCode) { + return switch (responseCode) { + PlatformBillingResponse.serviceTimeout => BillingResponse.serviceTimeout, + PlatformBillingResponse.featureNotSupported => + BillingResponse.featureNotSupported, + PlatformBillingResponse.serviceDisconnected => + BillingResponse.serviceDisconnected, + PlatformBillingResponse.ok => BillingResponse.ok, + PlatformBillingResponse.userCanceled => BillingResponse.userCanceled, + PlatformBillingResponse.serviceUnavailable => + BillingResponse.serviceUnavailable, + PlatformBillingResponse.billingUnavailable => + BillingResponse.billingUnavailable, + PlatformBillingResponse.itemUnavailable => BillingResponse.itemUnavailable, + PlatformBillingResponse.developerError => BillingResponse.developerError, + PlatformBillingResponse.error => BillingResponse.error, + PlatformBillingResponse.itemAlreadyOwned => + BillingResponse.itemAlreadyOwned, + PlatformBillingResponse.itemNotOwned => BillingResponse.itemNotOwned, + PlatformBillingResponse.networkError => BillingResponse.networkError, + }; +} + +/// Converts a [BillingResponse] to its Pigeon equivalent. +PlatformBillingResponse billingResponseFromWrapper( + BillingResponse responseCode) { + return switch (responseCode) { + BillingResponse.serviceTimeout => PlatformBillingResponse.serviceTimeout, + BillingResponse.featureNotSupported => + PlatformBillingResponse.featureNotSupported, + BillingResponse.serviceDisconnected => + PlatformBillingResponse.serviceDisconnected, + BillingResponse.ok => PlatformBillingResponse.ok, + BillingResponse.userCanceled => PlatformBillingResponse.userCanceled, + BillingResponse.serviceUnavailable => + PlatformBillingResponse.serviceUnavailable, + BillingResponse.billingUnavailable => + PlatformBillingResponse.billingUnavailable, + BillingResponse.itemUnavailable => PlatformBillingResponse.itemUnavailable, + BillingResponse.developerError => PlatformBillingResponse.developerError, + BillingResponse.error => PlatformBillingResponse.error, + BillingResponse.itemAlreadyOwned => + PlatformBillingResponse.itemAlreadyOwned, + BillingResponse.itemNotOwned => PlatformBillingResponse.itemNotOwned, + BillingResponse.networkError => PlatformBillingResponse.networkError, + }; +} + +/// Converts [ReplacementMode] enum to its Pigeon equivalent. +PlatformReplacementMode replacementModeFromWrapper( + ReplacementMode replacementMode) { + return switch (replacementMode) { + ReplacementMode.unknownReplacementMode => + PlatformReplacementMode.unknownReplacementMode, + ReplacementMode.withTimeProration => + PlatformReplacementMode.withTimeProration, + ReplacementMode.chargeProratedPrice => + PlatformReplacementMode.chargeProratedPrice, + ReplacementMode.withoutProration => + PlatformReplacementMode.withoutProration, + ReplacementMode.deferred => PlatformReplacementMode.deferred, + ReplacementMode.chargeFullPrice => PlatformReplacementMode.chargeFullPrice, + }; +} + +/// Converts [BillingClientFeature] enum to its Pigeon equivalent. +PlatformBillingClientFeature billingClientFeatureFromWrapper( + BillingClientFeature feature) { + return switch (feature) { + BillingClientFeature.alternativeBillingOnly => + PlatformBillingClientFeature.alternativeBillingOnly, + BillingClientFeature.priceChangeConfirmation => + PlatformBillingClientFeature.priceChangeConfirmation, + BillingClientFeature.productDetails => + PlatformBillingClientFeature.productDetails, + BillingClientFeature.subscriptions => + PlatformBillingClientFeature.subscriptions, + BillingClientFeature.subscriptionsUpdate => + PlatformBillingClientFeature.subscriptionsUpdate, + BillingClientFeature.billingConfig => + PlatformBillingClientFeature.billingConfig, + BillingClientFeature.externalOffer => + PlatformBillingClientFeature.externalOffer, + BillingClientFeature.inAppMessaging => + PlatformBillingClientFeature.inAppMessaging, + }; +} diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart index f76fc3e9005b..25907402469b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/change_subscription_param.dart @@ -10,7 +10,6 @@ class ChangeSubscriptionParam { /// Creates a new change subscription param object with given data ChangeSubscriptionParam({ required this.oldPurchaseDetails, - @Deprecated('Use replacementMode instead') this.prorationMode, this.replacementMode, }); @@ -18,13 +17,6 @@ class ChangeSubscriptionParam { /// upgrade/downgrade from. final GooglePlayPurchaseDetails oldPurchaseDetails; - /// The proration mode. - /// - /// This is an optional parameter that indicates how to handle the existing - /// subscription when the new subscription comes into effect. - @Deprecated('Use replacementMode instead') - final ProrationMode? prorationMode; - /// The replacement mode. /// /// This is an optional parameter that indicates how to handle the existing diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart index f2596d61326d..1dbe4305fa52 100644 --- a/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart +++ b/packages/in_app_purchase/in_app_purchase_android/lib/src/types/google_play_purchase_details.dart @@ -6,6 +6,7 @@ import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_inte import '../../billing_client_wrappers.dart'; import '../in_app_purchase_android_platform.dart'; +import '../pigeon_converters.dart'; /// The class represents the information of a purchase made using Google Play. class GooglePlayPurchaseDetails extends PurchaseDetails { @@ -38,8 +39,7 @@ class GooglePlayPurchaseDetails extends PurchaseDetails { source: kIAPSource), transactionDate: purchase.purchaseTime.toString(), billingClientPurchase: purchase, - status: const PurchaseStateConverter() - .toPurchaseStatus(purchase.purchaseState), + status: purchaseStatusFromWrapper(purchase.purchaseState), ); if (purchaseDetails.status == PurchaseStatus.error) { diff --git a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart index 3c15cc4750a9..1e15f2ad8e30 100644 --- a/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart +++ b/packages/in_app_purchase/in_app_purchase_android/pigeons/messages.dart @@ -35,10 +35,27 @@ class PlatformAccountIdentifiers { class PlatformBillingResult { PlatformBillingResult( {required this.responseCode, required this.debugMessage}); - final int responseCode; + final PlatformBillingResponse responseCode; final String debugMessage; } +/// Pigeon version of Java BillingClient.BillingResponseCode. +enum PlatformBillingResponse { + serviceTimeout, + featureNotSupported, + serviceDisconnected, + ok, + userCanceled, + serviceUnavailable, + billingUnavailable, + itemUnavailable, + developerError, + error, + itemAlreadyOwned, + itemNotOwned, + networkError, +} + /// Pigeon version of Java ProductDetails.OneTimePurchaseOfferDetails. class PlatformOneTimePurchaseOfferDetails { PlatformOneTimePurchaseOfferDetails({ @@ -110,7 +127,6 @@ class PlatformBillingConfigResponse { class PlatformBillingFlowParams { PlatformBillingFlowParams({ required this.product, - required this.prorationMode, required this.replacementMode, required this.offerToken, required this.accountId, @@ -120,11 +136,7 @@ class PlatformBillingFlowParams { }); final String product; - // Ideally this would be replaced with an enum on the dart side that maps - // to constants on the Java side, but it's deprecated anyway so that will be - // resolved during the update to the new API. - final int prorationMode; - final int replacementMode; + final PlatformReplacementMode replacementMode; final String? offerToken; final String? accountId; final String? obfuscatedProfileId; @@ -132,6 +144,15 @@ class PlatformBillingFlowParams { final String? purchaseToken; } +enum PlatformReplacementMode { + unknownReplacementMode, + withTimeProration, + chargeProratedPrice, + withoutProration, + deferred, + chargeFullPrice, +} + /// Pigeon version of Java ProductDetails.PricingPhase. class PlatformPricingPhase { PlatformPricingPhase({ @@ -169,6 +190,7 @@ class PlatformPurchase { required this.quantity, required this.purchaseState, required this.accountIdentifiers, + required this.pendingPurchaseUpdate, }); final String? orderId; @@ -184,6 +206,20 @@ class PlatformPurchase { final int quantity; final PlatformPurchaseState purchaseState; final PlatformAccountIdentifiers? accountIdentifiers; + final PlatformPendingPurchaseUpdate? pendingPurchaseUpdate; +} + +/// Pigeon version of Java Purchase. +/// +/// See also PendingPurchaseUpdateWrapper on the Dart side. +class PlatformPendingPurchaseUpdate { + PlatformPendingPurchaseUpdate({ + required this.products, + required this.purchaseToken, + }); + + final List products; + final String purchaseToken; } /// Pigeon version of PurchaseHistoryRecord. @@ -241,6 +277,7 @@ class PlatformSubscriptionOfferDetails { required this.offerToken, required this.offerTags, required this.pricingPhases, + required this.installmentPlanDetails, }); final String basePlanId; @@ -252,6 +289,7 @@ class PlatformSubscriptionOfferDetails { // internal API, we can always add that indirection later if we need it, // so for now this bypasses that unnecessary wrapper. final List pricingPhases; + final PlatformInstallmentPlanDetails? installmentPlanDetails; } /// Pigeon version of UserChoiceDetailsWrapper and Java UserChoiceDetails. @@ -280,6 +318,27 @@ class PlatformUserChoiceProduct { final PlatformProductType type; } +/// Pigeon version of ProductDetails.InstallmentPlanDetails. +/// https://developer.android.com/reference/com/android/billingclient/api/PendingPurchasesParams.Builder#enableOneTimeProducts() +class PlatformInstallmentPlanDetails { + PlatformInstallmentPlanDetails({ + required this.commitmentPaymentsCount, + required this.subsequentCommitmentPaymentsCount, + }); + + final int commitmentPaymentsCount; + final int subsequentCommitmentPaymentsCount; +} + +/// Pigeon version of Java PendingPurchasesParams. +class PlatformPendingPurchasesParams { + PlatformPendingPurchasesParams({ + required this.enablePrepaidPlans, + }); + + final bool enablePrepaidPlans; +} + /// Pigeon version of Java BillingClient.ProductType. enum PlatformProductType { inapp, @@ -300,6 +359,18 @@ enum PlatformBillingChoiceMode { userChoiceBilling, } +/// Pigeon version of Java BillingClient.FeatureType. +enum PlatformBillingClientFeature { + alternativeBillingOnly, + billingConfig, + externalOffer, + inAppMessaging, + priceChangeConfirmation, + productDetails, + subscriptions, + subscriptionsUpdate, +} + /// Pigeon version of Java Purchase.PurchaseState. enum PlatformPurchaseState { unspecified, @@ -322,7 +393,9 @@ abstract class InAppPurchaseApi { /// Wraps BillingClient#startConnection(BillingClientStateListener). @async PlatformBillingResult startConnection( - int callbackHandle, PlatformBillingChoiceMode billingMode); + int callbackHandle, + PlatformBillingChoiceMode billingMode, + PlatformPendingPurchasesParams pendingPurchasesParams); /// Wraps BillingClient#endConnection(BillingClientStateListener). void endConnection(); @@ -358,10 +431,7 @@ abstract class InAppPurchaseApi { List products); /// Wraps BillingClient#isFeatureSupported(String). - // TODO(stuartmorgan): Consider making this take a enum, and converting the - // enum value to string constants on the native side, so that magic strings - // from the Play Billing API aren't duplicated in Dart code. - bool isFeatureSupported(String feature); + bool isFeatureSupported(PlatformBillingClientFeature feature); /// Wraps BillingClient#isAlternativeBillingOnlyAvailableAsync(). @async 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 0932606e7ae8..bef60d7c6fdc 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.6+13 +version: 0.4.0 environment: sdk: ^3.5.0 @@ -21,13 +21,11 @@ dependencies: flutter: sdk: flutter in_app_purchase_platform_interface: ^1.4.0 - json_annotation: ^4.8.0 dev_dependencies: build_runner: ^2.0.0 flutter_test: sdk: flutter - json_serializable: ^6.3.1 mockito: ^5.4.4 pigeon: ^22.4.2 test: ^1.16.0 diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart index 1c72d02f99e5..59eb1d01f5c6 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_manager_test.dart @@ -7,6 +7,7 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; +import 'package:in_app_purchase_android/src/billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'package:in_app_purchase_android/src/messages.g.dart'; import 'package:mockito/mockito.dart'; @@ -21,8 +22,9 @@ void main() { setUp(() { WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); manager = BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, UserSelectedAlternativeBillingListener? @@ -32,14 +34,15 @@ void main() { group('BillingClientWrapper', () { test('connects on initialization', () { - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); }); test('waits for connection before executing the operations', () async { final Completer connectedCompleter = Completer(); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async { connectedCompleter.complete(); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); + return PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: ''); }); final Completer calledCompleter1 = Completer(); @@ -64,7 +67,7 @@ void main() { await manager.runWithClientNonRetryable((_) async {}); manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); }); test('re-connects when host calls reconnectWithBillingChoiceMode', @@ -83,11 +86,37 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.captured.single, PlatformBillingChoiceMode.alternativeBillingOnly); }); + test('re-connects when host calls reconnectWithPendingPurchasesParams', + () async { + // Ensures all asynchronous connected code finishes. + await manager.runWithClientNonRetryable((_) async {}); + + await manager.reconnectWithPendingPurchasesParams( + const PendingPurchasesParamsWrapper(enablePrepaidPlans: true)); + // Verify that connection was ended. + verify(mockApi.endConnection()).called(1); + + clearInteractions(mockApi); + + /// Fake the disconnect that we would expect from a endConnectionCall. + manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); + // Verify that after connection ended reconnect was called. + final VerificationResult result = + verify(mockApi.startConnection(any, any, captureAny)); + expect( + result.captured.single, + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + true)); + }); + test( 're-connects when operation returns BillingResponse.serviceDisconnected', () async { @@ -104,7 +133,7 @@ void main() { ); }, ); - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); expect(timesCalled, equals(2)); expect(result.responseCode, equals(BillingResponse.ok)); }, @@ -113,7 +142,7 @@ void main() { test('does not re-connect when disposed', () { clearInteractions(mockApi); manager.dispose(); - verifyNever(mockApi.startConnection(any, any)); + verifyNever(mockApi.startConnection(any, any, any)); verify(mockApi.endConnection()).called(1); }); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart index 0d94ddc7bf9e..2b4989a43e97 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.dart @@ -7,7 +7,9 @@ import 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:in_app_purchase_android/billing_client_wrappers.dart'; import 'package:in_app_purchase_android/src/billing_client_wrappers/billing_config_wrapper.dart'; +import 'package:in_app_purchase_android/src/billing_client_wrappers/pending_purchases_params_wrapper.dart'; import 'package:in_app_purchase_android/src/messages.g.dart'; +import 'package:in_app_purchase_android/src/pigeon_converters.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @@ -39,8 +41,9 @@ void main() { setUp(() { mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); billingClient = BillingClient( (PurchasesResultWrapper _) {}, (UserChoiceDetailsWrapper _) {}, api: mockApi); @@ -58,32 +61,13 @@ void main() { }); }); - // Make sure that the enum values are supported and that the converter call - // does not fail - test('response states', () async { - const BillingResponseConverter converter = BillingResponseConverter(); - converter.fromJson(-3); - converter.fromJson(-2); - converter.fromJson(-1); - converter.fromJson(0); - converter.fromJson(1); - converter.fromJson(2); - converter.fromJson(3); - converter.fromJson(4); - converter.fromJson(5); - converter.fromJson(6); - converter.fromJson(7); - converter.fromJson(8); - converter.fromJson(12); - }); - group('startConnection', () { test('returns BillingResultWrapper', () async { const String debugMessage = 'dummy message'; const BillingResponse responseCode = BillingResponse.developerError; - when(mockApi.startConnection(any, any)).thenAnswer( + when(mockApi.startConnection(any, any, any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.developerError, debugMessage: debugMessage, ), ); @@ -100,9 +84,16 @@ void main() { await billingClient.startConnection(onBillingServiceDisconnected: () {}); final VerificationResult result = - verify(mockApi.startConnection(captureAny, captureAny)); + verify(mockApi.startConnection(captureAny, captureAny, captureAny)); expect(result.captured[0], 0); expect(result.captured[1], PlatformBillingChoiceMode.playBillingOnly); + expect( + result.captured[2], + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + false)); }); test('passes billingChoiceMode alternativeBillingOnly when set', () async { @@ -110,7 +101,8 @@ void main() { onBillingServiceDisconnected: () {}, billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, + expect( + verify(mockApi.startConnection(any, captureAny, any)).captured.first, PlatformBillingChoiceMode.alternativeBillingOnly); }); @@ -125,7 +117,8 @@ void main() { onBillingServiceDisconnected: () {}, billingChoiceMode: BillingChoiceMode.alternativeBillingOnly); - expect(verify(mockApi.startConnection(any, captureAny)).captured.first, + expect( + verify(mockApi.startConnection(any, captureAny, any)).captured.first, PlatformBillingChoiceMode.alternativeBillingOnly); const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( @@ -147,43 +140,20 @@ void main() { expect(await completer.future, expected); }); - test('UserChoiceDetailsWrapper searilization check', () async { - // Test ensures that changes to UserChoiceDetailsWrapper#toJson are - // compatible with code in Translator.java. - const String transactionIdKey = 'originalExternalTransactionId'; - const String transactionTokenKey = 'externalTransactionToken'; - const String productsKey = 'products'; - const String productIdKey = 'id'; - const String productOfferTokenKey = 'offerToken'; - const String productTypeKey = 'productType'; - - const UserChoiceDetailsProductWrapper expectedProduct1 = - UserChoiceDetailsProductWrapper( - id: 'id1', - offerToken: 'offerToken1', - productType: ProductType.inapp); - const UserChoiceDetailsProductWrapper expectedProduct2 = - UserChoiceDetailsProductWrapper( - id: 'id2', - offerToken: 'offerToken2', - productType: ProductType.inapp); - const UserChoiceDetailsWrapper expected = UserChoiceDetailsWrapper( - originalExternalTransactionId: 'TransactionId', - externalTransactionToken: 'TransactionToken', - products: [ - expectedProduct1, - expectedProduct2, - ], - ); - final Map detailsJson = expected.toJson(); - expect(detailsJson.keys, contains(transactionIdKey)); - expect(detailsJson.keys, contains(transactionTokenKey)); - expect(detailsJson.keys, contains(productsKey)); - - final Map productJson = expectedProduct1.toJson(); - expect(productJson, contains(productIdKey)); - expect(productJson, contains(productOfferTokenKey)); - expect(productJson, contains(productTypeKey)); + test('passes pendingPurchasesParams when set', () async { + await billingClient.startConnection( + onBillingServiceDisconnected: () {}, + billingChoiceMode: BillingChoiceMode.alternativeBillingOnly, + pendingPurchasesParams: + const PendingPurchasesParamsWrapper(enablePrepaidPlans: true)); + + expect( + verify(mockApi.startConnection(any, any, captureAny)).captured.first, + isA().having( + (PlatformPendingPurchasesParams params) => + params.enablePrepaidPlans, + 'enablePrepaidPlans', + true)); }); }); @@ -200,8 +170,7 @@ void main() { when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.developerError, debugMessage: debugMessage), productDetails: [], )); @@ -224,8 +193,7 @@ void main() { when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), + responseCode: PlatformBillingResponse.ok, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) @@ -343,8 +311,8 @@ void main() { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; - const ProrationMode prorationMode = - ProrationMode.immediateAndChargeProratedPrice; + const ReplacementMode replacementMode = + ReplacementMode.chargeProratedPrice; expect( await billingClient.launchBillingFlow( @@ -352,7 +320,7 @@ void main() { accountId: accountId, obfuscatedProfileId: profileId, oldProduct: dummyOldPurchase.products.first, - prorationMode: prorationMode, + replacementMode: replacementMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final VerificationResult result = @@ -364,8 +332,10 @@ void main() { expect(params.oldProduct, equals(dummyOldPurchase.products.first)); expect(params.obfuscatedProfileId, equals(profileId)); expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, - const ProrationModeConverter().toJson(prorationMode)); + expect( + params.replacementMode, + replacementModeFromWrapper(replacementMode), + ); }); test( @@ -380,8 +350,7 @@ void main() { const ProductDetailsWrapper productDetails = dummyOneTimeProductDetails; const String accountId = 'hashedAccountId'; const String profileId = 'hashedProfileId'; - const ProrationMode prorationMode = - ProrationMode.immediateAndChargeFullPrice; + const ReplacementMode replacementMode = ReplacementMode.chargeFullPrice; expect( await billingClient.launchBillingFlow( @@ -389,7 +358,7 @@ void main() { accountId: accountId, obfuscatedProfileId: profileId, oldProduct: dummyOldPurchase.products.first, - prorationMode: prorationMode, + replacementMode: replacementMode, purchaseToken: dummyOldPurchase.purchaseToken), equals(expectedBillingResult)); final VerificationResult result = @@ -401,8 +370,10 @@ void main() { expect(params.oldProduct, equals(dummyOldPurchase.products.first)); expect(params.obfuscatedProfileId, equals(profileId)); expect(params.purchaseToken, equals(dummyOldPurchase.purchaseToken)); - expect(params.prorationMode, - const ProrationModeConverter().toJson(prorationMode)); + expect( + params.replacementMode, + replacementModeFromWrapper(replacementMode), + ); }); test('handles null accountId', () async { @@ -439,8 +410,7 @@ void main() { when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.ok, debugMessage: debugMessage), purchases: expectedList .map((PurchaseWrapper purchase) => @@ -464,8 +434,7 @@ void main() { when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.userCanceled, debugMessage: debugMessage), purchases: [], )); @@ -482,32 +451,6 @@ void main() { }); group('queryPurchaseHistory', () { - test('serializes and deserializes data', () async { - const BillingResponse expectedCode = BillingResponse.ok; - final List expectedList = - [ - dummyPurchaseHistoryRecord, - ]; - const String debugMessage = 'dummy message'; - const BillingResultWrapper expectedBillingResult = BillingResultWrapper( - responseCode: expectedCode, debugMessage: debugMessage); - when(mockApi.queryPurchaseHistoryAsync(any)) - .thenAnswer((_) async => PlatformPurchaseHistoryResponse( - billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), - debugMessage: debugMessage), - purchases: expectedList - .map(platformPurchaseHistoryRecordFromWrapper) - .toList(), - )); - - final PurchasesHistoryResult response = - await billingClient.queryPurchaseHistory(ProductType.inapp); - expect(response.billingResult, equals(expectedBillingResult)); - expect(response.purchaseHistoryRecordList, equals(expectedList)); - }); - test('handles empty purchases', () async { const BillingResponse expectedCode = BillingResponse.userCanceled; const String debugMessage = 'dummy message'; @@ -516,8 +459,7 @@ void main() { when(mockApi.queryPurchaseHistoryAsync(any)) .thenAnswer((_) async => PlatformPurchaseHistoryResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(expectedCode), + responseCode: PlatformBillingResponse.userCanceled, debugMessage: debugMessage), purchases: [], )); @@ -566,7 +508,8 @@ void main() { group('isFeatureSupported', () { test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => false); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); @@ -574,7 +517,8 @@ void main() { }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => true); final bool isSupported = await billingClient .isFeatureSupported(BillingClientFeature.subscriptions); @@ -603,7 +547,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'message'); when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await billingClient.isAlternativeBillingOnlyAvailable(); expect(result, expected); @@ -633,7 +578,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'message'); when(mockApi.showAlternativeBillingOnlyInformationDialog()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await billingClient.showAlternativeBillingOnlyInformationDialog(); expect(result, expected); @@ -645,8 +591,7 @@ PlatformBillingConfigResponse platformBillingConfigFromWrapper( BillingConfigWrapper original) { return PlatformBillingConfigResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), + responseCode: billingResponseFromWrapper(original.responseCode), debugMessage: original.debugMessage!, ), countryCode: original.countryCode); @@ -657,24 +602,8 @@ PlatformAlternativeBillingOnlyReportingDetailsResponse AlternativeBillingOnlyReportingDetailsWrapper original) { return PlatformAlternativeBillingOnlyReportingDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(original.responseCode), + responseCode: billingResponseFromWrapper(original.responseCode), debugMessage: original.debugMessage!, ), externalTransactionToken: original.externalTransactionToken); } - -PlatformPurchaseHistoryRecord platformPurchaseHistoryRecordFromWrapper( - PurchaseHistoryRecordWrapper wrapper) { - return PlatformPurchaseHistoryRecord( - // For some reason quantity is not currently exposed in - // PurchaseHistoryRecordWrapper. - quantity: 99, - purchaseTime: wrapper.purchaseTime, - originalJson: wrapper.originalJson, - purchaseToken: wrapper.purchaseToken, - signature: wrapper.signature, - products: wrapper.products, - developerPayload: wrapper.developerPayload, - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart index 5a93a96f9754..a5086ce40a60 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/billing_client_wrapper_test.mocks.dart @@ -120,6 +120,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { _i4.Future<_i2.PlatformBillingResult> startConnection( int? callbackHandle, _i2.PlatformBillingChoiceMode? billingMode, + _i2.PlatformPendingPurchasesParams? pendingPurchasesParams, ) => (super.noSuchMethod( Invocation.method( @@ -127,6 +128,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), returnValue: _i4.Future<_i2.PlatformBillingResult>.value( @@ -137,6 +139,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), )), @@ -148,6 +151,7 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { [ callbackHandle, billingMode, + pendingPurchasesParams, ], ), )), @@ -348,7 +352,9 @@ class MockInAppPurchaseApi extends _i1.Mock implements _i2.InAppPurchaseApi { ) as _i4.Future<_i2.PlatformProductDetailsResponse>); @override - _i4.Future isFeatureSupported(String? feature) => (super.noSuchMethod( + _i4.Future isFeatureSupported( + _i2.PlatformBillingClientFeature? feature) => + (super.noSuchMethod( Invocation.method( #isFeatureSupported, [feature], diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart index 3bd6a497490f..877a0d1259e9 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_details_wrapper_test.dart @@ -19,89 +19,14 @@ const ProductDetailsWrapper dummyOneTimeProductDetails = ProductDetailsWrapper( ), ); -const ProductDetailsWrapper dummySubscriptionProductDetails = - ProductDetailsWrapper( - description: 'description', - name: 'name', - productId: 'productId', - productType: ProductType.subs, - title: 'title', - subscriptionOfferDetails: [ - SubscriptionOfferDetailsWrapper( - basePlanId: 'basePlanId', - offerTags: ['offerTags'], - offerId: 'offerId', - offerIdToken: 'offerToken', - pricingPhases: [ - PricingPhaseWrapper( - billingCycleCount: 4, - billingPeriod: 'billingPeriod', - formattedPrice: r'$100', - priceAmountMicros: 100000000, - priceCurrencyCode: 'USD', - recurrenceMode: RecurrenceMode.finiteRecurring, - ), - ], - ), - ], -); - void main() { - group('ProductDetailsWrapper', () { - test('converts one-time purchase from map', () { - const ProductDetailsWrapper expected = dummyOneTimeProductDetails; - final ProductDetailsWrapper parsed = - ProductDetailsWrapper.fromJson(buildProductMap(expected)); - - expect(parsed, equals(expected)); - }); - - test('converts subscription from map', () { - const ProductDetailsWrapper expected = dummySubscriptionProductDetails; - final ProductDetailsWrapper parsed = - ProductDetailsWrapper.fromJson(buildProductMap(expected)); - - expect(parsed, equals(expected)); - }); - }); - group('ProductDetailsResponseWrapper', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - const String debugMessage = 'dummy message'; - final List productsDetails = - [ - dummyOneTimeProductDetails, - dummyOneTimeProductDetails, - ]; - const BillingResultWrapper result = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final ProductDetailsResponseWrapper expected = - ProductDetailsResponseWrapper( - billingResult: result, productDetailsList: productsDetails); - - final ProductDetailsResponseWrapper parsed = - ProductDetailsResponseWrapper.fromJson({ - 'billingResult': { - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'debugMessage': debugMessage, - }, - 'productDetailsList': >[ - buildProductMap(dummyOneTimeProductDetails), - buildProductMap(dummyOneTimeProductDetails), - ], - }); - - expect(parsed.billingResult, equals(expected.billingResult)); - expect( - parsed.productDetailsList, containsAll(expected.productDetailsList)); - }); - test('toProductDetails() should return correct Product object', () { - final ProductDetailsWrapper wrapper = ProductDetailsWrapper.fromJson( - buildProductMap(dummyOneTimeProductDetails)); + const ProductDetailsWrapper wrapper = dummyOneTimeProductDetails; final GooglePlayProductDetails product = - GooglePlayProductDetails.fromProductDetails(wrapper).first; + GooglePlayProductDetails.fromProductDetails( + dummyOneTimeProductDetails) + .first; expect(product.title, wrapper.title); expect(product.description, wrapper.description); expect(product.id, wrapper.productId); @@ -109,60 +34,9 @@ void main() { product.price, wrapper.oneTimePurchaseOfferDetails?.formattedPrice); expect(product.productDetails, wrapper); }); - - test('handles empty list of productDetails', () { - const BillingResponse responseCode = BillingResponse.error; - const String debugMessage = 'dummy message'; - final List productsDetails = - []; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final ProductDetailsResponseWrapper expected = - ProductDetailsResponseWrapper( - billingResult: billingResult, - productDetailsList: productsDetails); - - final ProductDetailsResponseWrapper parsed = - ProductDetailsResponseWrapper.fromJson({ - 'billingResult': { - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'debugMessage': debugMessage, - }, - 'productDetailsList': const >[] - }); - - expect(parsed.billingResult, equals(expected.billingResult)); - expect( - parsed.productDetailsList, containsAll(expected.productDetailsList)); - }); - - test('fromJson creates an object with default values', () { - final ProductDetailsResponseWrapper productDetails = - ProductDetailsResponseWrapper.fromJson(const {}); - expect( - productDetails.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(productDetails.productDetailsList, isEmpty); - }); }); group('BillingResultWrapper', () { - test('fromJson on empty map creates an object with default values', () { - final BillingResultWrapper billingResult = - BillingResultWrapper.fromJson(const {}); - expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); - expect(billingResult.responseCode, BillingResponse.error); - }); - - test('fromJson on null creates an object with default values', () { - final BillingResultWrapper billingResult = - BillingResultWrapper.fromJson(null); - expect(billingResult.debugMessage, kInvalidBillingResultErrorMessage); - expect(billingResult.responseCode, BillingResponse.error); - }); - test('operator == of ProductDetailsWrapper works fine', () { const ProductDetailsWrapper firstProductDetailsInstance = ProductDetailsWrapper( @@ -191,6 +65,10 @@ void main() { recurrenceMode: RecurrenceMode.finiteRecurring, ), ], + installmentPlanDetails: InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: 1, + subsequentCommitmentPaymentsCount: 2, + ), ), ], ); @@ -221,6 +99,10 @@ void main() { recurrenceMode: RecurrenceMode.finiteRecurring, ), ], + installmentPlanDetails: InstallmentPlanDetailsWrapper( + commitmentPaymentsCount: 1, + subsequentCommitmentPaymentsCount: 2, + ), ), ], ); @@ -243,73 +125,3 @@ void main() { }); }); } - -Map buildProductMap(ProductDetailsWrapper original) { - final Map map = { - 'title': original.title, - 'description': original.description, - 'productId': original.productId, - 'productType': const ProductTypeConverter().toJson(original.productType), - 'name': original.name, - }; - - if (original.oneTimePurchaseOfferDetails != null) { - map.putIfAbsent('oneTimePurchaseOfferDetails', - () => buildOneTimePurchaseMap(original.oneTimePurchaseOfferDetails!)); - } - - if (original.subscriptionOfferDetails != null) { - map.putIfAbsent('subscriptionOfferDetails', - () => buildSubscriptionMapList(original.subscriptionOfferDetails!)); - } - - return map; -} - -Map buildOneTimePurchaseMap( - OneTimePurchaseOfferDetailsWrapper original) { - return { - 'priceAmountMicros': original.priceAmountMicros, - 'priceCurrencyCode': original.priceCurrencyCode, - 'formattedPrice': original.formattedPrice, - }; -} - -List> buildSubscriptionMapList( - List original) { - return original - .map((SubscriptionOfferDetailsWrapper subscriptionOfferDetails) => - buildSubscriptionMap(subscriptionOfferDetails)) - .toList(); -} - -Map buildSubscriptionMap( - SubscriptionOfferDetailsWrapper original) { - return { - 'offerId': original.offerId, - 'basePlanId': original.basePlanId, - 'offerTags': original.offerTags, - 'offerIdToken': original.offerIdToken, - 'pricingPhases': buildPricingPhaseMapList(original.pricingPhases), - }; -} - -List> buildPricingPhaseMapList( - List original) { - return original - .map((PricingPhaseWrapper pricingPhase) => - buildPricingPhaseMap(pricingPhase)) - .toList(); -} - -Map buildPricingPhaseMap(PricingPhaseWrapper original) { - return { - 'formattedPrice': original.formattedPrice, - 'priceCurrencyCode': original.priceCurrencyCode, - 'priceAmountMicros': original.priceAmountMicros, - 'billingCycleCount': original.billingCycleCount, - 'billingPeriod': original.billingPeriod, - 'recurrenceMode': - const RecurrenceModeConverter().toJson(original.recurrenceMode), - }; -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart deleted file mode 100644 index d9fe397b525d..000000000000 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/product_wrapper_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// 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_android/billing_client_wrappers.dart'; -import 'package:test/test.dart'; - -const ProductWrapper dummyProduct = ProductWrapper( - productId: 'id', - productType: ProductType.inapp, -); - -void main() { - group('ProductWrapper', () { - test('converts product from map', () { - const ProductWrapper expected = dummyProduct; - final ProductWrapper parsed = productFromJson(expected.toJson()); - - expect(parsed, equals(expected)); - }); - }); -} - -ProductWrapper productFromJson(Map serialized) { - return ProductWrapper( - productId: serialized['productId'] as String, - productType: const ProductTypeConverter() - .fromJson(serialized['productType'] as String), - ); -} diff --git a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart index 14cd446bf8a0..71449d7e3782 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/billing_client_wrappers/purchase_wrapper_test.dart @@ -50,26 +50,8 @@ const PurchaseWrapper dummyUnacknowledgedPurchase = PurchaseWrapper( purchaseState: PurchaseStateWrapper.purchased, ); -const PurchaseHistoryRecordWrapper dummyPurchaseHistoryRecord = - PurchaseHistoryRecordWrapper( - purchaseTime: 0, - signature: 'signature', - products: ['product'], - purchaseToken: 'purchaseToken', - originalJson: '', - developerPayload: 'dummy payload', -); - void main() { group('PurchaseWrapper', () { - test('converts from map', () { - const PurchaseWrapper expected = dummyPurchase; - final PurchaseWrapper parsed = - PurchaseWrapper.fromJson(buildPurchaseMap(expected)); - - expect(parsed, equals(expected)); - }); - test('fromPurchase() should return correct PurchaseDetail object', () { final List details = GooglePlayPurchaseDetails.fromPurchase(dummyMultipleProductsPurchase); @@ -121,134 +103,4 @@ void main() { expect(details.pendingCompletePurchase, true); }); }); - - group('PurchaseHistoryRecordWrapper', () { - test('converts from map', () { - const PurchaseHistoryRecordWrapper expected = dummyPurchaseHistoryRecord; - final PurchaseHistoryRecordWrapper parsed = - PurchaseHistoryRecordWrapper.fromJson( - buildPurchaseHistoryRecordMap(expected)); - - expect(parsed, equals(expected)); - }); - }); - - group('PurchasesResultWrapper', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - final List purchases = [ - dummyPurchase, - dummyPurchase - ]; - const String debugMessage = 'dummy Message'; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final PurchasesResultWrapper expected = PurchasesResultWrapper( - billingResult: billingResult, - responseCode: responseCode, - purchasesList: purchases); - final PurchasesResultWrapper parsed = - PurchasesResultWrapper.fromJson({ - 'billingResult': buildBillingResultMap(billingResult), - 'responseCode': const BillingResponseConverter().toJson(responseCode), - 'purchasesList': >[ - buildPurchaseMap(dummyPurchase), - buildPurchaseMap(dummyPurchase) - ] - }); - expect(parsed.billingResult, equals(expected.billingResult)); - expect(parsed.responseCode, equals(expected.responseCode)); - expect(parsed.purchasesList, containsAll(expected.purchasesList)); - }); - - test('parsed from empty map', () { - final PurchasesResultWrapper parsed = - PurchasesResultWrapper.fromJson(const {}); - expect( - parsed.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(parsed.responseCode, BillingResponse.error); - expect(parsed.purchasesList, isEmpty); - }); - }); - - group('PurchasesHistoryResult', () { - test('parsed from map', () { - const BillingResponse responseCode = BillingResponse.ok; - final List purchaseHistoryRecordList = - [ - dummyPurchaseHistoryRecord, - dummyPurchaseHistoryRecord - ]; - const String debugMessage = 'dummy Message'; - const BillingResultWrapper billingResult = BillingResultWrapper( - responseCode: responseCode, debugMessage: debugMessage); - final PurchasesHistoryResult expected = PurchasesHistoryResult( - billingResult: billingResult, - purchaseHistoryRecordList: purchaseHistoryRecordList); - final PurchasesHistoryResult parsed = - PurchasesHistoryResult.fromJson({ - 'billingResult': buildBillingResultMap(billingResult), - 'purchaseHistoryRecordList': >[ - buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord), - buildPurchaseHistoryRecordMap(dummyPurchaseHistoryRecord) - ] - }); - expect(parsed.billingResult, equals(billingResult)); - expect(parsed.purchaseHistoryRecordList, - containsAll(expected.purchaseHistoryRecordList)); - }); - - test('parsed from empty map', () { - final PurchasesHistoryResult parsed = - PurchasesHistoryResult.fromJson(const {}); - expect( - parsed.billingResult, - equals(const BillingResultWrapper( - responseCode: BillingResponse.error, - debugMessage: kInvalidBillingResultErrorMessage))); - expect(parsed.purchaseHistoryRecordList, isEmpty); - }); - }); -} - -Map buildPurchaseMap(PurchaseWrapper original) { - return { - 'orderId': original.orderId, - 'packageName': original.packageName, - 'purchaseTime': original.purchaseTime, - 'signature': original.signature, - 'products': original.products, - 'purchaseToken': original.purchaseToken, - 'isAutoRenewing': original.isAutoRenewing, - 'originalJson': original.originalJson, - 'developerPayload': original.developerPayload, - 'purchaseState': - const PurchaseStateConverter().toJson(original.purchaseState), - 'isAcknowledged': original.isAcknowledged, - 'obfuscatedAccountId': original.obfuscatedAccountId, - 'obfuscatedProfileId': original.obfuscatedProfileId, - }; -} - -Map buildPurchaseHistoryRecordMap( - PurchaseHistoryRecordWrapper original) { - return { - 'purchaseTime': original.purchaseTime, - 'signature': original.signature, - 'products': original.products, - 'purchaseToken': original.purchaseToken, - 'originalJson': original.originalJson, - 'developerPayload': original.developerPayload, - }; -} - -Map buildBillingResultMap(BillingResultWrapper original) { - return { - 'responseCode': - const BillingResponseConverter().toJson(original.responseCode), - 'debugMessage': original.debugMessage, - }; } diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart index a9c76ab5afef..38c2deaf692b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_addition_test.dart @@ -27,8 +27,9 @@ void main() { setUp(() { widgets.WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); manager = BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, UserSelectedAlternativeBillingListener? @@ -80,7 +81,7 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.callCount, equals(2)); expect(result.captured.last, PlatformBillingChoiceMode.alternativeBillingOnly); @@ -95,7 +96,7 @@ void main() { manager.client.hostCallbackHandler.onBillingServiceDisconnected(0); // Verify that after connection ended reconnect was called. final VerificationResult result = - verify(mockApi.startConnection(any, captureAny)); + verify(mockApi.startConnection(any, captureAny, any)); expect(result.callCount, equals(2)); expect(result.captured.last, PlatformBillingChoiceMode.playBillingOnly); }); @@ -107,7 +108,8 @@ void main() { responseCode: BillingResponse.ok, debugMessage: 'dummy message'); when(mockApi.isAlternativeBillingOnlyAvailableAsync()).thenAnswer( (_) async => PlatformBillingResult( - responseCode: 0, debugMessage: expected.debugMessage!)); + responseCode: PlatformBillingResponse.ok, + debugMessage: expected.debugMessage!)); final BillingResultWrapper result = await iapAndroidPlatformAddition.isAlternativeBillingOnlyAvailable(); @@ -136,14 +138,12 @@ void main() { group('queryPurchaseDetails', () { test('returns ProductDetailsResponseWrapper', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), purchases: [ convertToPigeonPurchase(dummyPurchase), ], @@ -179,7 +179,8 @@ void main() { group('isFeatureSupported', () { test('isFeatureSupported returns false', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => false); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); @@ -187,7 +188,8 @@ void main() { }); test('isFeatureSupported returns true', () async { - when(mockApi.isFeatureSupported('subscriptions')) + when(mockApi + .isFeatureSupported(PlatformBillingClientFeature.subscriptions)) .thenAnswer((_) async => true); final bool isSupported = await iapAndroidPlatformAddition .isFeatureSupported(BillingClientFeature.subscriptions); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart index ee183eeba71a..4bd23f244e64 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/in_app_purchase_android_platform_test.dart @@ -30,8 +30,9 @@ void main() { widgets.WidgetsFlutterBinding.ensureInitialized(); mockApi = MockInAppPurchaseApi(); - when(mockApi.startConnection(any, any)).thenAnswer( - (_) async => PlatformBillingResult(responseCode: 0, debugMessage: '')); + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async => + PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: '')); iapAndroidPlatform = InAppPurchaseAndroidPlatform( manager: BillingClientManager( billingClientFactory: (PurchasesUpdatedListener listener, @@ -45,13 +46,13 @@ void main() { group('connection management', () { test('connects on initialization', () { //await iapAndroidPlatform.isAvailable(); - verify(mockApi.startConnection(any, any)).called(1); + verify(mockApi.startConnection(any, any, any)).called(1); }); test('re-connects when client sends onBillingServiceDisconnected', () { iapAndroidPlatform.billingClientManager.client.hostCallbackHandler .onBillingServiceDisconnected(0); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); }); test( @@ -59,19 +60,18 @@ void main() { () async { when(mockApi.acknowledgePurchase(any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: const BillingResponseConverter() - .toJson(BillingResponse.serviceDisconnected), + responseCode: PlatformBillingResponse.serviceDisconnected, debugMessage: 'disconnected'), ); - when(mockApi.startConnection(any, any)).thenAnswer((_) async { + when(mockApi.startConnection(any, any, any)).thenAnswer((_) async { // Change the acknowledgePurchase response to success for the next call. when(mockApi.acknowledgePurchase(any)).thenAnswer( (_) async => PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(BillingResponse.ok), + responseCode: PlatformBillingResponse.ok, debugMessage: 'disconnected'), ); - return PlatformBillingResult(responseCode: 0, debugMessage: ''); + return PlatformBillingResult( + responseCode: PlatformBillingResponse.ok, debugMessage: ''); }); final PurchaseDetails purchase = GooglePlayPurchaseDetails.fromPurchase(dummyUnacknowledgedPurchase) @@ -79,7 +79,7 @@ void main() { final BillingResultWrapper result = await iapAndroidPlatform.completePurchase(purchase); verify(mockApi.acknowledgePurchase(any)).called(2); - verify(mockApi.startConnection(any, any)).called(2); + verify(mockApi.startConnection(any, any, any)).called(2); expect(result.responseCode, equals(BillingResponse.ok)); }); }); @@ -99,13 +99,11 @@ void main() { group('queryProductDetails', () { test('handles empty productDetails', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [], )); @@ -116,13 +114,11 @@ void main() { test('should get correct product details', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) ], @@ -144,13 +140,11 @@ void main() { test('should get the correct notFoundIDs', () async { const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryProductDetailsAsync(any)) .thenAnswer((_) async => PlatformProductDetailsResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), productDetails: [ convertToPigeonProductDetails(dummyOneTimeProductDetails) ], @@ -224,14 +218,12 @@ void main() { }); const String debugMessage = 'dummy message'; - const BillingResponse responseCode = BillingResponse.ok; + const PlatformBillingResponse responseCode = PlatformBillingResponse.ok; when(mockApi.queryPurchasesAsync(any)) .thenAnswer((_) async => PlatformPurchasesResponse( billingResult: PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(responseCode), - debugMessage: debugMessage), + responseCode: responseCode, debugMessage: debugMessage), purchases: [ convertToPigeonPurchase(dummyPurchase), ], @@ -717,7 +709,7 @@ void main() { oldPurchaseDetails: GooglePlayPurchaseDetails.fromPurchase( dummyUnacknowledgedPurchase) .first, - prorationMode: ProrationMode.deferred, + replacementMode: ReplacementMode.deferred, )); await iapAndroidPlatform.buyNonConsumable(purchaseParam: purchaseParam); diff --git a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart b/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart index 483c7ee257f1..9c0bfae2aa7b 100644 --- a/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart +++ b/packages/in_app_purchase/in_app_purchase_android/test/test_conversion_utils.dart @@ -13,8 +13,7 @@ import 'package:in_app_purchase_android/src/pigeon_converters.dart'; /// target must have a non-null string as well. PlatformBillingResult convertToPigeonResult(BillingResultWrapper targetResult) { return PlatformBillingResult( - responseCode: - const BillingResponseConverter().toJson(targetResult.responseCode), + responseCode: billingResponseFromWrapper(targetResult.responseCode), debugMessage: targetResult.debugMessage!, ); }