diff --git a/extension-iap/api/iap.script_api b/extension-iap/api/iap.script_api index c323c03..00c4775 100644 --- a/extension-iap/api/iap.script_api +++ b/extension-iap/api/iap.script_api @@ -23,6 +23,10 @@ type: string desc: Facebook only. [icon:facebook] Optional custom unique request id to set for this transaction. The id becomes attached to the payment within the Graph API. + - name: token + type: string + desc: [icon:googleplay] Which subscription offer to use when buying a subscription. The token can be retrieved from + the subscriptions table returned when calling iap.list() examples: - desc: |- @@ -128,15 +132,56 @@ - name: price type: number desc: The price of the product. + [icon:googleplay]: Used only for in-app products - name: price_string type: string desc: The price of the product, as a formatted string (amount and currency symbol). + [icon:googleplay]: Used only for in-app products - name: currency_code type: string - desc: The currency code. On Google Play, this reflects the merchant's locale, instead of the user's. - [icon:ios] [icon:googleplay] [icon:facebook] + desc: [icon:ios] [icon:googleplay] [icon:facebook] The currency code. + [icon:googleplay]: The merchant's locale, instead of the user's + [icon:googleplay]: Used only for in-app products + + - name: subscriptions + type: table + desc: [icon:googleplay] List of subscription offers. + Each offer contains a token and a list of price and billing options. + See https://developer.android.com/reference/com/android/billingclient/api/ProductDetails.PricingPhase + members: + - name: token + type: string + desc: The token associated with the pricing phases for the subscription. + + - name: pricing + type: table + desc: The pricing phases for the subscription. + members: + - name: price_string + type: string + desc: Formatted price for the payment cycle, including currency sign. + + - name: price + type: number + desc: Price of the payment cycle in micro-units. + + - name: currency_code + type: string + desc: ISO 4217 currency code + + - name: billing_period + type: string + desc: Billing period of the payment cycle, specified in ISO 8601 format + + - name: billing_cycle_count + type: number + desc: Number of cycles for which the billing period is applied. + + - name: recurrence_mode + type: string + desc: FINITE, INFINITE or NONE - name: error type: table 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/iap_android.cpp b/extension-iap/src/iap_android.cpp index 32c0b38..caac8af 100644 --- a/extension-iap/src/iap_android.cpp +++ b/extension-iap/src/iap_android.cpp @@ -40,6 +40,8 @@ static IAP g_IAP; static int IAP_ProcessPendingTransactions(lua_State* L) { + DM_LUA_STACK_CHECK(L, 0); + dmAndroid::ThreadAttacher threadAttacher; JNIEnv* env = threadAttacher.GetEnv(); env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_ProcessPendingConsumables, g_IAP.m_IAPJNI); @@ -49,11 +51,11 @@ static int IAP_ProcessPendingTransactions(lua_State* L) static int IAP_List(lua_State* L) { - int top = lua_gettop(L); + DM_LUA_STACK_CHECK(L, 0); + char* buf = IAP_List_CreateBuffer(L); if( buf == 0 ) { - assert(top == lua_gettop(L)); return 0; } @@ -68,36 +70,46 @@ static int IAP_List(lua_State* L) env->DeleteLocalRef(products); free(buf); - assert(top == lua_gettop(L)); return 0; } static int IAP_Buy(lua_State* L) { - int top = lua_gettop(L); + DM_LUA_STACK_CHECK(L, 0); + int top = lua_gettop(L); const char* id = luaL_checkstring(L, 1); + const char* token = ""; + + if (top >= 2 && lua_istable(L, 2)) { + luaL_checktype(L, 2, LUA_TTABLE); + lua_pushvalue(L, 2); + lua_getfield(L, -1, "token"); + token = lua_isnil(L, -1) ? "" : luaL_checkstring(L, -1); + lua_pop(L, 2); + } dmAndroid::ThreadAttacher threadAttacher; JNIEnv* env = threadAttacher.GetEnv(); jstring ids = env->NewStringUTF(id); - env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Buy, ids, g_IAP.m_IAPJNI); + jstring tokens = env->NewStringUTF(token); + env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Buy, ids, tokens, g_IAP.m_IAPJNI); env->DeleteLocalRef(ids); + env->DeleteLocalRef(tokens); - assert(top == lua_gettop(L)); return 0; } static int IAP_Finish(lua_State* L) { + DM_LUA_STACK_CHECK(L, 0); + if(g_IAP.m_autoFinishTransactions) { dmLogWarning("Calling iap.finish when autofinish transactions is enabled. Ignored."); return 0; } - int top = lua_gettop(L); - luaL_checktype(L, 1, LUA_TTABLE); lua_getfield(L, -1, "state"); @@ -107,7 +119,6 @@ static int IAP_Finish(lua_State* L) { dmLogError("Invalid transaction state (must be iap.TRANS_STATE_PURCHASED)."); lua_pop(L, 1); - assert(top == lua_gettop(L)); return 0; } } @@ -130,13 +141,12 @@ static int IAP_Finish(lua_State* L) env->DeleteLocalRef(receiptUTF); } - assert(top == lua_gettop(L)); return 0; } static int IAP_Acknowledge(lua_State* L) { - int top = lua_gettop(L); + DM_LUA_STACK_CHECK(L, 0); luaL_checktype(L, 1, LUA_TTABLE); @@ -147,7 +157,6 @@ static int IAP_Acknowledge(lua_State* L) { dmLogError("Invalid transaction state (must be iap.TRANS_STATE_PURCHASED)."); lua_pop(L, 1); - assert(top == lua_gettop(L)); return 0; } } @@ -170,7 +179,6 @@ static int IAP_Acknowledge(lua_State* L) env->DeleteLocalRef(receiptUTF); } - assert(top == lua_gettop(L)); return 0; } @@ -178,20 +186,20 @@ static int IAP_Restore(lua_State* L) { // TODO: Missing callback here for completion/error // See iap_ios.mm + DM_LUA_STACK_CHECK(L, 1); - int top = lua_gettop(L); dmAndroid::ThreadAttacher threadAttacher; JNIEnv* env = threadAttacher.GetEnv(); env->CallVoidMethod(g_IAP.m_IAP, g_IAP.m_Restore, g_IAP.m_IAPJNI); - assert(top == lua_gettop(L)); - lua_pushboolean(L, 1); return 1; } static int IAP_SetListener(lua_State* L) { + DM_LUA_STACK_CHECK(L, 0); + IAP* iap = &g_IAP; bool had_previous = iap->m_Listener != 0; @@ -212,6 +220,8 @@ static int IAP_SetListener(lua_State* L) static int IAP_GetProviderId(lua_State* L) { + DM_LUA_STACK_CHECK(L, 1); + lua_pushinteger(L, g_IAP.m_ProviderId); return 1; } @@ -409,7 +419,7 @@ static dmExtension::Result InitializeIAP(dmExtension::Params* params) jclass iap_jni_class = dmAndroid::LoadClass(env, "com.defold.iap.IapJNI"); g_IAP.m_List = env->GetMethodID(iap_class, "listItems", "(Ljava/lang/String;Lcom/defold/iap/IListProductsListener;J)V"); - g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); + g_IAP.m_Buy = env->GetMethodID(iap_class, "buy", "(Ljava/lang/String;Ljava/lang/String;Lcom/defold/iap/IPurchaseListener;)V"); g_IAP.m_Restore = env->GetMethodID(iap_class, "restore", "(Lcom/defold/iap/IPurchaseListener;)V"); g_IAP.m_Stop = env->GetMethodID(iap_class, "stop", "()V"); g_IAP.m_ProcessPendingConsumables = env->GetMethodID(iap_class, "processPendingConsumables", "(Lcom/defold/iap/IPurchaseListener;)V"); diff --git a/extension-iap/src/iap_private.h b/extension-iap/src/iap_private.h index e1c287e..eb8708a 100644 --- a/extension-iap/src/iap_private.h +++ b/extension-iap/src/iap_private.h @@ -21,7 +21,7 @@ struct DM_ALIGNED(16) IAPCommand // Used for storing eventual callback info (if needed) dmScript::LuaCallbackInfo* m_Callback; - // THe actual command payload + // The actual command payload int32_t m_Command; int32_t m_ResponseCode; void* m_Data; diff --git a/extension-iap/src/java/com/defold/iap/IapAmazon.java b/extension-iap/src/java/com/defold/iap/IapAmazon.java index 3b1da11..d975e35 100644 --- a/extension-iap/src/java/com/defold/iap/IapAmazon.java +++ b/extension-iap/src/java/com/defold/iap/IapAmazon.java @@ -81,7 +81,7 @@ public class IapAmazon implements PurchasingListener { } } - public void buy(final String product, final IPurchaseListener listener) { + public void buy(final String product, final String token, final IPurchaseListener listener) { synchronized (purchaseListeners) { RequestId req = PurchasingService.purchase(product); if (req != null) { diff --git a/extension-iap/src/java/com/defold/iap/IapGooglePlay.java b/extension-iap/src/java/com/defold/iap/IapGooglePlay.java index 30772c3..22c1a36 100644 --- a/extension-iap/src/java/com/defold/iap/IapGooglePlay.java +++ b/extension-iap/src/java/com/defold/iap/IapGooglePlay.java @@ -19,26 +19,34 @@ 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.PricingPhases; +import com.android.billingclient.api.ProductDetails.PricingPhase; +import com.android.billingclient.api.ProductDetails.RecurrenceMode; +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 +118,67 @@ public class IapGooglePlay implements PurchasesUpdatedListener { return p.toString(); } - private JSONObject convertSkuDetails(SkuDetails skuDetails) { - JSONObject p = new JSONObject(); + private JSONArray convertSubscriptionOfferPricingPhases(SubscriptionOfferDetails details) { + JSONArray a = new JSONArray(); 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); + List pricingPhases = details.getPricingPhases().getPricingPhaseList(); + for (PricingPhase pricingPhase : pricingPhases) { + JSONObject o = new JSONObject(); + o.put("price_string", pricingPhase.getFormattedPrice()); + o.put("price", pricingPhase.getPriceAmountMicros() * 0.000001); + o.put("currency_code", pricingPhase.getPriceCurrencyCode()); + o.put("billing_period", pricingPhase.getBillingPeriod()); + o.put("billing_cycle_count", pricingPhase.getBillingCycleCount()); + switch (pricingPhase.getRecurrenceMode()) { + case RecurrenceMode.FINITE_RECURRING: + o.put("recurrence_mode", "FINITE"); + break; + case RecurrenceMode.INFINITE_RECURRING: + o.put("recurrence_mode", "INFINITE"); + break; + default: + case RecurrenceMode.NON_RECURRING: + o.put("recurrence_mode", "NONE"); + break; + } + a.put(o); + } } catch(JSONException e) { - Log.wtf(TAG, "Failed to convert sku details", e); + Log.wtf(TAG, "Failed to convert subscription offer pricing phases", e); + } + return a; + } + + private JSONObject convertProductDetails(ProductDetails productDetails) { + JSONObject p = new JSONObject(); + try { + p.put("ident", productDetails.getProductId()); + p.put("title", productDetails.getTitle()); + p.put("description", productDetails.getDescription()); + if (productDetails.getProductType().equals(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().equals(ProductType.SUBS)) { + List subscriptionOfferDetails = productDetails.getSubscriptionOfferDetails(); + JSONArray a = new JSONArray(); + for (SubscriptionOfferDetails offerDetails : subscriptionOfferDetails) { + JSONObject o = new JSONObject(); + o.put("token", offerDetails.getOfferToken()); + o.put("pricing", convertSubscriptionOfferPricingPhases(offerDetails)); + a.put(o); + } + p.put("subscriptions", a); + } + else { + Log.i(TAG, "convertProductDetails() unknown product type " + productDetails.getProductType()); + } + } + catch(JSONException e) { + Log.wtf(TAG, "Failed to convert product details", e); } return p; } @@ -184,21 +243,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 +251,34 @@ 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); + } + } + } + }; + + final QueryPurchasesParams inappParams = QueryPurchasesParams.newBuilder().setProductType(ProductType.INAPP).build(); + final QueryPurchasesParams subsParams = QueryPurchasesParams.newBuilder().setProductType(ProductType.SUBS).build(); + billingClient.queryPurchasesAsync(inappParams, purchasesListener); + billingClient.queryPurchasesAsync(subsParams, purchasesListener); } /** @@ -221,7 +287,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener { */ private void consumePurchase(final String purchaseToken, final ConsumeResponseListener consumeListener) { Log.d(TAG, "consumePurchase() " + purchaseToken); - ConsumeParams consumeParams = ConsumeParams.newBuilder() + final ConsumeParams consumeParams = ConsumeParams.newBuilder() .setPurchaseToken(purchaseToken) .build(); @@ -273,7 +339,7 @@ public class IapGooglePlay implements PurchasesUpdatedListener { * Handle a purchase. If the extension is configured to automatically * finish transactions the purchase will be immediately consumed. Otherwise * the product will be returned via the listener without being consumed. - * NOTE: Billing 3.0 requires purchases to be acknowledged within 3 days of + * NOTE: Billing 3.0+ requires purchases to be acknowledged within 3 days of * purchase unless they are consumed. */ private void handlePurchase(final Purchase purchase, final IPurchaseListener purchaseListener) { @@ -310,12 +376,18 @@ 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 String token, final IPurchaseListener purchaseListener) { this.purchaseListener = purchaseListener; - BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() - .setSkuDetails(sku) - .build(); + List productDetailsParams = new ArrayList(); + if (pd.getProductType().equals(ProductType.SUBS)) { + productDetailsParams.add(ProductDetailsParams.newBuilder().setProductDetails(pd).setOfferToken(token).build()); + } + else { + productDetailsParams.add(ProductDetailsParams.newBuilder().setProductDetails(pd).build()); + } + + final BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder().setProductDetailsParamsList(productDetailsParams).build(); BillingResult billingResult = billingClient.launchBillingFlow(this.activity, billingFlowParams); if (billingResult.getResponseCode() != BillingResponseCode.OK) { @@ -327,21 +399,21 @@ public class IapGooglePlay implements PurchasesUpdatedListener { /** * Called from Lua. */ - public void buy(final String product, final IPurchaseListener purchaseListener) { + public void buy(final String product, final String token, 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, token, 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), token, purchaseListener); } else { Log.e(TAG, "Unable to get product details before buying: " + billingResult.getDebugMessage()); @@ -356,30 +428,43 @@ 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); + + // we don't know if a product is a subscription or inapp product type + // instread we create two product lists from the same set of products and use INAPP for one and SUBS for the other + 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()); + } + + // do one query per product type + final QueryProductDetailsParams inappParams = QueryProductDetailsParams.newBuilder().setProductList(inappProductList).build(); + final QueryProductDetailsParams subsParams = QueryProductDetailsParams.newBuilder().setProductList(subsProductList).build(); + billingClient.queryProductDetailsAsync(inappParams, detailsListener); + billingClient.queryProductDetailsAsync(subsParams, detailsListener); } /** @@ -388,21 +473,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 { diff --git a/main/main.gui_script b/main/main.gui_script index dad867f..6252d52 100644 --- a/main/main.gui_script +++ b/main/main.gui_script @@ -3,7 +3,7 @@ local dirtylarry = require "dirtylarry/dirtylarry" local GOLDBARS_SMALL = "com.defold.iap.goldbar.small" local GOLDBARS_MEDIUM = "com.defold.iap.goldbar.medium" local GOLDBARS_LARGE = "com.defold.iap.goldbar.large" -local SUBSCRIPTION = "com.defold.iap.subscription" +local SUBSCRIPTION = "com.defold.iap.subscription.one" local NON_CONSUMABLE = "com.defold.iap.removeads" local items = { @@ -66,6 +66,7 @@ local function list() for k,p in pairs(products) do available_items[p.ident] = p log("Item %s", p.ident) + pprint(p) local button = item_buttons[p.ident] if button then gui.set_color(gui.get_node(button.."/larrylabel"), vmath.vector4(1,1,1,1))