diff --git a/GodotGooglePlayBilling.gdap b/GodotGooglePlayBilling.gdap index b6fddfd..0a7ec01 100644 --- a/GodotGooglePlayBilling.gdap +++ b/GodotGooglePlayBilling.gdap @@ -2,7 +2,7 @@ name="GodotGooglePlayBilling" binary_type="local" -binary="GodotGooglePlayBilling.1.1.2.release.aar" +binary="GodotGooglePlayBilling.2.0.0-rc.1.release.aar" [dependencies] -remote=["com.android.billingclient:billing:4.0.0"] +remote=["com.android.billingclient:billing:5.0.0"] diff --git a/build.gradle b/build.gradle index 25a626a..c206ca6 100644 --- a/build.gradle +++ b/build.gradle @@ -2,10 +2,10 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:4.0.0" + classpath "com.android.tools.build:gradle:7.2.2" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/godot-google-play-billing/build.gradle b/godot-google-play-billing/build.gradle index 8e987ea..238fe39 100644 --- a/godot-google-play-billing/build.gradle +++ b/godot-google-play-billing/build.gradle @@ -2,19 +2,23 @@ plugins { id 'com.android.library' } -ext.pluginVersionCode = 4 -ext.pluginVersionName = "1.1.2" +ext.pluginVersionCode = 5 +ext.pluginVersionName = "2.0.0-rc.1" android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdkVersion 33 + buildToolsVersion "33.0.0" defaultConfig { minSdkVersion 18 - targetSdkVersion 30 + targetSdkVersion 33 versionCode pluginVersionCode versionName pluginVersionName } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_9 + targetCompatibility JavaVersion.VERSION_1_9 + } libraryVariants.all { variant -> variant.outputs.all { output -> @@ -25,6 +29,6 @@ android { dependencies { implementation "androidx.legacy:legacy-support-v4:1.0.0" - implementation 'com.android.billingclient:billing:4.0.0' + implementation 'com.android.billingclient:billing:5.0.0' compileOnly fileTree(dir: 'libs', include: ['godot-lib*.aar']) } diff --git a/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/GodotGooglePlayBilling.java b/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/GodotGooglePlayBilling.java index a519223..a6eae82 100644 --- a/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/GodotGooglePlayBilling.java +++ b/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/GodotGooglePlayBilling.java @@ -34,6 +34,7 @@ import org.godotengine.godot.Godot; import org.godotengine.godot.plugin.GodotPlugin; import org.godotengine.godot.plugin.SignalInfo; +import org.godotengine.godot.plugin.UsedByGodot; import org.godotengine.godot.plugin.googleplaybilling.utils.GooglePlayBillingUtils; import androidx.annotation.NonNull; @@ -50,13 +51,18 @@ import com.android.billingclient.api.ConsumeResponseListener; import com.android.billingclient.api.PriceChangeConfirmationListener; import com.android.billingclient.api.PriceChangeFlowParams; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.ProductDetailsResponseListener; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.PurchasesResponseListener; import com.android.billingclient.api.PurchasesUpdatedListener; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryPurchasesParams; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; @@ -64,262 +70,271 @@ public class GodotGooglePlayBilling extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener, PriceChangeConfirmationListener { - private final BillingClient billingClient; - private final HashMap skuDetailsCache = new HashMap<>(); // sku → SkuDetails - private boolean calledStartConnection; - private String obfuscatedAccountId; - private String obfuscatedProfileId; - - public GodotGooglePlayBilling(Godot godot) { - super(godot); - - billingClient = BillingClient - .newBuilder(getActivity()) - .enablePendingPurchases() - .setListener(this) - .build(); - calledStartConnection = false; - obfuscatedAccountId = ""; - obfuscatedProfileId = ""; - } - - public void startConnection() { - calledStartConnection = true; - billingClient.startConnection(this); - } - - public void endConnection() { - billingClient.endConnection(); - } - - public boolean isReady() { - return this.billingClient.isReady(); - } - - public int getConnectionState() { - return billingClient.getConnectionState(); - } - - public void queryPurchases(String type) { - billingClient.queryPurchasesAsync(type, new PurchasesResponseListener() { - @Override - public void onQueryPurchasesResponse(BillingResult billingResult, - List purchaseList) { - Dictionary returnValue = new Dictionary(); - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - returnValue.put("status", 0); // OK = 0 - returnValue.put("purchases", GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(purchaseList)); - } else { - returnValue.put("status", 1); // FAILED = 1 - returnValue.put("response_code", billingResult.getResponseCode()); - returnValue.put("debug_message", billingResult.getDebugMessage()); - } - emitSignal("query_purchases_response", (Object)returnValue); - } - }); - } - - public void querySkuDetails(final String[] list, String type) { - List skuList = Arrays.asList(list); - - SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder() - .setSkusList(skuList) - .setType(type); - - billingClient.querySkuDetailsAsync(params.build(), new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, - List skuDetailsList) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - for (SkuDetails skuDetails : skuDetailsList) { - skuDetailsCache.put(skuDetails.getSku(), skuDetails); - } - emitSignal("sku_details_query_completed", (Object)GooglePlayBillingUtils.convertSkuDetailsListToDictionaryObjectArray(skuDetailsList)); - } else { - emitSignal("sku_details_query_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), list); - } - } - }); - } - - public void acknowledgePurchase(final String purchaseToken) { - AcknowledgePurchaseParams acknowledgePurchaseParams = - AcknowledgePurchaseParams.newBuilder() - .setPurchaseToken(purchaseToken) - .build(); - billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() { - @Override - public void onAcknowledgePurchaseResponse(BillingResult billingResult) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - emitSignal("purchase_acknowledged", purchaseToken); - } else { - emitSignal("purchase_acknowledgement_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); - } - } - }); - } - - public void consumePurchase(String purchaseToken) { - ConsumeParams consumeParams = ConsumeParams.newBuilder() - .setPurchaseToken(purchaseToken) - .build(); - - billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() { - @Override - public void onConsumeResponse(BillingResult billingResult, String purchaseToken) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - emitSignal("purchase_consumed", purchaseToken); - } else { - emitSignal("purchase_consumption_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); - } - } - }); - } - - @Override - public void onBillingSetupFinished(BillingResult billingResult) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - emitSignal("connected"); - } else { - emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); - } - } - - @Override - public void onBillingServiceDisconnected() { - emitSignal("disconnected"); - } - - public Dictionary confirmPriceChange(String sku) { - if (!skuDetailsCache.containsKey(sku)) { - Dictionary returnValue = new Dictionary(); - returnValue.put("status", 1); // FAILED = 1 - returnValue.put("response_code", null); // Null since there is no ResponseCode to return but to keep the interface (status, response_code, debug_message) - returnValue.put("debug_message", "You must query the sku details and wait for the result before confirming a price change!"); - return returnValue; - } - - SkuDetails skuDetails = skuDetailsCache.get(sku); - - PriceChangeFlowParams priceChangeFlowParams = - PriceChangeFlowParams.newBuilder().setSkuDetails(skuDetails).build(); - - billingClient.launchPriceChangeConfirmationFlow(getActivity(), priceChangeFlowParams, this); - - Dictionary returnValue = new Dictionary(); - returnValue.put("status", 0); // OK = 0 - return returnValue; - } - - public Dictionary purchase(String sku) { - return purchaseInternal("", sku, - BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); - } - - public Dictionary updateSubscription(String oldToken, String sku, int prorationMode) { - return purchaseInternal(oldToken, sku, prorationMode); - } - - private Dictionary purchaseInternal(String oldToken, String sku, int prorationMode) { - if (!skuDetailsCache.containsKey(sku)) { - Dictionary returnValue = new Dictionary(); - returnValue.put("status", 1); // FAILED = 1 - returnValue.put("response_code", null); // Null since there is no ResponseCode to return but to keep the interface (status, response_code, debug_message) - returnValue.put("debug_message", "You must query the sku details and wait for the result before purchasing!"); - return returnValue; - } - - SkuDetails skuDetails = skuDetailsCache.get(sku); - BillingFlowParams.Builder purchaseParamsBuilder = BillingFlowParams.newBuilder(); - purchaseParamsBuilder.setSkuDetails(skuDetails); - if (!obfuscatedAccountId.isEmpty()) { - purchaseParamsBuilder.setObfuscatedAccountId(obfuscatedAccountId); - } - if (!obfuscatedProfileId.isEmpty()) { - purchaseParamsBuilder.setObfuscatedProfileId(obfuscatedProfileId); - } - if (!oldToken.isEmpty() && prorationMode != BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { - BillingFlowParams.SubscriptionUpdateParams updateParams = - BillingFlowParams.SubscriptionUpdateParams.newBuilder() - .setOldSkuPurchaseToken(oldToken) - .setReplaceSkusProrationMode(prorationMode) - .build(); - purchaseParamsBuilder.setSubscriptionUpdateParams(updateParams); - } - BillingResult result = billingClient.launchBillingFlow(getActivity(), purchaseParamsBuilder.build()); - - Dictionary returnValue = new Dictionary(); - if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { - returnValue.put("status", 0); // OK = 0 - } else { - returnValue.put("status", 1); // FAILED = 1 - returnValue.put("response_code", result.getResponseCode()); - returnValue.put("debug_message", result.getDebugMessage()); - } - - return returnValue; - } - public void setObfuscatedAccountId(String accountId) { - obfuscatedAccountId = accountId; - } - - public void setObfuscatedProfileId(String profileId) { - obfuscatedProfileId = profileId; - } - - @Override - public void onPurchasesUpdated(final BillingResult billingResult, @Nullable final List list) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { - emitSignal("purchases_updated", (Object)GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(list)); - } else { - emitSignal("purchase_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); - } - } - - @Override - public void onPriceChangeConfirmationResult(BillingResult billingResult) { - emitSignal("price_change_acknowledged", billingResult.getResponseCode()); - } - - @Override - public void onMainResume() { - if (calledStartConnection) { - emitSignal("billing_resume"); - } - } - - @NonNull - @Override - public String getPluginName() { - return "GodotGooglePlayBilling"; - } - - @NonNull - @Override - public List getPluginMethods() { - return Arrays.asList("startConnection", "endConnection", "confirmPriceChange", "purchase", "updateSubscription", "querySkuDetails", "isReady", "getConnectionState", "queryPurchases", "acknowledgePurchase", "consumePurchase", "setObfuscatedAccountId", "setObfuscatedProfileId"); - } - - @NonNull - @Override - public Set getPluginSignals() { - Set signals = new ArraySet<>(); - - signals.add(new SignalInfo("connected")); - signals.add(new SignalInfo("disconnected")); - signals.add(new SignalInfo("billing_resume")); - signals.add(new SignalInfo("connect_error", Integer.class, String.class)); - signals.add(new SignalInfo("purchases_updated", Object[].class)); - signals.add(new SignalInfo("query_purchases_response", Object.class)); - signals.add(new SignalInfo("purchase_error", Integer.class, String.class)); - signals.add(new SignalInfo("sku_details_query_completed", Object[].class)); - signals.add(new SignalInfo("sku_details_query_error", Integer.class, String.class, String[].class)); - signals.add(new SignalInfo("price_change_acknowledged", Integer.class)); - signals.add(new SignalInfo("purchase_acknowledged", String.class)); - signals.add(new SignalInfo("purchase_acknowledgement_error", Integer.class, String.class, String.class)); - signals.add(new SignalInfo("purchase_consumed", String.class)); - signals.add(new SignalInfo("purchase_consumption_error", Integer.class, String.class, String.class)); - - return signals; - } + private final BillingClient billingClient; + private final HashMap productDetailsCache = new HashMap<>(); // sku → SkuDetails + private boolean calledStartConnection; + private String obfuscatedAccountId; + private String obfuscatedProfileId; + + public GodotGooglePlayBilling(Godot godot) { + super(godot); + + billingClient = BillingClient + .newBuilder(getActivity()) + .enablePendingPurchases() + .setListener(this) + .build(); + calledStartConnection = false; + obfuscatedAccountId = ""; + obfuscatedProfileId = ""; + } + + @UsedByGodot + public void startConnection() { + calledStartConnection = true; + billingClient.startConnection(this); + } + + @UsedByGodot + public void endConnection() { + billingClient.endConnection(); + } + + @UsedByGodot + public boolean isReady() { + return this.billingClient.isReady(); + } + + @UsedByGodot + public int getConnectionState() { + return billingClient.getConnectionState(); + } + + @UsedByGodot + public void queryPurchases(String type) { + QueryPurchasesParams params = QueryPurchasesParams.newBuilder().setProductType(type).build(); + + billingClient.queryPurchasesAsync(params, new PurchasesResponseListener() { + @Override + public void onQueryPurchasesResponse(BillingResult billingResult, + @NonNull List purchaseList) { + Dictionary returnValue = new Dictionary(); + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 + returnValue.put("purchases", GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(purchaseList)); + } else { + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", billingResult.getResponseCode()); + returnValue.put("debug_message", billingResult.getDebugMessage()); + } + emitSignal("query_purchases_response", (Object) returnValue); + } + }); + } + + @UsedByGodot + public void querySkuDetails(final String[] list, String type) { + ArrayList products = new ArrayList<>(); + + for (String productId : list) { + products.add(QueryProductDetailsParams.Product.newBuilder().setProductId(productId).setProductType(type).build()); + } + + QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder() + .setProductList(products) + .build(); + + billingClient.queryProductDetailsAsync(params, new ProductDetailsResponseListener() { + @Override + public void onProductDetailsResponse(@NonNull BillingResult billingResult, @NonNull List list) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + for (ProductDetails productDetails : list) { + productDetailsCache.put(productDetails.getProductId(), productDetails); + } + emitSignal("product_details_query_completed", (Object) GooglePlayBillingUtils.convertProductDetailsListToDictionaryObjectArray(list)); + } else { + emitSignal("product_details_query_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), list); + } + } + }); + } + + @UsedByGodot + public void acknowledgePurchase(final String purchaseToken) { + AcknowledgePurchaseParams acknowledgePurchaseParams = + AcknowledgePurchaseParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); + billingClient.acknowledgePurchase(acknowledgePurchaseParams, new AcknowledgePurchaseResponseListener() { + @Override + public void onAcknowledgePurchaseResponse(@NonNull BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_acknowledged", purchaseToken); + } else { + emitSignal("purchase_acknowledgement_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } + } + }); + } + + @UsedByGodot + public void consumePurchase(String purchaseToken) { + ConsumeParams consumeParams = ConsumeParams.newBuilder() + .setPurchaseToken(purchaseToken) + .build(); + + billingClient.consumeAsync(consumeParams, new ConsumeResponseListener() { + @Override + public void onConsumeResponse(BillingResult billingResult, String purchaseToken) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("purchase_consumed", purchaseToken); + } else { + emitSignal("purchase_consumption_error", billingResult.getResponseCode(), billingResult.getDebugMessage(), purchaseToken); + } + } + }); + } + + @Override + public void onBillingSetupFinished(BillingResult billingResult) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + emitSignal("connected"); + } else { + emitSignal("connect_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); + } + } + + @Override + public void onBillingServiceDisconnected() { + emitSignal("disconnected"); + } + + @UsedByGodot + public Dictionary purchase(String sku) { + return purchaseInternal("", sku, + BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY); + } + + @UsedByGodot + public Dictionary updateSubscription(String oldToken, String sku, int prorationMode) { + return purchaseInternal(oldToken, sku, prorationMode); + } + + private Dictionary purchaseInternal(String oldToken, String productId, int prorationMode) { + if (!productDetailsCache.containsKey(productId)) { + Dictionary returnValue = new Dictionary(); + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", null); // Null since there is no ResponseCode to return but to keep the interface (status, response_code, debug_message) + returnValue.put("debug_message", "You must query the product details and wait for the result before purchasing!"); + return returnValue; + } + + ProductDetails productDetails = productDetailsCache.get(productId); + assert productDetails != null; + + // TODO: Allow for selecting other than the default/first offer + + String offerToken = null; + if (productDetails.getSubscriptionOfferDetails() != null && !productDetails.getSubscriptionOfferDetails().isEmpty()) { + offerToken = productDetails.getSubscriptionOfferDetails().get(0).getOfferToken(); + } + + List params = List.of( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setOfferToken(offerToken) + .setProductDetails(productDetails) + .build() + ); + + BillingFlowParams.Builder purchaseParamsBuilder = BillingFlowParams.newBuilder(); + purchaseParamsBuilder.setProductDetailsParamsList(params); + + if (!obfuscatedAccountId.isEmpty()) { + purchaseParamsBuilder.setObfuscatedAccountId(obfuscatedAccountId); + } + if (!obfuscatedProfileId.isEmpty()) { + purchaseParamsBuilder.setObfuscatedProfileId(obfuscatedProfileId); + } + + if (!oldToken.isEmpty() && prorationMode != BillingFlowParams.ProrationMode.UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY) { + BillingFlowParams.SubscriptionUpdateParams updateParams = + BillingFlowParams.SubscriptionUpdateParams.newBuilder() + .setOldPurchaseToken(oldToken) + .setReplaceProrationMode(prorationMode) + .build(); + purchaseParamsBuilder.setSubscriptionUpdateParams(updateParams); + } + BillingResult result = billingClient.launchBillingFlow(getActivity(), purchaseParamsBuilder.build()); + + Dictionary returnValue = new Dictionary(); + if (result.getResponseCode() == BillingClient.BillingResponseCode.OK) { + returnValue.put("status", 0); // OK = 0 + } else { + returnValue.put("status", 1); // FAILED = 1 + returnValue.put("response_code", result.getResponseCode()); + returnValue.put("debug_message", result.getDebugMessage()); + } + + return returnValue; + } + + @UsedByGodot + public void setObfuscatedAccountId(String accountId) { + obfuscatedAccountId = accountId; + } + + @UsedByGodot + public void setObfuscatedProfileId(String profileId) { + obfuscatedProfileId = profileId; + } + + @Override + public void onPurchasesUpdated(final BillingResult billingResult, @Nullable final List list) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && list != null) { + emitSignal("purchases_updated", (Object) GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(list)); + } else { + emitSignal("purchase_error", billingResult.getResponseCode(), billingResult.getDebugMessage()); + } + } + + @Override + public void onPriceChangeConfirmationResult(BillingResult billingResult) { + emitSignal("price_change_acknowledged", billingResult.getResponseCode()); + } + + @Override + public void onMainResume() { + if (calledStartConnection) { + emitSignal("billing_resume"); + } + } + + @NonNull + @Override + public String getPluginName() { + return "GodotGooglePlayBilling"; + } + + @NonNull + @Override + public Set getPluginSignals() { + Set signals = new ArraySet<>(); + + signals.add(new SignalInfo("connected")); + signals.add(new SignalInfo("disconnected")); + signals.add(new SignalInfo("billing_resume")); + signals.add(new SignalInfo("connect_error", Integer.class, String.class)); + signals.add(new SignalInfo("purchases_updated", Object[].class)); + signals.add(new SignalInfo("query_purchases_response", Object.class)); + signals.add(new SignalInfo("purchase_error", Integer.class, String.class)); + signals.add(new SignalInfo("product_details_query_completed", Object[].class)); + signals.add(new SignalInfo("product_details_query_error", Integer.class, String.class, String[].class)); + signals.add(new SignalInfo("price_change_acknowledged", Integer.class)); + signals.add(new SignalInfo("purchase_acknowledged", String.class)); + signals.add(new SignalInfo("purchase_acknowledgement_error", Integer.class, String.class, String.class)); + signals.add(new SignalInfo("purchase_consumed", String.class)); + signals.add(new SignalInfo("purchase_consumption_error", Integer.class, String.class, String.class)); + + return signals; + } } diff --git a/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/utils/GooglePlayBillingUtils.java b/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/utils/GooglePlayBillingUtils.java index 1c1cb28..d4b7d58 100644 --- a/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/utils/GooglePlayBillingUtils.java +++ b/godot-google-play-billing/src/main/java/org/godotengine/godot/plugin/googleplaybilling/utils/GooglePlayBillingUtils.java @@ -32,6 +32,7 @@ import org.godotengine.godot.Dictionary; +import com.android.billingclient.api.ProductDetails; import com.android.billingclient.api.Purchase; import com.android.billingclient.api.SkuDetails; @@ -51,33 +52,59 @@ public static Dictionary convertPurchaseToDictionary(Purchase purchase) { dictionary.put("signature", purchase.getSignature()); // PBL V4 replaced getSku with getSkus to support multi-sku purchases, // use the first entry for "sku" and generate an array for "skus" - ArrayList skus = purchase.getSkus(); - dictionary.put("sku", skus.get(0)); - String[] skusArray = skus.toArray(new String[0]); - dictionary.put("skus", skusArray); + String[] products = purchase.getProducts().toArray(new String[0]); + dictionary.put("products", products); dictionary.put("is_acknowledged", purchase.isAcknowledged()); dictionary.put("is_auto_renewing", purchase.isAutoRenewing()); return dictionary; } - public static Dictionary convertSkuDetailsToDictionary(SkuDetails details) { + public static Dictionary convertProductDetailsToDictionary(ProductDetails details) { Dictionary dictionary = new Dictionary(); - dictionary.put("sku", details.getSku()); + dictionary.put("id", details.getProductId()); dictionary.put("title", details.getTitle()); dictionary.put("description", details.getDescription()); - dictionary.put("price", details.getPrice()); - dictionary.put("price_currency_code", details.getPriceCurrencyCode()); - dictionary.put("price_amount_micros", details.getPriceAmountMicros()); - dictionary.put("free_trial_period", details.getFreeTrialPeriod()); - dictionary.put("icon_url", details.getIconUrl()); - dictionary.put("introductory_price", details.getIntroductoryPrice()); - dictionary.put("introductory_price_amount_micros", details.getIntroductoryPriceAmountMicros()); - dictionary.put("introductory_price_cycles", details.getIntroductoryPriceCycles()); - dictionary.put("introductory_price_period", details.getIntroductoryPricePeriod()); - dictionary.put("original_price", details.getOriginalPrice()); - dictionary.put("original_price_amount_micros", details.getOriginalPriceAmountMicros()); - dictionary.put("subscription_period", details.getSubscriptionPeriod()); - dictionary.put("type", details.getType()); + dictionary.put("type", details.getProductType()); + + ProductDetails.OneTimePurchaseOfferDetails oneTimePurchaseOfferDetails = details.getOneTimePurchaseOfferDetails(); + if (oneTimePurchaseOfferDetails != null) { + Dictionary oneTimePurchaseDetailsDictionary = new Dictionary(); + oneTimePurchaseDetailsDictionary.put("formatted_price", oneTimePurchaseOfferDetails.getFormattedPrice()); + oneTimePurchaseDetailsDictionary.put("price_amount_micros", oneTimePurchaseOfferDetails.getPriceAmountMicros()); + oneTimePurchaseDetailsDictionary.put("price_currency_code", oneTimePurchaseOfferDetails.getPriceCurrencyCode()); + dictionary.put("one_time_purchase_details", oneTimePurchaseDetailsDictionary); + } + + List subscriptionOfferDetailsList = details.getSubscriptionOfferDetails(); + + if (subscriptionOfferDetailsList != null && !subscriptionOfferDetailsList.isEmpty()) { + ArrayList subscriptionOfferDetailsDictionaryList = new ArrayList<>(); + + for (ProductDetails.SubscriptionOfferDetails subscriptionOfferDetails : subscriptionOfferDetailsList) { + Dictionary subscriptionOfferDetailsDictionary = new Dictionary(); + subscriptionOfferDetailsDictionary.put("offer_token", subscriptionOfferDetails.getOfferToken()); + subscriptionOfferDetailsDictionary.put("offer_tags", subscriptionOfferDetails.getOfferTags()); + + ArrayList pricingPhasesDictionaryList = new ArrayList<>(); + for (ProductDetails.PricingPhase pricingPhase : subscriptionOfferDetails.getPricingPhases().getPricingPhaseList()) { + Dictionary pricingPhasesDictionary = new Dictionary(); + pricingPhasesDictionary.put("billing_cycle_count", pricingPhase.getBillingCycleCount()); + pricingPhasesDictionary.put("billing_period", pricingPhase.getBillingPeriod()); + pricingPhasesDictionary.put("formatted_price", pricingPhase.getFormattedPrice()); + pricingPhasesDictionary.put("price_amount_micros", pricingPhase.getPriceAmountMicros()); + pricingPhasesDictionary.put("price_currency_code", pricingPhase.getPriceCurrencyCode()); + pricingPhasesDictionary.put("recurrence_mode", pricingPhase.getRecurrenceMode()); + pricingPhasesDictionaryList.add(pricingPhasesDictionary); + } + + subscriptionOfferDetailsDictionary.put("pricing_phases", pricingPhasesDictionaryList); + + subscriptionOfferDetailsDictionaryList.add(subscriptionOfferDetailsDictionary); + } + + dictionary.put("subscription_offer_details", subscriptionOfferDetailsDictionaryList); + } + return dictionary; } @@ -91,11 +118,11 @@ public static Object[] convertPurchaseListToDictionaryObjectArray(List return purchaseDictionaries; } - public static Object[] convertSkuDetailsListToDictionaryObjectArray(List skuDetails) { - Object[] skuDetailsDictionaries = new Object[skuDetails.size()]; + public static Object[] convertProductDetailsListToDictionaryObjectArray(List productDetails) { + Object[] skuDetailsDictionaries = new Object[productDetails.size()]; - for (int i = 0; i < skuDetails.size(); i++) { - skuDetailsDictionaries[i] = GooglePlayBillingUtils.convertSkuDetailsToDictionary(skuDetails.get(i)); + for (int i = 0; i < productDetails.size(); i++) { + skuDetailsDictionaries[i] = GooglePlayBillingUtils.convertProductDetailsToDictionary(productDetails.get(i)); } return skuDetailsDictionaries; diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fef635c..1cb2d14 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jun 15 13:39:16 CEST 2020 +#Thu Sep 01 20:58:16 CEST 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +zipStoreBase=GRADLE_USER_HOME