IAP Badger: a unified approach to in-app purchases

Hey guys  :slight_smile:

I might be wrong but it seems that there is some kind of bug or delay in  purchaseListener  function, that lets me to buy nonconsumable product twice in debug test mode…

Here is my code:

function purchaseListener(product, event ) -- print("DO 1")      if(product == "removeAds") then       -- print("DO 2")     --Give the user a message saying the purchase was successful     removeAdsIcon:removeEventListener ( "tap", wasTapped )     removeAdsIcon:removeEventListener("tap", buyUnlock)     removeAdsIcon.alpha = 0     adsYouPurchased.alpha = 1     adsYouPurchased:addEventListener("tap", wasTapped)     --Save the inventory change     iap.saveInventory()     native.showAlert("Info", "Your purchase was successful", {"Okay"})     return true   elseif(product == "buyLevel2") then       -- print("DO 3")       level2:removeEventListener("tap", buyUnlock)       -- print("DO 5")       level2:setSequence("blink")       level2:play()       iap.saveInventory()       level2:addEventListener("tap", goSomewhere)       native.showAlert("Info", "Your purchase was successful", {"Okay"})    end end                buyUnlock=function(event)        --Tell IAP to initiate a purchase        iap.purchase(event.target.product, purchaseListener)     end

Now what happens here:

  • removeAdsIcon is a png (display.newImageRect)

  • level2  is NOT a png, it is a Sprite (display.newSprite)

When I test above code in Windows Corona Simulator 2015.2731 (debug mode) it goes well for buying removeAdsIcon (i.e. you can click only once to remove Ads (because this line is executed: “removeAdsIcon:removeEventListener(“tap”, buyUnlock)” )

The problem is that you can click TWICE to buy level2 (I repeat level2 is a Sprite :slight_smile: )

My question is, why “level2:removeEventListener(“tap”, buyUnlock)” line is executed only after two times clicking on level2 ?

1. So I click first time, and level2 native Text view pops up for buying level2 (I buy it and level2 starts to blink).

2.  Second time when I click native Text view pops up for buying level2 (again!), and at the same time I go to level 2 (remember that it was opened/started to blink after first click on it - which is OK !) ?

Waiting your reply.

Many thanks.

Ivan 

Just to clarify, your actions and console output from your debug statements would be:

[Tap level2]

DO 1 

DO 3 

DO 5

[pop up appears, level2 blinks]

[Tap level2 again]

DO 1

DO 3

DO 5

[pop up appears, level2 starts at the same time]

It looks like this:

[Tap level2 - I click successful]

DO 1 

DO 3 

DO 5

[pop up appears, level2 blinks]

[Tap level2 again]

DO 1

DO 3

Runtime error

[pop up appears, level2 starts at the same time]

 

Pop up window offering buying level2 stays when level 2 is shown.

And I have a new pop up window showing Corona error:

 

"attempt to call method ‘removeEventListener’ (a nil value)

stace traceback: menu.lua:276: in function 'postStoreTransactionCallbackListener’

iap_badger.lua:1141: in function 'storeTransactionCallback’

iap_badger.lua:1556: in function <iap_badger.lua:1529>

 

I have even tried to create separate function for following code (that is outside of ‘function purchaseListener’, and I

call it from within purchaseListener):

level2:removeEventListener("tap", buyUnlock) &nbsp;-- print("DO 5") level2:setSequence("blink") level2:play() level2:addEventListener("tap", goSomewhere)

Surprisingly buying “removeAds” works like a charm.

Buying level2 doesn`t work… 

Buying level3 also doesn`t work (tried this morning)… 

Thank you.

Ivan

Could you show me the code where you initiate your level2 sprite and attach the listener?  It looks like there may an additional event listener acting on the object.

Following code is  below above mentioned code.

local sheetInfo_level2  = require(“level2”)

local myImageSheet_level2  = graphics.newImageSheet( “images/level2.png”, sheetInfo_level2:getSheet() )

local sequenceData_level2  = {

            {

    name=“blink”,                                  – name of the animation

    sheet=myImageSheet_level2,                           – the image sheet

    start=sheetInfo_level2:getFrameIndex(“level21”), – first frame

    count=2,                                      – number of frames

    time=300,                                    – speed

    loopCount= 0                                 – repeat

            },            

}

level2 = display.newSprite( grp, myImageSheet_level2, sequenceData_level2 )

level2.x = centerX + (screenWidth*0.25)

level2.y = level1.y

level2.id=“level2”

level2.product ="buyLevel2"

level2:addEventListener(“tap”, buyUnlock)

grp:insert(level2) – grp is display.newGroup called in ‘scene:create’

Looks like you cannot easily remove tap listener because purchase function is ongoing or something…

Sometimes if I am really fast and I click  successful  second time before game proceeds to level2 (I get no errors then). And then I go back from level2 to main,  and third time when I click listener is removed (everything works OK).

I think that is because I am calling this in menu.lua ‘scene:show’ :

if (iap.getInventoryValue("buyThatLevel2")==true) then level2:removeEventListener("tap", buyUnlock) level2:setSequence("blink") level2:play() level2:addEventListener("tap", goSomewhere) level2:addEventListener ( "tap", wasTapped ) else &nbsp; &nbsp; --Otherwise add a tap listener to the button that unlocks the game &nbsp; &nbsp; level2:addEventListener("tap", buyUnlock) end

So basically everything works OK only if you get a chance to read the inventory during menu.lua initialization.

************************************************************************

NEW TEST THAT PASSES:

  1. Cleared Corona Sandbox.

  2. Bought level2.

  3. Did not clicked on level2 (second time).

  4. Went to options.lua (which is basically new scene/lua file - i.e. menu.lua was cleared).

  5. Went back to menu.lua, and level2 is purchased (no pop up window when I click on it the second time).

************************************************************************

But when I try my first test (staying all the time in menu.lua), level2 is offered 2 times - original problem.

What am I missing here?

I can’t double check from where I am at the moment, but I am 99% sure that, for some reason, your buyUnlock tap listener is being called twice when you tap on your level2 object.  (Which is why the code is creating an exception on line 276; the first time your purchase listener is called, your listener successfully removes the tap listener for buyUnlock; the second time, it attempts to remove the same listener, which no longer exists, raising the exception.)

An easy test for this would be to include two print statements in your buyUnlock function, alongside you other debug prints:

function buyUnlock(event) &nbsp; &nbsp;print("Entering buyUnlock") &nbsp; &nbsp;iap.purchase(event.target.product, purchaseListener) &nbsp; &nbsp;print("Leaving buyUnlock") end

Try the change and see what console output you get now.

You were right!

This code was redundant (’ level2:addEventListener(“tap”, buyUnlock) '), I deleted it and now everything is fine.

I will put you on Credits page.

Thank you!

I have two last questions:

  1. Is your code enough or I should add additional separate functions for:
  • no internet connection

  • lost internet connection

  • unable to connect to the store or timeout ?

  1. Does Apple/Google mandate “Restore Purchases” button, or I could restore them automatically when user is online and enters menu.lua (of course if he/she has deleted app previously) ?

Many, many thanks!  :slight_smile:

Ivan

IAP Badger is just a wrapper class for Corona’s native store code, which I believe handles the above situations for you.  I’ve not tried it myself, but I’m guessing any attempts to make a purchase without a connection will just come back as a ‘fail’ event; and when you initiate a restore purchases call, you can (and have to) specify a timeout function anyway, which is initiated after a set period of time whether there is an internet connection or not.

If anyone knows differently to this, I’d be happy to update the code base to reflect this.

Hope that helps / makes sense.

Many thanks for the credit  :slight_smile:

Happymongoose, thanks for advice!

Everything was correct with code, but license key was not correct, because I missed brackets!

I installed adb logcat as you said and saw that licensing is not working. USB debugging is really cool! :slight_smile:

I believe, there is no way to find it out without adb logcat.

Of course adb logcat Corona:v *:s (I found it on corona forums) is the best way for corona debugging.

One more dumb question.

User makes one purchase to unlock content. What is best practice to “keep in mind” that purchase- should I need to check if user has bought it through google store next and every time when user lounches app? Or save some data on device?

In practice, using IAP Badger’s inventory should be enough to cope with most situations, assuming you are saving the inventory after purchases etc.  The flow I follow is:

* when the app is launched (not restored), on Android devices I initiate a product restore (Apple tell you explicitly not to do this).

* I offer a product restore button in the settings menu on Android and iOS

The above will handle non-consumables.  In terms of consumables, it is your responsibility to store purchases on the device, as you’ll receive no information from the App Stores about consumables during the restore process.  Again, IAP Badger’s inventory is designed to handle this for you.

Hey HappyMongoose,

Thanks for your corona file, its been a God send. I am having a slight problem in my apple app. I am able to purchase my product when i click my buy button and if I delete the app and re-download the app, when I click the buy button again, the app restores my purchase. However Apple rejected my app because when I re-download it, the buy button needs to be pressed again (they’re so nitpicky smh). So In order for the app to be approved, I need to make a conditional statement that automatically changes the text on my “buy” button to a “restore” as soon as I enter the app. Can you explain in detail how I can do this? Thank you

Hi Benyemi,

I hope you are not right because my app (under review) will be rejected too!  :mellow:

As I understand:

  • Apple suggests that you DO NOT check server purchases condition on each app launch!

  • So what you suggested (Buy/Restore) is not possible without checking purchases condition on each app launch

  • Checking purchases condition on each app launch would annoy users (they would have to sign in each time app launches)

I think you have to have Restore Purchases button somewhere in your app (like in Options or Settings).

When user clicks it all Buy buttons should disappear if transaction was successful, and if user indeed purchased that particular item

That should be sufficient I think…  :o   :smiley:

Hi Benyemi,

My understanding is that ivan888 is correct.  Apple explicitly say not to keep checking their servers for IAP information when you launch your app.  You should have a separate Restore Purchases button somewhere in your app so users can recover non-consumables.

Google recommend the opposite and say you should initiate a restore purchases call every time the app launches.

Different strokes…

Apple does not want the user to experience a login/purchase prompt when the app starts. Just put a Restore Purchases button somewhere in your app and you will be good.

Rob

sir, a quetion, how to get the item price? thx.

Hi Azhar,
Item price is displayed to the customer when he/she attemtps to purchase the product.
You set the item price on itunnes connect when seting up your app.
Item price depends on App Store location (US, Germany,…).

Maybe IAP badger knows more…

Hi Azhar,

ivan888 is correct in that when you attempt to make a purchase, iOS/Android will tell the user how much they will be charged.

However, IAP Badger can tell you the price of all the items in your catalogue in the users local currency, as well as a localised IAP product title and description.  It pulls the information from the console on the relevant app store (ie. iTunes Connect or Google Play Console).  At the moment, you cannot access the information on Amazon’s console.  This means you can display IAP pricing information for the user before they hit the purchase button (which is fair, really - they will want to know how much something costs before committing to payment).

To do this, you make a call to iap.loadProducts().  You should do this as soon as possible in your app’s execution, because the function can take several seconds (or longer) to contact the relevant server and download the product information.  Once this is complete, you call iap.loadProductsCatalogue() to get a table containing all the relevant information.  If the information is not yet back from the server, the function will return an empty {} table.

For debug purposes, you can hard code some values for your IAP products’ pricing and descriptions to test you UI code.

Sample code for getting pricing information can be found in IAP Badger’s documentation for iap.loadProducts()and iap.getLoadProductsCatalogue().

I know, what I mean is the value of price so I can display the price before user click the trigger. Thx. I will try :D.

I got an error the variable is nil. How to anticipate the delay? Thx