Quick Links: Gideros Home | Download Gideros | Developer Guide
How to add Android InApp purchases
  • Radiesel +1 -1 (+12 / -3 )
    Member
    Hi folks,

    I’d like to share my experience with Android InApp purchasement. Since the following is working for me, users have asked for s Step-by-Step instructions. I’ll try me very best here...

    First of all I have to say, that Android development is my weaker part, so if I did something wrong, please correct me.

    Gideros documentation on googlebilling is not fully complete enough as Atilim stated himself. Or well, documentation is complete, but in my opinion there are missing samples...

    Here’s the basic code to handle InApp purchases. Very simple code and very similar to StoreKit.

    Step 1: Add the folling code
    require "googlebilling"
     
    --------------------------------------------------
    -- GOOGLE Billing
    -- @param event
    -- @return
    --------------------------------------------------
    local function onRequestPurchaseComplete(event)
    if (event.responseCode == GoogleBilling.OK) then
    -- transaction has been sent to google (thanks Atilim)
    -- don't unlock items here
    else
    local msg = "purchase failed"
    if (event.responseCode == GoogleBilling.USER_CANCELED) then
    msg = "GoogleBilling.USER_CANCELED"
    end
    if (event.responseCode == GoogleBilling.SERVICE_UNAVAILABLE) then
    msg = "GoogleBilling.SERVICE_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.BILLING_UNAVAILABLE) then
    msg = "GoogleBilling.BILLING_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.ITEM_UNAVAILABLE) then
    msg = "GoogleBilling.ITEM_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.DEVELOPER_ERROR) then
    msg = "GoogleBilling.DEVELOPER_ERROR"
    end
    if (event.responseCode == GoogleBilling.ERROR) then
    msg = "GoogleBilling.ERROR"
    end
     
    print(msg)
    end
    end
     
    --------------------------------------------------
    -- GOOGLE Billing
    -- @param event
    -- @return
    --------------------------------------------------
    local function onPurchaseStateChange(event)
    if (event.purchaseState == GoogleBilling.CANCELED) then
    if (event.productId == "your_product_001") then
    -- lock or don't unlock
    elseif (event.productId == "your_product_002") then
    -- lock or don't unlock
    elseif (event.productId == "android.test.purchased") then
    -- lock or don't unlock
    end
    elseif (event.purchaseState == GoogleBilling.PURCHASED) then
    if (event.productId == "your_product_001") then
    -- unlock
    -- show Message, item bought
    elseif (event.productId == "your_product_002") then
    -- unlock
    -- show Message, item bought
    elseif (event.productId == "android.test.purchased") then
    -- unlock
    -- show Message, item bought
    end
    elseif (event.purchaseState == GoogleBilling.REFUNDED) then
    if (event.productId == "your_product_001") then
    -- lock or don't unlock
    elseif (event.productId == "your_product_002") then
    -- lock or don't unlock
    elseif (event.productId == "android.test.purchased") then
    -- lock or don't unlock
    end
    elseif (event.purchaseState == GoogleBilling.EXPIRED) then
    -- for subscriptions
    else
    -- unknown state
    end
     
    googlebilling:confirmNotification(event.notificationId)
    end
     
    -- maybe it better to modify original publicKey by XOR or split into parts as google suggests
    googlebilling:setPublicKey("yourverylongpublickey")
    googlebilling:addEventListener(Event.REQUEST_PURCHASE_COMPLETE, onRequestPurchaseComplete)
    googlebilling:addEventListener(Event.PURCHASE_STATE_CHANGE, onPurchaseStateChange)


    Somewhere in your code add an EventListener e.g. to a button (I've used the "button" class here from the forum)
     
    local buybutton = Button.new(Bitmap.new(Texture.new("assets/gfx/buyanitem.png", true)))
    stage:addChild(buybutton)
    buybutton:addEventListener("click",
    function()
    googlebilling:requestPurchase("your_product_001")
    end)


    To test that the basic purchasing functionallity is working correct, you can simulate purchasing by using static product IDs. Simply instead of

    googlebilling:requestPurchase("your_product_001")

    you can use

    googlebilling:requestPurchase("android.test.purchased")

    to simulate purchasing an item. No credit card need and there is no real payment made, but google sends the messages like you would have purchased a valid item. To test a canceled item, simply use

    googlebilling:requestPurchase("android.test.canceled")

    Both testproducts partly (!) work in gideros player on Android. Real products don't! You'll have to setup real products in GooglePlay and also have to create a testaccount which you have to use as the primary account on your android device. Either factory reset your device to the testaccount or use Android 4.2 which supports multiple logins on one device. In GooglePlay set the testaccounts email addresses under "Settings" - "Account details" - "GMail accounts with test-access" (if that is not 100% correct, it's because I have german GooglePlay Developer Console).

    Step 2: Export your project as Android project and make sure your AndroidManifest.xml has the following line:
    <uses-permission android:name="com.android.vending.BILLING"></uses-permission>


    Step 3: Create a signed .apk of your project

    Step 4: Upload to GooglePlay, don't publish/release the .apk, but you might want to fill in all the required fields

    Step 5: Within "InApp-Products", create your InApp products, e.g. "your_product_001", etc...

    Step 6: Set the InApp products to "active", otherwise you App will not have access to the items

    Step 7: Install your signed .apk on the device with your testaccount and run it

    As far as I recall that's basically all you have to do. It should work now. "Great" thing, it isn't...

    Believe it or not, you have to wait some hours until google sets the items to a purchasable state.

    I tried several times and got messages like
    - "Application is not configured to make InApp purchases"
    - "Retrieving data failed"
    - "etc.etc."

    Without changing anything error messages changed all the time and all of a sudden, it worked....

    As the final test you can purchase one item using the testaccount with a valid credit card. You can refund the amount of your sold product in "Google Checkout" (don't know the english version here).

    I hope I did not forget anything, if you have the feeling there is something missing, please let me know and I will modify/enhance the Step-by-Step instructions

    best regards
    Michael

    PS: As a hint, don't try to make intensive tests in gideros player, even testproducts above don't work completely. You should create an .apk and first test using googlebilling:requestPurchase("android.test.purchased"). If that works, you can enter real product IDs.
  • I think you should alert the user if something goes wrong.
  • atilimatilim +1 -1
    Maintainer
    that's a great tutorial. thank you.
  • Radiesel +1 -1 (+1 / -0 )
    Member
    @mertocan, not 100% sure if it's necessary - google throws a lot of dialogs itself ;-) But however, usually makes sense....

    Likes: mertocan

  • Step-by-Step instructions are not fully correct. I will adjust soon, so please be careful using the above code... Stay tuned...
  • I have modified the code which works pretty good for me. I also added some more comments/hints... Hope it helps!
  • It doesn't work for me. I am purchasing the item but onPurchaseStateChange function never gets executed.
  • Hello, I also have the same problem as mertocan, all work fine except onPurchaseStateChange is never called at all
  • But in the onRequestPurchaseComplete(event) you receive GoogleBilling.OK?
    Then maybe something is wrong on the Google Play setup part
  • Yes, the onRequestPurchaseComplete do receive GoogleBilling.OK

    We have done the test with android.test.purchased, this dummy test item result available, I do see all the Android system popups, also the "purchase successful, your item will appear soon" popup (it appear over a black screen). after this our Gideros app reappear with the correct screen, the only problem is the onPurchaseStateChange callback is never called...
  • In my Google Play Developer Console I see a message that tells:

    On December 5th we begin to send third party apps a new order ID format within in-app billing notifications. Google Order Number will be replaced with Merchant Order Number. Developers using the Checkout XML V2 Notification API will no longer be able to use it. Developers can continue to use the Checkout XML V2 Order Report API since that report supports both Merchant and Google Order Numbers.


    Maybe this have to do something with it?
  • That's why I wrote, in gideros player it only works partly (!). I had the same problems, but it works on a real device. Please install the App on a real device and use the "android.test.purchased" item. You might want to add an altertBox to make sure your function (onPurchaseStateChange()) got called.

    Michael
  • It doesnt work on real device also
  • Hello, same problem here, we have tested on a device using android.text.purchased, onPurchaseStateChange still never called... any other suggestion?
  • Hmm, just realized, that Step 2 above didn't show the android manifest information. I have modified that. I will try again workflow when I am back home.

    @nat12, hope "text" is a type, should be "test".

    Did you get any error messages?

    Michael
  • Just tested, still working without problems...

    If you call

    googlebilling:requestPurchase("android.test.purchase")

    first thing that shows up is a dialog showing "Sample Title",etc. There's a button "accept & buy", pay with "VISA xxx-FAKE". When you click on "accept & buy" a small hint shows up "your product will appear soon" (or similar). It takes up to 10 seconds on my device until the function onPurchaseStateChange() gets called. But it definitely gets called, because after about 10 scones, my test-alert shows up.

    If I test with real products, onPurchaseStateChange() gets called much sooner. Basically there is no real delay like I have when testing with the test product.

    One more thing, I am using a separate gmail account for testing purchasing products. Sooner or later you will have to set up such an account to test real products. To fully test real products you even have to add your credit card number for the new test-account. I will test if I am running into the same problem when using my real account instead of my test-account.

    If you're still stuck, you might want to send me your code, I will try to figure out what the problem is.

    regards!
    Michael
  • tested with my primary account, working good, too. test-alert dialog (within onPurchaseStateChange()) showed up sooner this time. Maybe it's some sort of random time period after google sends the message ;-)
  • Which gideros version do you use? I do same thing with you but onPurchaseStateChange() function never gets called. I have no idea how to fix it.
  • Yes, "text.purchased" is a typo in the message, in the code we have "test.purchased".
    I do see all the "Sample Title/accept&buy/your product will appear soon" system messages.
    I also see the lua alertBox from onRequestPurchaseComplete with responseCode==OK.
    No matter how much I do wait, onPurchaseStateChange alertBox never appear.

    We do use Gideros 2012.09.2 (we will try 9.5 soon), our testing device is a Samsung GT-i9000 with Android 2.2

    I have noticed these warning in the Eclipse problems window:
    Class is a raw type. References to generic type Class should be parameterized PurchaseObserver.java /Crash_Dummy_LITE/src/com/giderosmobile/android/plugins/googlebilling line 35 Java Problem
    The import android.os.Handler is never used PurchaseObserver.java /Crash_Dummy_LITE/src/com/giderosmobile/android/plugins/googlebilling line 17 Java Problem
    The method getInterfaceDescriptor() from the type IMarketBillingService.Stub.Proxy is never used locally IMarketBillingService.java /Crash_Dummy_LITE/gen/com/android/vending/billing line 80 Java Problem
    The value of the local variable packageName is not used Security.java /Crash_Dummy_LITE/src/com/giderosmobile/android/plugins/googlebilling line 171 Java Problem
  • I am using 9.5 but I have same problem with you. My testing device is HTC Desire HD 2.2 . These codes was working 2-3 ago. I doesn't work now. Maybe , Google changed something :S
  • I am using Nexus 7 with Android 4.2, still working good. Maybe it's android version related... Somewhere I am having an old HTC desire 2.3... I'll test with that device also...

    Did you add the line in Step 2 into your android manifest? I manually had to add it...
  • and I am still using gideros 9.2 since new versions seem to make problems....
  • When I had 9.2 it was working but I dont think it is about Gideros version. Because @nat12 has 9.2 and has same problem
  • tested on HTC desire, android 2.3 - working...
    tested on HTC desire S, android 4.0.4 - working...

    One test, android.test.canceled took 30 seconds until message arrived...

    At least for me still seems to work very good...
  • Damn! Why it is not working for me
  • I downloaded 9.2 and tried on it. When i buy test item, Gideros Player crashes. So strange things happenig for me :D
  • hmm, maybe it has something to do with the android SDK? I am using 4.1.2... You can still send me parts of your code via BM if you like... And android manifest....
  • Can you try this application. When you click on the screen it will go to play store and if you purchase the item , apk will be shut down.
  • which, where, what? ;-)
  • I forgot to upload sorry :)
    New Project.rar
    2M
  • @mertocan :-

    yes it shutdown after the message like your item will appear shortly.

    googlebilling:setPublicKey("yourverylongpublickey") --do you have replaced this with your key?

    also i think so it is necessary to upload apk on google play and same apk (as per my thought same package name and same keystore also atleast it would be worth to try) should be used for testing purpose.

    :)
  • I think problem is my country. In Turkey Android in-app purchases not allowed yet. So if it is working on your device, there is no problem with codes. Thanks @hgyyas123
  • yeah really android market is like hell (it is my feeling for it currently otherwise i love it)
    bcoz as per my knowledge india was not allowed to sell iap or paid apps in android market so i had setteled my game viking runner for that and when i was about to upload it on market i found hell i can now sell iaps and paid apps in market :D
    obviously it is a good thing for me but i can not publish my game on that day and now i am waiting for saturday-sunday :(

    i hope turkey and all other will added soon to sell apps

    :)
  • NascodeNascode +1 -1 (+1 / -0 )
    Guru

    Likes: hgvyas123

    have fun with our games~
    http://www.nightspade.com
  • great it was there since October and i found it just 1 day ago :D

    :)
  • @Radiesel and @all

    i had tried this code last night still i had not tried real products just android.test.purchase it works for me i can get success message but if i minimize the apk with home button (i think so after purchasing test product from market but didn't get any confirmation) sometimes later my game get forceclose even though it is not running so do you have any idea what to do for this case.

    also

    googlebilling:addEventListener(Event.REQUEST_PURCHASE_COMPLETE, onRequestPurchaseComplete)
    googlebilling:addEventListener(Event.PURCHASE_STATE_CHANGE, onPurchaseStateChange)

    above two lines add listener is there any need to remove this listener i am confused bcoz sometimes it takes approx 1 minute or even more to get confirmation about purchase so i assume it is a not good idea to remove this listener and making related function global right?

    edit : may be putting this code in init.lua or main.lua will and just requestPurchase on any scene will do the trick?
    :)
  • Hmm, maybe that should be answered by a real LUA expert? (which I am not.. :\"> ). Actually, I have put the EventListener functions in main.lua. Purchase is called in one of my scenes using SceneManager. I am having several places calling "Purchase", but I guess a good idea would be, to only change the state of purchased items in the event listener and not inform the user in the listener function. If user reaches some special scenes (like "main menu"), you can inform the user: "item received".

    But actually I am a bit stumped for the answer how to handle "home button". I just realized, that when purchasing a real product, the StateChange Event happens usually within a second (more or less immediately). But that's only experience with a few tests...

    Maybe some Gurus like ar2rsawseen have the "correct" answer? ;-)
  • ar2rsawseenar2rsawseen +1 -1
    Maintainer Accepted Answer
    It would definitely make sense to make the callback event global, because the user may go to other scene, etc.

    Good question about home button. I guess when there is a situation when you know that purchase was made, but you have not received a confirmation, you could use GoogleBilling:restoreTransactions() when the app starts or reloads to check if purchase was actually made
  • @ar2rsawseen, exactly what I thought... Thanks to the Guru ;-)
  • GoogleBilling:restoreTransactions() will not work for me as i have un managed iap that needed to be purchase again and again like coins.

    and i am not confused about that i am confused about crash when user quickly close the game after iap with home button might be my game can not handle this when it is in background not sure what is the exact situation i am just guessing the problem.

    for now i am thinking to give message to not close the game until you receive your payment as @radiesel says it will not take much time when it is in live state so i think it will be ok for now.

    :)
  • and dear @mertocan you also have this problem. :D
  • @hgvyas, I am having the same issue. Selling unmanaged IAP... I am not 100% sure, but my understanding is, that google will send messages until the message is confirmed. So when you close the App, there will be no

    googlebilling:confirmNotification(event.notificationId)

    from your App called. Later on when you start the App again, you might get the message (but that's only a guess). I will make more tests on this, also on restoreTransaction() which is difficult to test, too.

    Michael
  • ok plz dont take my above posts seriously it is not taking that much of long time to give purchase state it is almost instant i had tested lots of time last day and all the time i had got result at the same time so there might be some issue on that day sorry for any confusion caused by me

    :)
  • @Radiesel Hello. Thank you for this guide. It almost work for me.
    1. I tap on icon to buy item.
    2. It goes to google play.
    3. I pay money for my item
    4. Back to App
    5. But purchase doesn't gave me what it should.
    6. After that i have message in status bar "Mobile dungeons it is impossible to make a purchase"
    Do you know why it can happens?
  • require "googlebilling"
     
    --------------------------------------------------
    -- GOOGLE Billing
    -- @param event
    -- @return
    --------------------------------------------------
    local function onRequestPurchaseComplete(event)
    if (event.responseCode == GoogleBilling.OK) then
    -- transaction has been sent to google (thanks Atilim)
    -- don't unlock items here
    -- don't unlock items here
    else
    local msg = "purchase failed"
    if (event.responseCode == GoogleBilling.USER_CANCELED) then
    msg = "GoogleBilling.USER_CANCELED"
    end
    if (event.responseCode == GoogleBilling.SERVICE_UNAVAILABLE) then
    msg = "GoogleBilling.SERVICE_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.BILLING_UNAVAILABLE) then
    msg = "GoogleBilling.BILLING_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.ITEM_UNAVAILABLE) then
    msg = "GoogleBilling.ITEM_UNAVAILABLE"
    end
    if (event.responseCode == GoogleBilling.DEVELOPER_ERROR) then
    msg = "GoogleBilling.DEVELOPER_ERROR"
    end
    if (event.responseCode == GoogleBilling.ERROR) then
    msg = "GoogleBilling.ERROR"
    end
    end
    end
     
    --------------------------------------------------
    -- GOOGLE Billing
    -- @param event
    -- @return
    --------------------------------------------------
     
    local function onPurchaseStateChange(event)
    if (event.purchaseState == GoogleBilling.CANCELED) then
    if (event.productId == "mobiledungeons200") then
    -- lock or don't unlock
    elseif (event.productId == "your_product_002") then
    -- lock or don't unlock
    end
    elseif (event.purchaseState == GoogleBilling.PURCHASED) then
    if (event.productId == "mobiledungeons200") then
    -- unlock
    coins=coins+200
    coins = dataSaver.loadValue("coins")
    -- show Message, item bought
    elseif (event.productId == "your_product_002") then
    -- unlock
    coins=coins+500
    coins = dataSaver.loadValue("coins")
    -- show Message, item bought
    end
    elseif (event.purchaseState == GoogleBilling.REFUNDED) then
    if (event.productId == "mobiledungeons200") then
    -- lock or don't unlock
    coins=coins-200
    coins = dataSaver.loadValue("coins")
    elseif (event.productId == "your_product_002") then
    -- lock or don't unlock
    coins=coins-500
    coins = dataSaver.loadValue("coins")
    --elseif (event.productId == "android.test.purchased") then
    -- lock or don't unlock
    end
    elseif (event.purchaseState == GoogleBilling.EXPIRED) then
    -- for subscriptions
    else
    -- unknown state
    end
     
    googlebilling:confirmNotification(event.notificationId)
    end
     
    -- maybe it better to modify original publicKey by XOR or split into parts as google suggests
    googlebilling:setPublicKey("myverylongkey")
    googlebilling:addEventListener(Event.REQUEST_PURCHASE_COMPLETE, onRequestPurchaseComplete)
    googlebilling:addEventListener(Event.PURCHASE_STATE_CHANGE, onPurchaseStateChange)]]

    It is code. It is pretty same. But coins doesn't increase. Where this code should be?
    Thank you.
  • @unlying, looks pretty OK to me.

    Did you get the "item will appear soon" message after buying the item? Does it show up in your "google checkout" as a bought item? Did you use a different user to buy the item?

    Also, I would add an alert box within each state (PURCHASED, etc.) to test if the InApp purchase reached the correct part of the code. Also, make alertBoxes in onRequestPurchaseComplete(). Most frequent error that happens in my code is GoogleBilling.DEVELOPER_ERROR....

    And maybe there is something wrong with your coins (local, global problem)?

    Michael
  • Look like i'm just lame. This is my problem... coins = dataSaver.loadValue("coins")
    Sorry for bother
  • unlying +1 -1 (+1 / -0 )
    Guru
    It works! Awesome!

    Likes: ar2rsawseen

  • ;-) while I am having problems with another test-account... Damned InApp Purchasing...
    Just says: "This purchase is not possible for this user"... Argh!!!!
  • Guess it's because my other user is not added to the "Testuser" in developer console. Seems this is required for InApp Purchases as long as the App is not published...

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Login with Facebook Sign In with Google Sign In with OpenID

In this Discussion

Top Posters