diff --git a/GodotGooglePlayBilling.gdap b/GodotGooglePlayBilling.gdap index 03505b5..55cd56d 100644 --- a/GodotGooglePlayBilling.gdap +++ b/GodotGooglePlayBilling.gdap @@ -2,7 +2,7 @@ name="GodotGooglePlayBilling" binary_type="local" -binary="GodotGooglePlayBilling.1.0.1.release.aar" +binary="GodotGooglePlayBilling.1.1.0.release.aar" [dependencies] -remote=["com.android.billingclient:billing:3.0.0"] +remote=["com.android.billingclient:billing:4.0.0"] diff --git a/godot-google-play-billing/build.gradle b/godot-google-play-billing/build.gradle index 551c6ba..6306abb 100644 --- a/godot-google-play-billing/build.gradle +++ b/godot-google-play-billing/build.gradle @@ -2,16 +2,16 @@ plugins { id 'com.android.library' } -ext.pluginVersionCode = 2 -ext.pluginVersionName = "1.0.1" +ext.pluginVersionCode = 3 +ext.pluginVersionName = "1.1.0" android { - compileSdkVersion 29 - buildToolsVersion "29.0.3" + compileSdkVersion 30 + buildToolsVersion "30.0.3" defaultConfig { minSdkVersion 18 - targetSdkVersion 29 + targetSdkVersion 30 versionCode pluginVersionCode versionName pluginVersionName } @@ -25,6 +25,6 @@ android { dependencies { implementation "androidx.legacy:legacy-support-v4:1.0.0" - implementation 'com.android.billingclient:billing:3.0.0' + implementation 'com.android.billingclient:billing:4.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 b5c1d12..beb0d14 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 @@ -48,7 +48,10 @@ import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.PriceChangeConfirmationListener; +import com.android.billingclient.api.PriceChangeFlowParams; import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchasesResponseListener; import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; @@ -59,10 +62,12 @@ import java.util.List; import java.util.Set; -public class GodotGooglePlayBilling extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener { +public class GodotGooglePlayBilling extends GodotPlugin implements PurchasesUpdatedListener, BillingClientStateListener, PriceChangeConfirmationListener { private final BillingClient billingClient; private final HashMap skuDetailsCache = new HashMap<>(); // sku → SkuDetails + private String obfuscatedAccountId; + private String obfuscatedProfileId; public GodotGooglePlayBilling(Godot godot) { super(godot); @@ -72,6 +77,8 @@ public GodotGooglePlayBilling(Godot godot) { .enablePendingPurchases() .setListener(this) .build(); + obfuscatedAccountId = ""; + obfuscatedProfileId = ""; } public void startConnection() { @@ -86,20 +93,27 @@ public boolean isReady() { return this.billingClient.isReady(); } - public Dictionary queryPurchases(String type) { - Purchase.PurchasesResult result = billingClient.queryPurchases(type); - - Dictionary returnValue = new Dictionary(); - if (result.getBillingResult().getResponseCode() == BillingClient.BillingResponseCode.OK) { - returnValue.put("status", 0); // OK = 0 - returnValue.put("purchases", GooglePlayBillingUtils.convertPurchaseListToDictionaryObjectArray(result.getPurchasesList())); - } else { - returnValue.put("status", 1); // FAILED = 1 - returnValue.put("response_code", result.getBillingResult().getResponseCode()); - returnValue.put("debug_message", result.getBillingResult().getDebugMessage()); - } + public int getConnectionState() { + return billingClient.getConnectionState(); + } - return returnValue; + 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) { @@ -173,7 +187,37 @@ 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 @@ -183,11 +227,23 @@ public Dictionary purchase(String sku) { } SkuDetails skuDetails = skuDetailsCache.get(sku); - BillingFlowParams purchaseParams = BillingFlowParams.newBuilder() - .setSkuDetails(skuDetails) - .build(); - - BillingResult result = billingClient.launchBillingFlow(getActivity(), purchaseParams); + 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) { @@ -199,6 +255,13 @@ public Dictionary purchase(String sku) { } return returnValue; + } + public void setObfuscatedAccountId(String accountId) { + obfuscatedAccountId = accountId; + } + + public void setObfuscatedProfileId(String profileId) { + obfuscatedProfileId = profileId; } @Override @@ -210,6 +273,16 @@ public void onPurchasesUpdated(final BillingResult billingResult, @Nullable fina } } + @Override + public void onPriceChangeConfirmationResult(BillingResult billingResult) { + emitSignal("price_change_acknowledged", billingResult.getResponseCode()); + } + + @Override + public void onMainResume() { + emitSignal("billing_resume"); + } + @NonNull @Override public String getPluginName() { @@ -219,7 +292,7 @@ public String getPluginName() { @NonNull @Override public List getPluginMethods() { - return Arrays.asList("startConnection", "endConnection", "purchase", "querySkuDetails", "isReady", "queryPurchases", "acknowledgePurchase", "consumePurchase"); + return Arrays.asList("startConnection", "endConnection", "confirmPriceChange", "purchase", "updateSubscription", "querySkuDetails", "isReady", "getConnectionState", "queryPurchases", "acknowledgePurchase", "consumePurchase"); } @NonNull @@ -229,11 +302,14 @@ public Set getPluginSignals() { 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)); 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 69fa0ba..724b110 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 @@ -35,6 +35,7 @@ import com.android.billingclient.api.Purchase; import com.android.billingclient.api.SkuDetails; +import java.util.ArrayList; import java.util.List; public class GooglePlayBillingUtils { @@ -45,8 +46,14 @@ public static Dictionary convertPurchaseToDictionary(Purchase purchase) { dictionary.put("purchase_state", purchase.getPurchaseState()); dictionary.put("purchase_time", purchase.getPurchaseTime()); dictionary.put("purchase_token", purchase.getPurchaseToken()); + dictionary.put("quantity", purchase.getQuantity()); dictionary.put("signature", purchase.getSignature()); - dictionary.put("sku", purchase.getSku()); + // 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); dictionary.put("is_acknowledged", purchase.isAcknowledged()); dictionary.put("is_auto_renewing", purchase.isAutoRenewing()); return dictionary;