IAP Badger: a unified approach to in-app purchases

(On the non-working version, obviously)

Thanks, Simon :slight_smile:
Here what the relevant console output is … 

MacBook-Pro-3:platform-tools user$ ./adb logcat Corona:v *:s

  • waiting for device -

--------- beginning of system

--------- beginning of main

I/Corona  ( 1388): ERROR: Runtime error

I/Corona  ( 1388): ?:0: attempt to call field ‘purchase’ (a nil value)

I/Corona  ( 1388): stack traceback:

I/Corona  ( 1388): ?: in function ‘purchase’

I/Corona  ( 1388): /Users/user/Dropbox/AppDev/Karate Diary/payment.lua:98: in function ‘actionFN’

I/Corona  ( 1388): /Users/user/Dropbox/AppDev/Karate Diary/com/ponywolf/plugins/button.lua:83: in function ‘method’

I/Corona  ( 1388): /Users/jenkins/slaveroot/workspace/Templates/label/android/platform/resources/init.lua:221: in function </Users/jenkins/slaveroot/workspace/Templates/label/android/platform/resources/init.lua:190>

Do you get any output after you call the init function?  Or is that the entirety of the output from IAP Badger?

Sorry for the late answer. This is what I get, the debug window never shows…:
 

I/Corona (19417): -------------------------------------------------------------------- I/Corona (19417): IAP Badger: init I/Corona (19417): Called with: I/Corona (19417): table: 0xb4b972a0 { I/Corona (19417): [verboseDebugOutput] =\> true I/Corona (19417): [filename] =\> "inventory.txt" I/Corona (19417): [failedListener] =\> function: 0xb4b961a8 I/Corona (19417): [debugMode] =\> true I/Corona (19417): [cancelledListener] =\> function: 0xb4b961a8 I/Corona (19417): [salt] =\> "something tr1cky to gue55!" I/Corona (19417): [catalogue] =\> table: 0xb4b972a0 { I/Corona (19417): [inventoryItems] =\> table: 0xb4b97180 { I/Corona (19417): [yearlyTax] =\> table: 0xb4b97220 { I/Corona (19417): [productType] =\> "consumable" I/Corona (19417): } I/Corona (19417): } I/Corona (19417): [products] =\> table: 0xb4b97180 { I/Corona (19417): [buyYearlyTax] =\> table: 0xb4b971a0 { I/Corona (19417): [onRefund] =\> function: 0xb4b961d8 I/Corona (19417): [onPurchase] =\> function: 0xb4b961c0 I/Corona (19417): [productNames] =\> table: 0xb4b971e0 { I/Corona (19417): [apple] =\> "~~" I/Corona (19417): [google] =\> "~~" I/Corona (19417): } I/Corona (19417): [productType] =\> "consumable" I/Corona (19417): } I/Corona (19417): } I/Corona (19417): } I/Corona (19417): } I/Corona (19417): VerboseDebugOutput set to true I/Corona (19417): IAP Badger: leaving init

I/Corona (19417): IAP Badger: entering purchase I/Corona (19417): Called with productList: I/Corona (19417): buyYearlyTax I/Corona (19417): On simulator/debug mode - faking purchases I/Corona (19417): IAP Badger: leaving purchase

Are your devices standard/vanilla Google Play devices?

The weird thing is, the plug-in is tripping on line 1470… which it shouldn’t ever reach when running on an Android device.  Also, your console output is missing some debug messages that should come out of iap.init function - messages saying you’re running on Android and you’re (probably) using asynchronous Google IAP v3.

All of which leads me to believe that IAP Badger isn’t able to detect that it’s running on a Google Play device.

So, in your code, just before you call iap.init(…), could you try the following:

print (system.getInfo("targetAppStore"))

and tell me what you get.

Also, I’d also be interested to see what the following gives you   after you initialise IAP Badger:

print (iap\_badger.getStoreName())

Looking back, I’ve also noticed that in your Corona build dialog, you don’t have a target app store selected - this is most likely the issue.

Simon :slight_smile:

Hi, support said I should direct you to the problem I am having here https://forums.coronalabs.com/topic/72478-iap-badger-not-working-with-sandbox-test-account/

I’ll pick this up on the other thread.

Simon

Hi there,

Just giving you notice of another bug fix - the plug-in and Github repos have been updated.

Version 16

  • fixed checkProductExists bug (many thanks to bogomazon for spotting this)

Simon

Hi

I use IAP Badger in the version “The simplest possible code for handling a purchase” without any changes. So far, in Google all is working fine and the product is bought.

Question: I deinstall the application on the device and reinstall it again (or I install the App on a new phone with the same google account). If I buy my IAP again, I get from Google the message “Error Transaction failed: Unable to buy item (response: 7:Item Already Owned)” which is in my opinion correct.

The User is not able, do have restored his “owned” puchase.  Is this correct or do I something wrong?

  1. Easy for me would be when the IAP Badger would handle this case like a buy and call the “purchaseListener”-function.

  2. Is there any possibility to handle this error message by myself ?

  3. other suggestion?

Thanks for helping

Hanspeter

Hi Hanspeter,

The solution to this is give the user the option to ‘restore’ items they’ve purchased in the past.  On Google Play, you should automatically run a restore when the application starts (see the iap.restore function).

Using iap.restore is very similar to iap.purchase, with only a couple of minor differences.

For more detailed information about restoring products, check out the ‘product restores’ section of this tutorial.

Sample 2 on this page also gives you sample code for making this happen.

Kind regards,

Simon

Dear Simon

Thanks a lot for helping.

I tried all afternoon to program the “restore fonction” ( using the examples), but without sucsess. I definitly don’t know what I’m doing wrong and I don’t know where to look anymore. I attached my code, maybe you see the mistake right away.

  • variable IAP_Gekauft is the switch I need.

“N”= not bought  --> I try to restore --> if restore not succefull = purchase

“J”= doing nothing

Without the added IAP-Restore line - iap.restore(false, restoreListener, restoreTimeout) - ,  purchase works fine but I get the messge “already bought”.

Thanks in advance for taking the time to look into my problem.

Best regards

Hanspeter

Hi Hanspeter,

Google definitely won’t let you buy a non-consumable product twice, which is why you get the error (correctly).  In my experience, when you call this, your purchase listener still gets called anyway.

The key thing here is - if the user owns the item already, you shouldn’t give them the option of purchasing it.

So, when you’re restore listener gets the message, “the user owns this”, you should remove / disable the menu option to purchase - and unlock access to the IAP.

If you’re having difficulty getting this up and running, I’d:

  • take my code sample (as mentioned above)
  • insert your Google IAP identifier into the code
  • build and sign using your app key (don’t forget to add your Google App’s key into config.lua) - use a version number that is identical to your last build in Google Play’s console
  • run this on your device and test

Also: have you tried running your restore code on the simulator, in debug mode?  This will test the flow of your logic - eliminating the difficulties of integrating with the Google Play store.

Simon

Just for people that are following this with similar problems, this has also come up in another thread, so here’s more info:

[–

… I didn’t realise you were using “android.test.purchased” as your Google Play product ID.

 

Your best bet for testing IAP on a real device, with a real connection to Google play, is to set up your IAP in Google Play console and use a real product identifier.

 

–]

 

Simon

Hi Simon

Thanks for your answer.

Code more or less (without spinner) = your example

When the user purchased a IAP, the program will remember it, until the user install the app for e.g. on a new phone. Thats the moment,  the restore happens.

Purchase works fine in Google Play with real Credit-Card

Not working is: iap.restore( false, restoreListener, restoreTimeout )

  • it doesn’t call the function restoreListener nor restoreTimeout. I don’t know why.

With Debugging I do not see any action with the iap.restore.

I attach the code. Maybe you just see the mistake or whats going wrong.

Sorry, to bother you

Thanks again, Hanspeter

[lua]

    – --------------------------------------
    – Antwort auf die Frage Abbrechen/Kaufen
    function onKauf( event )
        if ( event.action == “clicked” ) then
                local i = event.index
            if ( i == 1 ) then
                   – keine Aktion, da der Benutzer nicht kaufen will
            elseif ( i == 2 ) then
                IAP_Badger() – In APP KAUF Abwicckeln ***
            end
        end
    end

    – --------------
    – Kauf abwickeln
    function IAP_Badger()
        print("**** function IAP_Badger()")
    
        --Called when the relevant app store has completed the purchase
        --Make a record of the purchase using whatever method you like
        function purchaseListener( product )
            IAP_Gekauft = “J”
            writeparameter()
            gekauft_Vorschlag_anzeigen()
        end
        iap.purchase(“removeAds”, purchaseListener)
    end

    – FUNCTION FOR RESTORE IAP
    – ------------------------
    – If this function is called, the app store never replied to the request
    – for a restore (which probably means there were no products to restore)
    function restoreTimeout()
        print ("**** function restoreTimeout()")
            – spinner.hide()
           – Tell user something went wrong
            ShowAlert = native.showAlert(“Restore failed”, “No response from App Store”, {“Okay”})
    end

    – This function is called on a successful restore.
    – If event.firstRestoreCallback is set to true, then this is the first time the function
    – has been called.
    function restoreListener(productNames, event)
        print ("**** function restoreListener(productName, event)")
        – If this is the first transaction…
        if (event.firstRestoreCallback) then           
            – Tell user purchases have been restored
                ShowAlert = native.showAlert(“Restore”, “Your items are being restored”, {“Okay”})
        end
        --Remove ads
        IAP_Gekauft = “J”
        writeparameter()
    end

    – ---------------------------------------------
    – Frage ob In App Kauf ausgeführt werden soll ?
    if IAP_Gekauft == “N” then
        --Load IAP Badger
        iap = require(“plugin.iap_badger”)
        --Create the catalogue
        catalogue = {
        --Information about the product on the app stores
            products = {     
            --removeAds is the product identifier.
                removeAds = {
                    --A list of product names or identifiers specific to apple’s App Store or Google Play.
                    productNames = {
                        apple = “takeit_01”,
                        google = “takeit_01”,
                        amazon = “takeit_01”
                        },
                        --The product type
                        productType = “non-consumable”
                    }
                },
                --Information about how to handle the inventory item
                inventoryItems = {
                    unlock = { productType=“non-consumable” }
                }
            }

        --This table contains all of the options we need to specify in this example program.
        local iapOptions = {
            catalogue=catalogue
            }
        --[[
        – DEBUGING MODE ***
        local iapOptions = {
        --The catalogue generated above
        catalogue=catalogue,
        --The filename in which to save the inventory
        filename=“goodies.txt”,
        --Salt for the hashing algorithm
        salt = “something tr1cky to gue55!”,
        --***New stuff below
        --Debugging mode
        debugMode = true,
        --If on the simulator, pretend to be the following store:
        debugStore = “apple”
        }
        --]]
        --Initialise IAP badger
        iap.init(iapOptions)
        – Restore purchases
        print("*************************1")
        iap.restore( false, restoreListener, restoreTimeout )
        print("*************************2")
        – Ist der IAP_Gekauft immer noch “N”, dann war ein Restore unmöglich
        if IAP_Gekauft == “N” then
            ShowAlert = native.showAlert( K_TextTitel, K_TextLang, { K_TextAbbrechen, K_TextKaufen }, onKauf)
        else
            – IAP gekauft  ***
            gekauft_Vorschlag_anzeigen()
        end
    else
        – IAP gekauft  ***
        gekauft_Vorschlag_anzeigen()
    end

[/lua]

[lua]

– config.lua

application =
{
    content =
    {
        width = 768,
        height = 1024,
        scale = “zoomEven”,
        fps = 60,

        --[[
        imageSuffix =
        {
                ["@2x"] = 2,
                ["@4x"] = 4,
        },
        --]]
    },
    license =
    {
        google =
        {
            key = “MIIBIjANBg xxxxxx”,
        },
    },
}

[/lua]

[lua]

– build settings


– For more information on build.settings, see the Project Build Settings guide at:
https://docs.coronalabs.com/guide/distribution/buildSettings

settings =
{
    orientation =
    {
        – Supported values for orientation:
        – portrait, portraitUpsideDown, landscapeLeft, landscapeRight
        default = “portrait”,
        supported = { “portrait” },
    },

    –
    – Android section
    –
    android =
    {
        usesPermissions =
        {
            “android.permission.INTERNET”,
            “com.android.vending.BILLING”,
        },
    },

    –
    – iOS section
    –
    iphone =
    {
        xcassets = “Images.xcassets”,
        plist =
        {
            UIStatusBarHidden = false,
            UILaunchStoryboardName = “LaunchScreen”,
            NSMotionUsageDescription = “This app would like to access the accelerometer.”,
        },
        
    },

    –
    – Plugins section
    –
    plugins =
    {
        [“plugin.google.iap.v3”] =
        {
            publisherId = “com.coronalabs”,
            supportedPlatforms = { android = true },
        },
        [“plugin.googleAnalytics”] =
        {
            publisherId = “com.coronalabs”
        },
        [“plugin.iap_badger”] =
        {
            publisherId = “uk.co.happymongoose”,
        },
            --Amazon IAP
        [“plugin.amazon.iap”] =
        {
            publisherId = “com.coronalabs”,
            supportedPlatforms = { [“android-kindle”]=true }
        },
    },

    –
    – Project section
    –
    excludeFiles =
    {
        – Exclude unnecessary files for each platform
        all = { “Icon.png”, “Icon-*dpi.png”, “Images.xcassets”, },
        android = { “LaunchScreen.storyboardc”, },
    },
}

[/lua]

Dir Simon

No hurry, we are aproximately one week without any internet connection.

Thanks again.

Best regards Hanspeter

When you get your internet back, could you set verboseDebugOutput to true when you call iap.init (as described here).

Then hook up your device via USB, run adb logcat (Google to see how to use this) and try a restore.

Capture the (relevant output), post it here and I’ll take a look at it.

Simon

Dear Simon

Many thanks for the answer, you are really generous.

I found the error thanks to the Verbose debugging. I had overlooked that the program continues to run after the iap.restore and that the result of iap.restore arrives sometimes later. I rearanged the code after the “restore” into the “restore listener” and everything works fine. After I realised my silly mistake, it is very easy :slight_smile:

Thanks again for the help and the availability of your fantastic IAP Badger library.

Best regards. Haaspeter

Glad it’s working for you - thanks for the kind words about the plug-in

Simon :slight_smile:

For those of you new (or struggling) with IAP, I’ve simplified the example code given the documentation for IAP Badger.

I’ve removed lots of referencing UI elements to try to make the code as simple / followable / copy-and-pastable as possible.

Simon