Amazon store plugin seems to be loading, but store.canLoadProducts returns false. What am I doing wrong?

Blazing the beta trail here…

I read the plugin docs on this site and on Amazon’s re: getting IAP set up for testing.  I’m at the stage where I’m testing it on an actual Kindle HD device, but the store.canLoadProducts is returning false even though the docs here say it should always return true.  My app then fails to load the actual product skus for testing.  Not sure what I’m doing wrong, but here’s what I did to set things up:

  • created IAP skus (consumables) for my app on my Amazon dev. account console.
  • created a JSON file called amazon.sdktester.json that contains the skus and parameters for each iap I want to test.  I put this file in the …/sdcard/ folder on the device
  • I downloaded the sdk tester .apk from Amazon’s SDK and installed it on the device
  • In my app I require the amazon plugin in main.lua, and enable the plugin in build.settings
  • In the store scene in my app I init the store if I detect store.target == “amazon” and if I detect store.isActive load the products skus with store.loadProducts and a callback.  

But since the store.canLoadProducts is returning false it never gets to load the actual skus and instead loads some dummy default ones I set up to test the simulator.

I’m sure at any one of those steps I could have made a dumb mistake or typo but I’ve got debugging print statements throughout and don’t see where I’ve gone wrong.  I’ve exercised and tested my IAP store scene pretty thoroughly and it works on devices/stores for Google Play and Apple so I don’t think the problem is there.  Perhaps I’ve left out a crucial step, or there could be an issue with the plugin itself, but at this point I’m out of ideas.  Anyone having success getting Amazon IAP to work have a clue where I might be going wrong?

Hello HardBoiledIndustries,

Just a clarification, in the first paragraph you mention ‘store.canLoadProducts’ returns false while later on you mention 'store.loadProducts 'is returning false. Which are you checking? ‘store.loadProducts’ has no return value

The field ‘canLoadProducts’ in the Amazon IAP plugin should indeed always equal true. Can you provide some sample code? Are you certain you are calling ‘store = require “plugin.amazon.iap”’  and ‘store.init( storeListener )’ before calling ‘store.loadProducts()’? It looks like the only way ‘store.canLoadProducts’ will equal ‘true’ is if you are using the Corona store API(instead of the Amazon IAP plugin).

 
 

Hi tamkinp,

That was a typo (I fixed it in my original post).  I am indeed checking store.canLoadProducts.

In terms of my code, since I’m doing a cross platform app it is a little messy with if thens to cover all the app stores and slightly different requirements for each, but I’ll snip out some of the relevant parts here:

EDIT:  ugh.  This forum editor/tagging really screwed up the indentation of my code when I used the lua tags.  Had to revert back to normal text for the lua code parts.

from build.settings:

        ["plugin.amazon.iap"] = { publisherId = "com.amazon",}, --comment out when NOT building for Amazon store?         --["CoronaProvider.gameNetwork.google"] = { publisherId = "com.coronalabs", }, -- comment out when building for iOS or Amazon Android, otherwise won't build for iOS. waiting for plugin to be enabled for iOS.  App will force quit on Amazon Android version since it can't find google play store.  How to do leaderboards on Amazon version?     },  

from main.lua:

–in app purchases in Corona

local store = require(“store”) --covers Apple and Google Play stores

if store.target == “amazon” then 

    store = require “plugin.amazon.iap” --covers Android for Amazon store

end

From my IAP scene:  This is a little tough to follow but I’ll snip out the relevant bits in the order it runs:

    --initialize the store
    if Component:testNetworkConnection() == true then
        if store.availableStores.apple then
            currentProductList = appleProductList
            store.init(“apple”, transactionCallback)
            print(“Using Apple’s in-app purchase system.”)
        elseif store.availableStores.google then
            currentProductList = googleProductList
            store.init(“google”, transactionCallback)
            print(“Using Google’s Google Play Android In-App purchase system.”)  
        elseif store.target == “amazon”  then-- amazon IAP plugin doesn’t support store.availableStores API so need to use store.target
            --amazon store
            currentProductList = amazonProductList
            store.init(transactionCallback)
            print(“Using Amazon’s Android In-App purchase system.”)  
        --elseif        then
            --TODO add Nook store IAP functionality

        elseif isSimulator == true then
            currentProductList = simulatorProductList
        else

            print(“In-app purchases is not supported on this system/device.”)
            native.showAlert(“Store purchases are not supported on this system/device.”, 
            { “OK” }, onClose )
        end
    else
        native.showAlert( strings:getString(“alertNoNetworkDetected”), strings:getString(“alertNetworkRequiredForIAP”), { strings:getString(“alertNetworkCloseButton”) }, onClose )
    end
    
timer.performWithDelay (500, setupMyStore) 

after the 500 ms delay, setupMyStore fires:

    function setupMyStore(event)

        

        if store.isActive or isSimulator then

            print("store.canLoadProducts = "…tostring(store.canLoadProducts))

            if (store.canLoadProducts) then 

                – Property “canLoadProducts” indicates that localized product information such as name and price

                – can be retrieved from the store (such as iTunes). Fetch all product info here asynchronously.

                store.loadProducts( currentProductList, loadProductsCall

                

            else

                print(“can’t load products, either google play or simulator, or a problem!”)back )

                print (“After store.loadProducts, waiting for callback”)

if store.canLoadProducts was true then it would continue to my loadProductsCallback function, but it never gets here since it store.canLoadProducts is false for some reason.  But here’s the loadProductsCallback anyway:

    function loadProductsCallback( event )

        local products = event.products

        for i=1, #event.products do 

            print(event.products[i].title) 

            print(event.products[i].description) 

            print(event.products[i].localizedPrice) 

            print(event.products[i].productIdentifier) 

        end 

        for i=1, #event.invalidProducts do 

            print(event.invalidProducts[i]) 

        end 

        

        – save for later use

        validProducts = event.products

        invalidProducts = event.invalidProducts    

        unpackValidProducts()

        

    end

I also have a transactionCallback function that I know functions correctly for the other app stores, so I won’t include it here, but so far it doesn’t matter since the product ids/skus are not being grabbed from the ones I set up for Amazon.

And here is the amazon.sdktester.json file (actual skus removed) I placed on the device inside the …/sdcard/ folder on the device.

{   "...candy101": {     "itemType": "CONSUMABLE",     "price": 0.99,     "title": "Candy Bag",     "description": "Big bag with 1,000 pieces of Candy! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },   "...candy201": {     "itemType": "CONSUMABLE",     "price": 1.99,     "title": "Candy Bowl",     "description": "Large bowl filled with 2,500 pieces of Candy! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy301": {     "itemType": "CONSUMABLE",     "price": 2.99,     "title": "Candy Wagon",     "description": "Huge wagon filled with 7,500 pieces of Candy! Get more and save!  TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy401": {     "itemType": "CONSUMABLE",     "price": 3.99,     "title": "Candy Barrel",     "description": "Barrel bursting with 7,500 pieces of Candy!  Get more and save! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy501": {     "itemType": "CONSUMABLE",     "price": 4.99,     "title": "Candy Crate",     "description": "Massive crate filled with 10,000 pieces of Candy.  A steal! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy601": {     "itemType": "CONSUMABLE",     "price": 9.99,     "title": "Candy Crane",     "description": "Construction crane spilling with 25,000 pieces of Candy!  Best deal! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   } }  

At first I thought perhaps the delayed setupMyStore function was getting called to early, before the store.init fired, but checking the logcat debugging prints I see it is getting called after.  Here’s part of the logcat log with my debugging statements:

I/Corona  ( 4827): Output string ::     Using Amazon's Android In-App purchase system. I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     GameStats:getDisplayAdsState() = false I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     removeBannerAd I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     removing cookie rotate listener I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     store.canLoadProducts = false I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     can't load products (google play or simulator... I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Loading product list I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Product list loaded I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Country: US I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Found 6 valid items   

Thanks for taking a look.

Hello HardBoiledIndustries,

Are you certain ‘store.target == “amazon”’ and that  ‘store = require “plugin.amazon.iap”’ are being executed? It may be helpful to have a debugging statement right after ‘require “plugin.amazon.iap”’ to ensure this line is being executed. 

Are you selecting ‘Amazon’ for ‘Target App Store’ when building the apk? This is what causes ‘store.target’ to equal ‘amazon’ in your first code snippet from your main.lua.

-Preston

Thanks Preston, you were correct.  I had used this code in my main.lua from the Corona sample on the Amazon Plugin docs

store = require "store" if store.target == "amazon" or system.getInfo("environment") == "simulator" then store = require "plugin.amazon.iap"   

but it looks like store = require “plugin.amazon.iap” never gets executed even though store.target is set to “amazon” when I build.   I modified the code to this:

if device.isKindle then      store = require "plugin.amazon.iap" --covers Android for Amazon store      print("using amazon iap plugin") else      store = require("store") --covers Apple and Google Play stores      print("using regular corona iap") end  

And now the skus from the amazon.sdktester.json are loading!

FYI, I have a module I require prior to this called “device.lua” where I discover if I’m running on a Kindle or not, so device.isKindle will be true if I’m running on a Kindle and I can then load the correct store.   This is actually not a good solution since I want the amazon IAP to work on any android device where the user has used the Kindle store to purchase my app, not just Kindle Fires.  I need a way to detect the store.target _before _I require store so I know which store to require!  Catch 22.

EDIT:  oops.  Forgot about system.getInfo(“targetAppStore”)  That will get me the target app store.  so this works well:

if system.getInfo("targetAppStore") == "amazon" then     store = require "plugin.amazon.iap" --covers Android for Amazon store     print("using amazon iap plugin") else     store = require("store") --covers Apple and Google Play stores     print("using regular corona iap") end  

Thanks for your help.

Beta test update…

I seem to have got it all working.  I get the Amazon branded IAP popup for each of my test skus, and purchasing any of the IAP works like the real deal in my app.  

A couple minor issues I noticed:

  • If the user cancels the purchase by closing the IAP popup, the event.transaction.state seems to be “failed” rather than “cancelled”.   
  • The valid products table is in alphabetical order based on the title of the sku.  This seems to be set automatically in the Amazon IAP dev. console with no way to change it.  Google Play store lets me change the display order of the products (In my case I want to show them in price ascending order)

But otherwise seems like IAP for Amazon plugin is working well.

Hello HardBoiledIndustries,

I am happy you found the issue and a solution. ‘store.target’ is working correctly in my testing, but this is a Corona feature so I am unsure as to what your issue could have been.

As for your issues:

  1. This is in the documentation. The Amazon IAP SDK does not differentiate between  ‘cancelled’ and ‘failed’ purchases

  2. Unfortunately the Amazon IAP SDK  does not have this same functionality.

-Preston

@hardboiledindustries It’s weird that the code (if store.target == “amazon” then…) isn’t working for you. I’ve used it exclusively throughout my code and it works on all my test devices. I’m on Mac OSX. The only reason I can think of is if during the build of the APK that Amazon wasn’t selected in the dialog, but you say you’ve checked that so I’m a bit stumped.

Hmmmm,  that is weird.  I include the plugin in main.lua but I don’t call store.init until later in another module, so maybe my earlier problems were due to some kind of scoping issue?  Or perhaps I just did something dumb and fixed it without realizing it.  (it was a late night coding session).

It’s sounds like a scoping issue… but I know how late-night coding sessions tend to mess with logic :slight_smile:

Hello HardBoiledIndustries,

Just a clarification, in the first paragraph you mention ‘store.canLoadProducts’ returns false while later on you mention 'store.loadProducts 'is returning false. Which are you checking? ‘store.loadProducts’ has no return value

The field ‘canLoadProducts’ in the Amazon IAP plugin should indeed always equal true. Can you provide some sample code? Are you certain you are calling ‘store = require “plugin.amazon.iap”’  and ‘store.init( storeListener )’ before calling ‘store.loadProducts()’? It looks like the only way ‘store.canLoadProducts’ will equal ‘true’ is if you are using the Corona store API(instead of the Amazon IAP plugin).

 
 

Hi tamkinp,

That was a typo (I fixed it in my original post).  I am indeed checking store.canLoadProducts.

In terms of my code, since I’m doing a cross platform app it is a little messy with if thens to cover all the app stores and slightly different requirements for each, but I’ll snip out some of the relevant parts here:

EDIT:  ugh.  This forum editor/tagging really screwed up the indentation of my code when I used the lua tags.  Had to revert back to normal text for the lua code parts.

from build.settings:

        ["plugin.amazon.iap"] = { publisherId = "com.amazon",}, --comment out when NOT building for Amazon store?         --["CoronaProvider.gameNetwork.google"] = { publisherId = "com.coronalabs", }, -- comment out when building for iOS or Amazon Android, otherwise won't build for iOS. waiting for plugin to be enabled for iOS.  App will force quit on Amazon Android version since it can't find google play store.  How to do leaderboards on Amazon version?     },  

from main.lua:

–in app purchases in Corona

local store = require(“store”) --covers Apple and Google Play stores

if store.target == “amazon” then 

    store = require “plugin.amazon.iap” --covers Android for Amazon store

end

From my IAP scene:  This is a little tough to follow but I’ll snip out the relevant bits in the order it runs:

    --initialize the store
    if Component:testNetworkConnection() == true then
        if store.availableStores.apple then
            currentProductList = appleProductList
            store.init(“apple”, transactionCallback)
            print(“Using Apple’s in-app purchase system.”)
        elseif store.availableStores.google then
            currentProductList = googleProductList
            store.init(“google”, transactionCallback)
            print(“Using Google’s Google Play Android In-App purchase system.”)  
        elseif store.target == “amazon”  then-- amazon IAP plugin doesn’t support store.availableStores API so need to use store.target
            --amazon store
            currentProductList = amazonProductList
            store.init(transactionCallback)
            print(“Using Amazon’s Android In-App purchase system.”)  
        --elseif        then
            --TODO add Nook store IAP functionality

        elseif isSimulator == true then
            currentProductList = simulatorProductList
        else

            print(“In-app purchases is not supported on this system/device.”)
            native.showAlert(“Store purchases are not supported on this system/device.”, 
            { “OK” }, onClose )
        end
    else
        native.showAlert( strings:getString(“alertNoNetworkDetected”), strings:getString(“alertNetworkRequiredForIAP”), { strings:getString(“alertNetworkCloseButton”) }, onClose )
    end
    
timer.performWithDelay (500, setupMyStore) 

after the 500 ms delay, setupMyStore fires:

    function setupMyStore(event)

        

        if store.isActive or isSimulator then

            print("store.canLoadProducts = "…tostring(store.canLoadProducts))

            if (store.canLoadProducts) then 

                – Property “canLoadProducts” indicates that localized product information such as name and price

                – can be retrieved from the store (such as iTunes). Fetch all product info here asynchronously.

                store.loadProducts( currentProductList, loadProductsCall

                

            else

                print(“can’t load products, either google play or simulator, or a problem!”)back )

                print (“After store.loadProducts, waiting for callback”)

if store.canLoadProducts was true then it would continue to my loadProductsCallback function, but it never gets here since it store.canLoadProducts is false for some reason.  But here’s the loadProductsCallback anyway:

    function loadProductsCallback( event )

        local products = event.products

        for i=1, #event.products do 

            print(event.products[i].title) 

            print(event.products[i].description) 

            print(event.products[i].localizedPrice) 

            print(event.products[i].productIdentifier) 

        end 

        for i=1, #event.invalidProducts do 

            print(event.invalidProducts[i]) 

        end 

        

        – save for later use

        validProducts = event.products

        invalidProducts = event.invalidProducts    

        unpackValidProducts()

        

    end

I also have a transactionCallback function that I know functions correctly for the other app stores, so I won’t include it here, but so far it doesn’t matter since the product ids/skus are not being grabbed from the ones I set up for Amazon.

And here is the amazon.sdktester.json file (actual skus removed) I placed on the device inside the …/sdcard/ folder on the device.

{   "...candy101": {     "itemType": "CONSUMABLE",     "price": 0.99,     "title": "Candy Bag",     "description": "Big bag with 1,000 pieces of Candy! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },   "...candy201": {     "itemType": "CONSUMABLE",     "price": 1.99,     "title": "Candy Bowl",     "description": "Large bowl filled with 2,500 pieces of Candy! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy301": {     "itemType": "CONSUMABLE",     "price": 2.99,     "title": "Candy Wagon",     "description": "Huge wagon filled with 7,500 pieces of Candy! Get more and save!  TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy401": {     "itemType": "CONSUMABLE",     "price": 3.99,     "title": "Candy Barrel",     "description": "Barrel bursting with 7,500 pieces of Candy!  Get more and save! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy501": {     "itemType": "CONSUMABLE",     "price": 4.99,     "title": "Candy Crate",     "description": "Massive crate filled with 10,000 pieces of Candy.  A steal! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   },     "...candy601": {     "itemType": "CONSUMABLE",     "price": 9.99,     "title": "Candy Crane",     "description": "Construction crane spilling with 25,000 pieces of Candy!  Best deal! TEST AMAZON",     "smallIconUrl": "http://www.hardboiledindustries.com/page4/files/stacks\_image\_19.png"   } }  

At first I thought perhaps the delayed setupMyStore function was getting called to early, before the store.init fired, but checking the logcat debugging prints I see it is getting called after.  Here’s part of the logcat log with my debugging statements:

I/Corona  ( 4827): Output string ::     Using Amazon's Android In-App purchase system. I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     GameStats:getDisplayAdsState() = false I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     removeBannerAd I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     removing cookie rotate listener I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     store.canLoadProducts = false I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     can't load products (google play or simulator... I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Loading product list I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Product list loaded I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Country: US I/Corona  ( 4827):  I/Corona  ( 4827): Output string ::     Found 6 valid items   

Thanks for taking a look.

Hello HardBoiledIndustries,

Are you certain ‘store.target == “amazon”’ and that  ‘store = require “plugin.amazon.iap”’ are being executed? It may be helpful to have a debugging statement right after ‘require “plugin.amazon.iap”’ to ensure this line is being executed. 

Are you selecting ‘Amazon’ for ‘Target App Store’ when building the apk? This is what causes ‘store.target’ to equal ‘amazon’ in your first code snippet from your main.lua.

-Preston

Thanks Preston, you were correct.  I had used this code in my main.lua from the Corona sample on the Amazon Plugin docs

store = require "store" if store.target == "amazon" or system.getInfo("environment") == "simulator" then store = require "plugin.amazon.iap"   

but it looks like store = require “plugin.amazon.iap” never gets executed even though store.target is set to “amazon” when I build.   I modified the code to this:

if device.isKindle then      store = require "plugin.amazon.iap" --covers Android for Amazon store      print("using amazon iap plugin") else      store = require("store") --covers Apple and Google Play stores      print("using regular corona iap") end  

And now the skus from the amazon.sdktester.json are loading!

FYI, I have a module I require prior to this called “device.lua” where I discover if I’m running on a Kindle or not, so device.isKindle will be true if I’m running on a Kindle and I can then load the correct store.   This is actually not a good solution since I want the amazon IAP to work on any android device where the user has used the Kindle store to purchase my app, not just Kindle Fires.  I need a way to detect the store.target _before _I require store so I know which store to require!  Catch 22.

EDIT:  oops.  Forgot about system.getInfo(“targetAppStore”)  That will get me the target app store.  so this works well:

if system.getInfo("targetAppStore") == "amazon" then     store = require "plugin.amazon.iap" --covers Android for Amazon store     print("using amazon iap plugin") else     store = require("store") --covers Apple and Google Play stores     print("using regular corona iap") end  

Thanks for your help.

Beta test update…

I seem to have got it all working.  I get the Amazon branded IAP popup for each of my test skus, and purchasing any of the IAP works like the real deal in my app.  

A couple minor issues I noticed:

  • If the user cancels the purchase by closing the IAP popup, the event.transaction.state seems to be “failed” rather than “cancelled”.   
  • The valid products table is in alphabetical order based on the title of the sku.  This seems to be set automatically in the Amazon IAP dev. console with no way to change it.  Google Play store lets me change the display order of the products (In my case I want to show them in price ascending order)

But otherwise seems like IAP for Amazon plugin is working well.

Hello HardBoiledIndustries,

I am happy you found the issue and a solution. ‘store.target’ is working correctly in my testing, but this is a Corona feature so I am unsure as to what your issue could have been.

As for your issues:

  1. This is in the documentation. The Amazon IAP SDK does not differentiate between  ‘cancelled’ and ‘failed’ purchases

  2. Unfortunately the Amazon IAP SDK  does not have this same functionality.

-Preston

@hardboiledindustries It’s weird that the code (if store.target == “amazon” then…) isn’t working for you. I’ve used it exclusively throughout my code and it works on all my test devices. I’m on Mac OSX. The only reason I can think of is if during the build of the APK that Amazon wasn’t selected in the dialog, but you say you’ve checked that so I’m a bit stumped.

Hmmmm,  that is weird.  I include the plugin in main.lua but I don’t call store.init until later in another module, so maybe my earlier problems were due to some kind of scoping issue?  Or perhaps I just did something dumb and fixed it without realizing it.  (it was a late night coding session).

It’s sounds like a scoping issue… but I know how late-night coding sessions tend to mess with logic :slight_smile: