diff --git a/extension-iap/manifests/android/build.gradle b/extension-iap/manifests/android/build.gradle index 8ad2663..1f4aa9e 100644 --- a/extension-iap/manifests/android/build.gradle +++ b/extension-iap/manifests/android/build.gradle @@ -1,3 +1,3 @@ dependencies { - implementation 'com.android.billingclient:billing:3.0.0' + implementation 'com.android.billingclient:billing:5.0.0' } diff --git a/extension-iap/src/java/com/defold/iap/IapGooglePlay.java b/extension-iap/src/java/com/defold/iap/IapGooglePlay.java index 30772c3..8fdf335 100644 --- a/extension-iap/src/java/com/defold/iap/IapGooglePlay.java +++ b/extension-iap/src/java/com/defold/iap/IapGooglePlay.java @@ -19,26 +19,31 @@ import android.util.Log; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient.BillingResponseCode; -import com.android.billingclient.api.BillingClient.SkuType; +import com.android.billingclient.api.BillingClient.ProductType; import com.android.billingclient.api.BillingResult; import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.Purchase.PurchasesResult; import com.android.billingclient.api.Purchase.PurchaseState; -import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.ProductDetails; +import com.android.billingclient.api.ProductDetails.OneTimePurchaseOfferDetails; +import com.android.billingclient.api.ProductDetails.SubscriptionOfferDetails; import com.android.billingclient.api.ConsumeParams; import com.android.billingclient.api.BillingFlowParams; -import com.android.billingclient.api.SkuDetailsParams; +import com.android.billingclient.api.BillingFlowParams.ProductDetailsParams; +import com.android.billingclient.api.QueryPurchasesParams; +import com.android.billingclient.api.QueryProductDetailsParams; +import com.android.billingclient.api.QueryProductDetailsParams.Product; import com.android.billingclient.api.AcknowledgePurchaseParams; import com.android.billingclient.api.PurchasesUpdatedListener; import com.android.billingclient.api.BillingClientStateListener; import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.SkuDetailsResponseListener; +import com.android.billingclient.api.PurchasesResponseListener; +import com.android.billingclient.api.ProductDetailsResponseListener; import com.android.billingclient.api.AcknowledgePurchaseResponseListener; public class IapGooglePlay implements PurchasesUpdatedListener { public static final String TAG = "IapGooglePlay"; - private Map products = new HashMap(); + private Map products = new HashMap(); private BillingClient billingClient; private IPurchaseListener purchaseListener; private boolean autoFinishTransactions; @@ -110,16 +115,24 @@ public class IapGooglePlay implements PurchasesUpdatedListener { return p.toString(); } - private JSONObject convertSkuDetails(SkuDetails skuDetails) { + private JSONObject convertProductDetails(ProductDetails productDetails) { JSONObject p = new JSONObject(); try { - p.put("price_string", skuDetails.getPrice()); - p.put("ident", skuDetails.getSku()); - p.put("currency_code", skuDetails.getPriceCurrencyCode()); - p.put("price", skuDetails.getPriceAmountMicros() * 0.000001); + p.put("ident", productDetails.getProductId()); + + if (productDetails.getProductType() == ProductType.INAPP) { + OneTimePurchaseOfferDetails offerDetails = productDetails.getOneTimePurchaseOfferDetails(); + p.put("price_string", offerDetails.getFormattedPrice()); + p.put("currency_code", offerDetails.getPriceCurrencyCode()); + p.put("price", offerDetails.getPriceAmountMicros() * 0.000001); + } + else if (productDetails.getProductType() == ProductType.INAPP) { + List subscriptionDetails = productDetails.getSubscriptionOfferDetails(); + + } } catch(JSONException e) { - Log.wtf(TAG, "Failed to convert sku details", e); + Log.wtf(TAG, "Failed to convert product details", e); } return p; } @@ -184,21 +197,6 @@ public class IapGooglePlay implements PurchasesUpdatedListener { return billingResponseCodeToDefoldResponse(result.getResponseCode()); } - /** - * Query Google Play for purchases done within the app. - */ - private List queryPurchases(final String type) { - PurchasesResult result = billingClient.queryPurchases(type); - List purchases = result.getPurchasesList(); - if (purchases == null) { - purchases = new ArrayList(); - } - if (result.getBillingResult().getResponseCode() != BillingResponseCode.OK) { - Log.e(TAG, "Unable to query pending purchases: " + result.getBillingResult().getDebugMessage()); - } - return purchases; - } - /** * This method is called either explicitly from Lua or from extension code * when "set_listener()" is called from Lua. @@ -207,12 +205,32 @@ public class IapGooglePlay implements PurchasesUpdatedListener { */ public void processPendingConsumables(final IPurchaseListener purchaseListener) { Log.d(TAG, "processPendingConsumables()"); - List purchasesList = new ArrayList(); - purchasesList.addAll(queryPurchases(SkuType.INAPP)); - purchasesList.addAll(queryPurchases(SkuType.SUBS)); - for (Purchase purchase : purchasesList) { - handlePurchase(purchase, purchaseListener); - } + + + PurchasesResponseListener purchasesListener = new PurchasesResponseListener() { + private List allPurchases = new ArrayList(); + private int queries = 2; + + @Override + public void onQueryPurchasesResponse(BillingResult billingResult, List purchases) { + if (billingResult.getResponseCode() != BillingResponseCode.OK) { + Log.e(TAG, "Unable to query pending purchases: " + billingResult.getDebugMessage()); + } + if (purchases != null) { + allPurchases.addAll(purchases); + } + // we're finished when we have queried for both in-app and subs + queries--; + if (queries == 0) { + for (Purchase purchase : allPurchases) { + handlePurchase(purchase, purchaseListener); + } + } + } + }; + + billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(ProductType.INAPP).build(), purchasesListener); + billingClient.queryPurchasesAsync(QueryPurchasesParams.newBuilder().setProductType(ProductType.SUBS).build(), purchasesListener); } /** @@ -310,11 +328,14 @@ public class IapGooglePlay implements PurchasesUpdatedListener { * Buy a product. This method stores the listener and uses it in the * onPurchasesUpdated() callback. */ - private void buyProduct(SkuDetails sku, final IPurchaseListener purchaseListener) { + private void buyProduct(ProductDetails pd, final IPurchaseListener purchaseListener) { this.purchaseListener = purchaseListener; + List productDetailsParams = new ArrayList(); + productDetailsParams.add(ProductDetailsParams.newBuilder().setProductDetails(pd).build()); + BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() - .setSkuDetails(sku) + .setProductDetailsParamsList(productDetailsParams) .build(); BillingResult billingResult = billingClient.launchBillingFlow(this.activity, billingFlowParams); @@ -330,18 +351,18 @@ public class IapGooglePlay implements PurchasesUpdatedListener { public void buy(final String product, final IPurchaseListener purchaseListener) { Log.d(TAG, "buy()"); - SkuDetails sku = this.products.get(product); - if (sku != null) { - buyProduct(sku, purchaseListener); + ProductDetails pd = this.products.get(product); + if (pd != null) { + buyProduct(pd, purchaseListener); } else { - List skuList = new ArrayList(); - skuList.add(product); - querySkuDetailsAsync(skuList, new SkuDetailsResponseListener() { + List productList = new ArrayList(); + productList.add(product); + queryProductDetailsAsync(productList, new ProductDetailsResponseListener() { @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { - if (billingResult.getResponseCode() == BillingResponseCode.OK && (skuDetailsList != null) && !skuDetailsList.isEmpty()) { - buyProduct(skuDetailsList.get(0), purchaseListener); + public void onProductDetailsResponse(BillingResult billingResult, List productDetailsList) { + if (billingResult.getResponseCode() == BillingResponseCode.OK && (productDetailsList != null) && !productDetailsList.isEmpty()) { + buyProduct(productDetailsList.get(0), purchaseListener); } else { Log.e(TAG, "Unable to get product details before buying: " + billingResult.getDebugMessage()); @@ -356,30 +377,40 @@ public class IapGooglePlay implements PurchasesUpdatedListener { * Get details for a list of products. The products can be a mix of * in-app products and subscriptions. */ - private void querySkuDetailsAsync(final List skuList, final SkuDetailsResponseListener listener) { - SkuDetailsResponseListener detailsListener = new SkuDetailsResponseListener() { - private List allSkuDetails = new ArrayList(); + private void queryProductDetailsAsync(final List productList, final ProductDetailsResponseListener listener) { + ProductDetailsResponseListener detailsListener = new ProductDetailsResponseListener() { + private List allProductDetails = new ArrayList(); private int queries = 2; @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetails) { - if (skuDetails != null) { - // cache skus (cache will be used to speed up buying) - for (SkuDetails sd : skuDetails) { - IapGooglePlay.this.products.put(sd.getSku(), sd); + public void onProductDetailsResponse(BillingResult billingResult, List productDetails) { + if (productDetails != null) { + // cache products (cache will be used to speed up buying) + for (ProductDetails pd : productDetails) { + IapGooglePlay.this.products.put(pd.getProductId(), pd); } - // add to list of all sku details - allSkuDetails.addAll(skuDetails); + // add to list of all product details + allProductDetails.addAll(productDetails); } // we're finished when we have queried for both in-app and subs queries--; if (queries == 0) { - listener.onSkuDetailsResponse(billingResult, allSkuDetails); + listener.onProductDetailsResponse(billingResult, allProductDetails); } } }; - billingClient.querySkuDetailsAsync(SkuDetailsParams.newBuilder().setSkusList(skuList).setType(SkuType.INAPP).build(), detailsListener); - billingClient.querySkuDetailsAsync(SkuDetailsParams.newBuilder().setSkusList(skuList).setType(SkuType.SUBS).build(), detailsListener); + + List inappProductList = new ArrayList(); + List subsProductList = new ArrayList(); + for (String productId : productList) { + inappProductList.add(Product.newBuilder().setProductId(productId).setProductType(ProductType.INAPP).build()); + subsProductList.add(Product.newBuilder().setProductId(productId).setProductType(ProductType.SUBS).build()); + } + QueryProductDetailsParams inappParams = QueryProductDetailsParams.newBuilder().setProductList(inappProductList).build(); + QueryProductDetailsParams subsParams = QueryProductDetailsParams.newBuilder().setProductList(subsProductList).build(); + + billingClient.queryProductDetailsAsync(inappParams, detailsListener); + billingClient.queryProductDetailsAsync(subsParams, detailsListener); } /** @@ -388,21 +419,21 @@ public class IapGooglePlay implements PurchasesUpdatedListener { public void listItems(final String products, final IListProductsListener productsListener, final long commandPtr) { Log.d(TAG, "listItems()"); - // create list of skus from comma separated string - List skuList = new ArrayList(); + // create list of product ids from comma separated string + List productList = new ArrayList(); for (String p : products.split(",")) { if (p.trim().length() > 0) { - skuList.add(p); + productList.add(p); } } - querySkuDetailsAsync(skuList, new SkuDetailsResponseListener() { + queryProductDetailsAsync(productList, new ProductDetailsResponseListener() { @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetails) { + public void onProductDetailsResponse(BillingResult billingResult, List productDetails) { JSONArray a = new JSONArray(); if (billingResult.getResponseCode() == BillingResponseCode.OK) { - for (SkuDetails sd : skuDetails) { - a.put(convertSkuDetails(sd)); + for (ProductDetails pd : productDetails) { + a.put(convertProductDetails(pd)); } } else {