Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
Google Billing Refund — Gideros Forum

Google Billing Refund

ArtLeeAppsArtLeeApps Member
edited September 2013 in General questions
Hi all,

Im testing the In-app purchases in google.
- all is good purchasing, and restoring transactions.
but if i refund a transaction in Google Play. The transaction is marked as refunded and my account is credited.
my app knows its refunded, so it puts back the "buy the thing now" button.
but when i click it again, it tells me from google that "You already own this item".

its a Managed item, i would have expected it to be re-purchasable if needed.

Comments

  • Did you call GoogleBilling:confirmNotification(notificationId) and received Event.CONFIRM_NOTIFICATION_COMPLETE event after receiving item refunded event?
  • Hi @ar2rsawseen,

    ok im alittle confused.
    The code snippet i have below is mainly from bits in the forums.
    it includes the "googlebilling:confirmNotification(event.notificationId)"
    (note lowercase googlebilling?) as this is how i call it "require "googlebilling"".

    it includes a REFUND section, but the only thing different to your suggestion is there is no listener "CONFIRM_NOTIFICATION_COMPLETE", would i use that to call the REFUND section ?

    i also calls restoretransactions and the other 2 listeners...

    onPurchaseStateChange = function(event)
     
    	if (event.purchaseState == GoogleBilling.CANCELED) then
     
                if (event.productId == "stars_3") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_9") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_27") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_100") then
    			-- lock or don't unlock
    		elseif (event.productId == "remove_advertising") then
    			-- lock or don't unlock
    		end
    	elseif (event.purchaseState == GoogleBilling.PURCHASED) then
     
            if (event.productId == "stars_3") then
    				stars.bought = stars.bought + 3
    				stars.current = stars.current + 3
    		elseif (event.productId == "stars_9") then
    				stars.bought = stars.bought + 9
    				stars.current = stars.current + 9
    		elseif (event.productId == "stars_27") then
    				stars.bought = stars.bought + 27
    				stars.current = stars.current + 27
    		elseif (event.productId == "stars_100") then
    				stars.bought = stars.bought + 100
    				stars.current = stars.current + 100
    		elseif (event.productId == "remove_advertising") then
    			sets.advertising = false
    			ad.off()
    			-- show Message, item bought
    		end
    	elseif (event.purchaseState == GoogleBilling.REFUNDED) then
     
            if (event.productId == "stars_3") then
    			stars.bought = stars.bought - 3
    			stars.current = stars.current - 3
    			-- lock or don't unlock
    		elseif (event.productId == "stars_9") then
    			stars.bought = stars.bought - 9
    			stars.current = stars.current - 9
    			-- lock or don't unlock
    		elseif (event.productId == "stars_27") then
    			stars.bought = stars.bought - 27
    			stars.current = stars.current - 27
    			-- lock or don't unlock
    		elseif (event.productId == "stars_100") then
    			stars.bought = stars.bought - 100
    			stars.current = stars.current - 100
    			-- lock or don't unlock
    		elseif (event.productId == "remove_advertising") then
    			sets.advertising = true
    			ads.on()
    		end
    	elseif (event.purchaseState == GoogleBilling.EXPIRED) then
    		-- for subscriptions
    	else
    		-- unknown state
    	end
     
    	dataSaver.saveValue("sets", sets)
    	dataSaver.saveValue("stars", stars)
     
    	googlebilling:confirmNotification(event.notificationId)
     
    end
     
     
     
    if isAndroid == true then
     
    	googlebilling:addEventListener(Event.REQUEST_PURCHASE_COMPLETE, onRequestPurchaseComplete)
    	googlebilling:addEventListener(Event.PURCHASE_STATE_CHANGE, onPurchaseStateChange)
     
     
    googlebilling:restoreTransactions()
     
     
    end
  • ArtLeeAppsArtLeeApps Member
    edited September 2013
    Hi @ar2rsawseen,

    Now its crashing, as there is a NIL value passed to the ConfirmNotification ?
     
    09-09 20:12:51.623: D/Gideros(9496): *CppLuaBridge::luaEvent* stack NOT ok begin:3 end:4 delta:0
    09-09 20:12:51.623: D/Gideros(9496): *EventDispatcherBinder::dispatchEvent* stack NOT ok begin:2 end:4 delta:0
    09-09 20:12:51.623: D/Gideros(9496): *enterFrame* stack NOT ok begin:0 end:4 delta:0
    09-09 20:12:51.623: D/Gideros(9496): C:/Gideros_Development/Deployments/Bubble-Adventure-Colors/assets/assets/main.lua.jet:346: bad argument #1 to 'confirmNotification' (string expected, got nil)
    09-09 20:12:51.623: D/Gideros(9496): stack traceback:
    09-09 20:12:51.623: D/Gideros(9496): 	C:/Gideros_Development/Deployments/Bubble-Adventure-Colors/assets/assets/main.lua.jet:346: in function 
    09-09 20:12:51.643: D/Finsky(9525): [1] MarketBillingService.sendResponseCode: Sending response RESULT_OK for request 2858100355449254834 to com.artleeapps.bubbleadventurecolors.
    09-09 20:12:51.673: D/AudioTrack(9496): Audio Track already stopped
    09-09 20:12:51.673: D/AudioPolicyService(1274): stopOutput() tid 1519
    09-09 20:12:51.673: D/AudioPolicyManagerBase(1274): stopOutput() output 4, stream 3, session 521
  • just checking but in your lua code did you set the api verion?
    googlebilling:setApiVersion(2)
  • Hi @ar2rsawseen,

    I didnt have that, now ive added it just after i set the "setPublicKey".

    I still get the NIL error.
    09-10 21:49:17.533: D/Gideros(25653): *CppLuaBridge::luaEvent* stack NOT ok begin:3 end:4 delta:0
    09-10 21:49:17.533: D/Gideros(25653): *EventDispatcherBinder::dispatchEvent* stack NOT ok begin:2 end:4 delta:0
    09-10 21:49:17.533: D/Gideros(25653): *enterFrame* stack NOT ok begin:0 end:4 delta:0
    09-10 21:49:17.533: D/Gideros(25653): C:/Gideros_Development/Deployments/Bubble-Adventure-Colors/assets/assets/main.lua.jet:348: bad argument #1 to 'confirmNotification' (string expected, got nil)
    09-10 21:49:17.533: D/Gideros(25653): stack traceback:
    09-10 21:49:17.533: D/Gideros(25653): 	C:/Gideros_Development/Deployments/Bubble-Adventure-Colors/assets/assets/main.lua.jet:348: in function <C:/Gideros_Development/Deployments/Bubble-Adventure-Colors/assets/assets/main.lua.jet:270>

    Would it be "ok" to put a check around it.
    like
    	if event ~= nil and event.notificationId ~= nil then
    		googlebilling:confirmNotification(event.notificationId)
    	end

  • I guess so, but do you know what exact transaction type provides the error?
    The test transaction, or cancel state, etc?
  • @ar2rsawseen ,

    i put some dialogs in to show what is being run.

    i start the listeners, then call the "googlebilling:restoreTransactions()"

    Nil "event.notificationId" came through for "googlebilling:confirmNotification(event.notificationId)":

    For this transaction:
    - event.productid = "remove_advertising"
    - event.purchasestate = "purchased"

    Even so:
    - the remove_advertising has been purchased, and then refunded.
    so i would expect both to come through ?

    Im sooooo close to knocking this app off.. its becoming annoying :-??
    Sorry for being a Pain-in-da-but....

    Here are the important bits from my Lua - Main:
    Ive cut out the other app related bits....
    deviceInfo = application:getDeviceInfo()
     
    if deviceInfo == "Android" then
    	isAndroid = true
    elseif deviceInfo == "IOS" or deviceInfo == "MAS OS" then
    	isIOS = true
    end
     
    if isAndroid == true then
    	require "googlebilling"
     
    	googlebilling:setPublicKey(<MY KEY here >)
    	googlebilling:setApiVersion(2)
    end
     
    onRequestPurchaseComplete = function(event)
        if (event.responseCode == GoogleBilling.OK) then
    		-- 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
     
    onPurchaseStateChange = function(event)
     
    	if (event.purchaseState == GoogleBilling.CANCELED) then
     
                if (event.productId == "stars_3") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_9") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_27") then
    			-- lock or don't unlock
    		elseif (event.productId == "stars_100") then
    			-- lock or don't unlock
    		elseif (event.productId == "remove_advertising") then
    			-- lock or don't unlock
    		end
    	elseif (event.purchaseState == GoogleBilling.PURCHASED) then
     
            if (event.productId == "stars_3") then
    				stars.bought = stars.bought + 3
    				stars.current = stars.current + 3
    		elseif (event.productId == "stars_9") then
    				stars.bought = stars.bought + 9
    				stars.current = stars.current + 9
    		elseif (event.productId == "stars_27") then
    				stars.bought = stars.bought + 27
    				stars.current = stars.current + 27
    		elseif (event.productId == "stars_100") then
    				stars.bought = stars.bought + 100
    				stars.current = stars.current + 100
    		elseif (event.productId == "remove_advertising") then
    			sets.advertising = false
    			ad.off()
     
    		end
    	elseif (event.purchaseState == GoogleBilling.REFUNDED) then
     
     
    if (event.productId == "stars_3") then
    			stars.bought = stars.bought - 3
    			stars.current = stars.current - 3
    			-- lock or don't unlock
    		elseif (event.productId == "stars_9") then
    			stars.bought = stars.bought - 9
    			stars.current = stars.current - 9
    			-- lock or don't unlock
    		elseif (event.productId == "stars_27") then
    			stars.bought = stars.bought - 27
    			stars.current = stars.current - 27
    			-- lock or don't unlock
    		elseif (event.productId == "stars_100") then
    			stars.bought = stars.bought - 100
    			stars.current = stars.current - 100
    			-- lock or don't unlock
    		elseif (event.productId == "remove_advertising") then
    			sets.advertising = true
    			ads.on()
    		end
    	elseif (event.purchaseState == GoogleBilling.EXPIRED) then
    		-- for subscriptions
    	else
    		-- unknown state
    	end
     
    	dataSaver.saveValue("sets", sets)
    	dataSaver.saveValue("stars", stars)
     
    	if event ~= nil and event.notificationId ~= nil then
    		googlebilling:confirmNotification(event.notificationId)
    	end
    end
     
    if isAndroid == true then
     
    	googlebilling:addEventListener(Event.REQUEST_PURCHASE_COMPLETE, onRequestPurchaseComplete)
    	googlebilling:addEventListener(Event.PURCHASE_STATE_CHANGE, onPurchaseStateChange)
    	googlebilling:addEventListener(Event.CONFIRM_NOTIFICATION_COMPLETE, onPurchaseStateChange)
     
    googlebilling:restoreTransactions()
     
    end
    		<service android:name="com.giderosmobile.android.plugins.googlebilling.BillingService" />
     
    		<receiver android:name="com.giderosmobile.android.plugins.googlebilling.BillingReceiver">
    		     <intent-filter>
    		         <action android:name="com.android.vending.billing.IN_APP_NOTIFY" />
    		         <action android:name="com.android.vending.billing.RESPONSE_CODE" />
    		         <action android:name="com.android.vending.billing.PURCHASE_STATE_CHANGED" />
    		      </intent-filter>
    		</receiver>
  • Aha, ok after reading more, it seems that refunds does not indeed need to confirm transactions.

    And further more, if in your google checkout you press cancel order, then you will be dispatched refund event, but if you will press refund money, then only money will be refunded, but no events to the app and product will still be marked as purchased.

    Is that what happened?

  • ArtLeeAppsArtLeeApps Member
    edited September 2013
    @ar2rsawseen brilliant find!.

    Yes.
    1) the app is uploaded but in draft, so available to my test users.
    2) user1, purchased a managed item "remove_advertising"
    3) user1, the bank account was still debted (even though it says test users wont, no drama)
    4) i went to my google console and refunded it there.
    5) it showed refunded, and user1's account $ was refunded (all but 1cent lol)
    6) if she tries to purchase again, it shows a google message ..like you already have this item
    so its like google has paid it back, but it hasnt flagged it back as available to purchase, or sent my app a refund event when i do a restore transactions.

    so you are very close... is there a solution, or is it just a google glitch...
  • ar2rsawseenar2rsawseen Maintainer
    edited September 2013
    Is there a still an option to cancel the purchase inside Google Checkout?

    As in there should be two options, refund and cancel, both refunds money and only cancel changes purchase state.
  • i can see the cancel and refund for the one i refunded. both are greyed out.
    i had a look at another purchase (but that was for another android pay up front game i sold.) that only has "refund" available and the cancel is greyed.
    when you read the "?" it refers to cancel for subscriptions.

    Maybe i should just retreat, and if someone wants refunds, theyll still get their money, but the app just wont get the message..
  • @artleeapps did you ever figure out how to get refunds recognized by your app?
  • @larf no, all im hoping is if someone wants a refund, at least i can refund the money, the app may not undo the purchase though. it shouldnt be a common thing.. i hope.
  • Forgot to mention that there is nothing about refunding in Google Billing v3 api, so as far as google is concerned, you should not refund anything in your apps :-/
  • @ar2rsawseen that would make coding it easier, but still not a very nice option for little 5yo tommy that buys a $487 digital chair for his virtual farm app using his mums google play account, she then has to live with the fact she cant afford to buy a real life chair from IKEA...

    hmmm
  • @ar2rsawseen that would make coding it easier, but still not a very nice option for little 5yo tommy that buys a $487 digital chair for his virtual farm app using his mums google play account, she then has to live with the fact she cant afford to buy a real life chair from IKEA...
    hmmm
    Developers that actually sell Virtual goods of that kind know what they are doing and actually prey on such sales... Unfortunately *GREED* is in the top 5 vices at the moment.
    twitter: @ozapps | http://www.oz-apps.com | http://howto.oz-apps.com | http://reviewme.oz-apps.com
    Author of Learn Lua for iOS Game Development from Apress ( http://www.apress.com/9781430246626 )
    Cool Vizify Profile at https://www.vizify.com/oz-apps
  • @OZApps agreed. Years ago my daughter asked me for real money for her "Habo"(online PC reality game.). so she could buy virtual furniture for her Habo room to impress her friends. i told her then, i wasnt going to pay like $5 for a virtual table for her virtual room ! lol
    I saw a TV show mention they kids we paying like $100+ for food for animals in apps so they wouldnt die. its not right hey.
  • What I meant is, that you can refund any payment in your Google Checkout, but, there is no event (or nothing similar) about refund in Helper libs to actually know if the item was refunded and remove it in the app.

    What you can do, is to use Purchase Status API, to query statuses of specific purchases, but even then, it is meant to use from your server backend, and not from your app.
  • @ar2rsawseen i suppose we hope there is only ever a tiny percentage of people that want a refund :) cos the app wont really know to revert the bought item.
  • @ArtLeeApps, That reminds me of an incident that happened at the Uni a couple of years ago. A lecturer send a file to the students and then was informed by the school that the data was copyright'ed and could not be send so the lecturer send another email explaining that the file was copyrighted and the students need to send the file back to him... problem solved ;)

    Since there is no return policy on Apple Store, it is less of a headache, where as on the google store, it is a problem. A user can buy the app, download the binary .apk then request a refund and the binary is now available for them to crack and use without paying for it. This is primarily a reason for google apps being pirated the most and thereby it being laden with ADs as the ads will show even on pirated apps. I am not sure that you want to deal with the refunds yourself, otherwise you'd be spending more time on the store handling that than anything else.

    I understand the need for a refund policy, the app does not work on your device, or not compatible or like most android apps is super slow (compared to the iOS app) and even that earlier you required a credit card for purchases now there are Google Play store cards.

    Isn't it funny that people buy $5 or more candy/coffee/cigarettes and what not and throw away the same coz they do not like the taste but want a refund on a $0.99 app...


    Likes: phongtt

    twitter: @ozapps | http://www.oz-apps.com | http://howto.oz-apps.com | http://reviewme.oz-apps.com
    Author of Learn Lua for iOS Game Development from Apress ( http://www.apress.com/9781430246626 )
    Cool Vizify Profile at https://www.vizify.com/oz-apps
    +1 -1 (+1 / -0 )Share on Facebook
  • @ArtLeeApps, That reminds me of an incident that happened at the Uni a couple of years ago. A lecturer send a file to the students and then was informed by the school that the data was copyright'ed and could not be send so the lecturer send another email explaining that the file was copyrighted and the students need to send the file back to him... problem solved ;)
    =)) i like that...

    I suppose you could put your own disclaimer within our apps stating:

    "no refunds will be given, suck lemons dude or dudette which ever the case may be. Thanking you"

    :)

    Likes: phongtt

    +1 -1 (+1 / -0 )Share on Facebook
Sign In or Register to comment.