Google Play IAP event.transaction.state == "purchased" never gets called

Everyone,

We just tested this today with “managed” and “unmanaged” products and it is working.

We tested an app built with the release version (build #1076) on the following devices:

  • Galaxy Nexus 4 with OS version 4.2.2 with Google Play version 4.0.26

  • Galaxy Nexus with OS version 4.0.4 with Google Play version 4.0.27

  • Galaxy SII with OS version 2.3 with Google Play version 3.10.14

We verified that Corona does correctly receive a “purchased” event with a JSON receipt.

We also verified that in-app purchases work with the new and old versions of the Google Play app.

Now, I do know that purchases will fail for the following reasons:

  1. You cannot use your developer account to test purchases.  You need to set up a separate test account with Google Play on your device and via the Android Developer Console.

  2. If the purchase came from an app package name that doesn’t exist in your Android Developer Console.  I believe it must be signed with the same keystore as well, meaning that you cannot use our debug.keystore for testing in-app purchases.

I’ve also noticed that the Google Play app will crash when attempting to do an in-app purchase if the Google Play app is in the middle of updating itself.  That is, you’ll first see the old Google Play in-app purchase screen (which is full screen), you’ll be kicked out of the window almost immediately afterwards (because Google Play crashed), and when you attempt to purchase the same product again you’ll see the new Google Play in-app purchase popup dialog instead within your own.  It doesn’t cause your app to crash, it’s more like the Google Play app effectively reboots itself to use the newest version.  It’ll work correctly for subsequent in-app purchases.

By the way, we’ve tested with real purchasable products that charged our credit card.

We did not test with Google’s test products, such as “android.test.purchased”.

Okay looks like I have some tweaking to do. I was using googles test card to test. I will try a real card and report back.

Okay I just tried a bunch of orders using real products and real credit cards. I’m still seeing the same issue. My canceled transaction fires the listener but my successful one does not. Maybe it’s just my code? Can someone take a look at it?

storeinit.lua

local store = require("store") function transactionCallback( event ) local infoString -- Log transaction info. print("transactionCallback: Received event " .. tostring(event.name)) print("state: " .. tostring(event.transaction.state)) print("errorType: " .. tostring(event.transaction.errorType)) print("errorString: " .. tostring(event.transaction.errorString)) if event.transaction.state == "purchased" then infoString = "Transaction successful!" elseif event.transaction.state == "restored" then infoString = "Restoring transaction:" .. "\n Original ID: " .. tostring(event.transaction.originalTransactionIdentifier) .. "\n Original date: " .. tostring(event.transaction.originalDate) elseif event.transaction.state == "refunded" then infoString = "A previously purchased product was refunded by the store." elseif event.transaction.state == "cancelled" then infoString = "Transaction cancelled by user." elseif event.transaction.state == "failed" then infoString = "Transaction failed, type: " .. tostring(event.transaction.errorType) .. " " .. tostring(event.transaction.errorString) else infoString = "Unknown event" end toast.new(infoString,10000) store.finishTransaction(event.transaction) end --Load in the right store for the right phone if store.availableStores.apple then store.init("apple", transactionCallback) elseif store.availableStores.google then store.init("google", transactionCallback) else toast.new("No app store found on your device", 1000) end

store.lua

function checkStoreIsAvailable(purchaseItem) if(store.isActive == false) then showStoreNotAvailableWarning() elseif(store.canMakePurchases == false) then native.showAlert("Store purchases are not available, please try again later", {"OK"}) elseif purchaseItem then store.purchase({purchaseItem}) end end function doublePayouts() checkStoreIsAvailable("testprod") end function showStoreNotAvailableWarning() if isSimulator then native.showAlert("Notice", "In-app purchases is not supported by the Corona Simulator.", { "OK" } ) else native.showAlert("Notice", "In-app purchases is not supported on this device.", { "OK" } ) end end

It’s pretty basic code though I don’t see where the issue could be in the code.

I don’t see anything wrong with your code.

I recommend that you have a look at the log via Android SDK tool “adb logcat” or “ddms”.  Check to see if anything unusual gets logged after making a purchase.  Particularly from the Google Play app.  I’m wondering if Google is responding with an HTTP status code of 403, which means Google’s servers have temporarily banned your device from communicating with them too much… which typically happens to Android developers when doing too many in-app purchase “restores”.  I’ve heard that this ban lasts for about 3 weeks, in which case, you’ll have to test with another Android device.

Also, you may want to double check the following:

  • You have the “com.android.vending.BILLING” android permission set in your “build.settings” file.

  • Try launching the Google Play app separately to make sure that app can connect to Google’s servers.

  • Your in-app products are flagged as “Active” under the “Status” column in the Google Play Developer Console.

  • Check if your Merchant Account is “Active” in the Google Play Developer Console.

One more thing.  Are you building your app with the Corona Simulator or Corona Enterprise?  If it’s with Corona Enterprise, then I’ll have some follow-up questions for you about how your AndroidManifest.xml file is set up.

Other than that, I’m starting to run out of ideas.  I had one other person here test out Google’s in-app purchase system and it worked for him as well.  If there is a real issue here, then we’ll be happy to look into it.

Here is the log cat for the purchase, seems to be okay to me. The last line is the only thing that looks a little odd?

D/Finsky ( 4328): [30] InAppBillingUtils.getPreferredAccount: wheel.slots.le om.com: Account from first account - [In\_h0C\_V5hSY629rEleRs0SN5cg] D/Finsky ( 4328): [30] InAppBillingUtils.getPreferredAccount: wheel.slots.le om.com: Account from first account - [In\_h0C\_V5hSY629rEleRs0SN5cg] D/Finsky ( 4328): [1] MarketBillingService.sendResponseCode: Sending respons ESULT\_OK for request 7568336821629477583 to wheel.slots.leetcom.com. D/Finsky ( 4328): [8] InAppBillingUtils.getPreferredAccount: wheel.slots.lee m.com: Account from first account - [In\_h0C\_V5hSY629rEleRs0SN5cg] D/Finsky ( 4328): [8] InAppBillingUtils.getPreferredAccount: wheel.slots.lee m.com: Account from first account - [In\_h0C\_V5hSY629rEleRs0SN5cg] D/Finsky ( 4328): [1] MarketBillingService.sendResponseCode: Sending respons ESULT\_OK for request 8542737001937316334 to wheel.slots.leetcom.com. D/Finsky ( 4328): [1] MarketBillingService.sendResponseCode: Sending respons ESULT\_OK for request 3800187125826941047 to wheel.slots.leetcom.com. I/Gmail ( 4409): calculateUnknownSyncRationalesAndPurgeInBackground: queuei I/Gmail ( 4409): calculateUnknownSyncRationalesAndPurgeInBackground: runnin I/Gmail ( 4409): MainSyncRequestProto: lowestBkwdConvoId: 0, highestHandled verOp: 274246, normalSync: true D/Finsky ( 4328): [1] MarketBillingService.sendResponseCode: Sending respons ESULT\_OK for request 6218416608661063811 to wheel.slots.leetcom.com. D/Finsky ( 4328): [1] PendingNotificationsService.setMarketAlarm: Setting al for account=xxxxxxxxx@gmail.com, duration=120000
  • I do have the billing permission set in my settings

  • I can connect just fine and purchase other apps with no problem. I also purchased an in app purchased an in another app

  • The in app item is active in google play portal the app is in draft. The apk is signed and is the same version number

  • My merchant account is active as well

It doesn’t seem to be anything on google’s side as I can make the purchase just fine. I can see the payment in my finance report and everything seems to go through just fine. The listener for the purchase just doesn’t get called. If I cancel the order it gets called or if I try to place the order in air plane mode it also gets called.

I’ve tried using 2 different accounts on 2 devices. I will try a few other devices but I expect the same results. Also all the test have been done over wifi, not sure if that matters or not.

Is there anything else I can provide you with to help?

Okay in an epic change of events it’s working now…What did I do? Absolutely nothing. 

I just kept putting orders though and then after like 10 orders it started working. The next 5 went through just fine. Really confusing, but oh well it works :slight_smile:

Thanks for everyone’s help.

Thanks for the log output.  The “PendingNotificationService” log message at the end is the “purchased” notification be broadcasted by the Google Play app.  So, that’s a good sign that the notification was successfully sent to your device.

Also, you haven’t ever hacked your APK and changed its AndroidManifest.xml file, have you?

Or… are you a Corona Enterprise user?

I ask because in both cases, you might be missing a BroadcastReceiver in your AndroidManifest.xml file that is needed to receive notifications from Google.  (It is there for all Corona Simulator builds.)

Hah!  We both posted at the same time!

I’m glad it’s working for you.  I’m wondering if it was some kind of caching issue with the Google Play app.  I’m not really sure.

Okay I’m seeing strange results. Sometimes I will make a purchase and everything will work just fine other times I will make the same purchase and I will get 5+ listener calls. They all have the same data returned (orderID, productID etc) but the resquest numbers are different. Any idea on why randomly products will return a bunch of success calls?

This is the response

D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 5628181007854239765 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 1700531723790948464 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 582475236503027374 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 2698016979307428823 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 3489384765658278205 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 5368633466355141703 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 875961568349548605 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 8999392674353360692 to wheel.slots.leetcom.com. D/Finsky(4328): [1] MarketBillingService.sendResponseCode: Sending response RESULT\_OK for request 1439908923599180228 to wheel.slots.leetcom.com.

This is  what the above returned 9 times. What is strange is there are 9 orders returned in the orders object. I only purchased 1 order though.

I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ transactionCallback: Received event storeTransaction I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ state: purchased I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ errorType: none I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ errorString: I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ Transaction successful! I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ receipt: {"nonce":-7696189100248259916,"orders":[{"notificationId":"-4611826006870453728","orderId":"12999763169054705758.1366811113029972","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367034727000,"purchaseState":0,"purchaseToken":"zltwxsvzxdtfwirsrrhtrvkc"},{"notificationId":"7162215564741436666","orderId":"12999763169054705758.1367189289214852","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367035693000,"purchaseState":0,"purchaseToken":"epcjznbnikjdlrevhvuwjbdx"},{"notificationId":"-8412612498536890168","orderId":"12999763169054705758.1321995054202457","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367035772000,"purchaseState":0,"purchaseToken":"cgggkhqypyzgzgivyqdmubny"},{"notificationId":"-395483000777125701","orderId":"12999763169054705758.1331697714566000","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367037734000,"purchaseState":0,"purchaseToken":"gkmsyfqvoygzuginummivlyd"},{"notificationId":"2278988028017084230","orderId":"12999763169054705758.1346107916484550","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367037812000,"purchaseState":0,"purchaseToken":"ryhwlrgkrnxxxmxyazxzqgzv"},{"notificationId":"8366231144424214981","orderId":"12999763169054705758.1390432587546936","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367038058000,"purchaseState":0,"purchaseToken":"afnlvmoidnezygjuoyladqia"},{"notificationId":"180569651592993847","orderId":"12999763169054705758.1391895778016296","packageName":"wheel.slots.leetcom.com","productId":"double\_payouts","purchaseTime":1367038154000,"purchaseState":0,"purchaseToken":"fulkvxelvnbwmvyiabgdjvax"},{"notificationId":"-5533012885893683259","orderId":"12999763169054705758.1313748973204732","packageName":"wheel.slots.leetcom.com","productId":"jackpot\_500k","purchaseTime":1367038183000,"purchaseState":0,"purchaseToken":"qtwevcemaghczevwlyfvifom"},{"notificationId":"-6963194004359951465","orderId":"12999763169054705758.1387536113005002","packageName":"wheel.slots.leetcom.com","productId":"jackpot\_500k","purchaseTime":1367038234000,"purchaseState":0,"purchaseToken":"xefankpofgilpkgrxdpezwxy"}]} I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ identifier:8366231144424214981 I/Corona(7091): ~~~~~~~~APP STORE~~~~~~~~ signature: LGH7glkLEtxzcwHKXVts4h1ah5G+YJUGDcyDzO7F5OTH0xnL2/5NkRZGHW7VwX+YCvZuPiOJF9wZ1qc6DRJcTZecAAw3aqSND8uUsS85mVht4GfVF7gmDxii59zwzORHDg/zCPsQghUNLpT5aaRWCsqDmpeMlBrd7KLcN+lHypdm4uAYgSWUyTpyE/Trr10SfSTl/3Cb3NI5OF1Mnp46XytuI6/zVoobPWZjYOgzyCS0LWo6U8irXguL/8S2avF5VugOk7zYnGJwJAWzBjisuTTsTg8EnmC3DqJv5CKT9TwAAElK73NbGmkrdPXpHYKUBX5+Mw3UgglqRUJQEtWfig==

Not sure what to make of any of this but I just uninstalled the app and reinstalled it. I then made 1 purchase and let it sit for 20 minutes or so after the purchase. The listener was called successfully 10 times and then a few minutes later the listener was called successfully 8 times. 

I’m so confused on what’s going on with this thing. Anyone have any ideas on why the listener is getting called so many times after 1 purchase?

Also the 18 purchases that come through aren’t even the right purchases. It’s a different sku all together.

EDIT: I think I figured it out there was an error parsing the data so store.finishTransaction(event.transaction) was never getting called. I’m not sure what this function does exactly but once I added that it seems to be working correctly now. Does that just tell google that we’ve received the item?

Hello, please someone help me. 

I don’t know why I’ve got a hundred of callback calls with invalidclient error being called continously. I don’t have any way of making it stop. I’m calling the store.finishTransaction at the end of the callback, but still it’s being called a lot of times.

Can someone help me? or hint me how can I make google stop sending it?

I had it at the end of my call as well but I found out that something was failing before it was getting called so my code wouldn’t actually make the call even though it looked like it was. Try and move the finish call above the code you’re running and see if that resolves the issue. If so it’s an issue in your code somewhere.

seems it got resolved, maybe some server mismatch between versions. I reinstalled everything and now it received a correct user cancelled message and seems everything got stable. Thanks for your help!

An InvalidClient error typically means something went wrong with the Google Play app.  Such as an account has not been set up on your device (and it must be a test account, not your developer account), or you haven’t agreed to Google’s license agreement yet, or something.

Try the following:

  1. Launch the Google Play app and verify that you can browse apps.

  2. Make sure that the account you are logged in with in Google Play is not your developer account.

  3. View the Android log when making an in-app purchase in your app via the Android SDK tool “adb logcat” or “ddms”.  Look for any additional errors in the log.

I’ve also heard (but not sure if it’s true) that the newest version of the Google Play app is timing out with Google’s servers when making in-app purchases, but only in certain regions.  In the USA, we haven’t had any problems.  We’ve made several real purchases (with a real credit card) and verified that in-app purchasing is still working.  We’ve posted our test results here…

http://forums.coronalabs.com/topic/34070-google-play-iap-eventtransactionstate-purchased-never-gets-called/?p=177386

That’s my understanding of what store.finishTransaction() does, but I’m not 100% sure so don’t hold me to that. I think it just notifies iTunes / Google Play that you have successfully received confirmation of the purchase, in case the user lost their connection or something during the purchase process.

The store.finishTransaction() function sends a confirmation to Google that your app has successfully received the purchase notification.  If you do not send this confirmation, then Google Play will repeatedly send that purchase notification to your app until it does, which accounts for the duplicate events that you are seeing.  This is a safety mechanism on Google’s part in case your app fails to respond for whatever reason, such as if it crashed or had an error in the middle of a transaction.

Please note that you should only call store.finishTransaction() after you have saved the purchase to file/database.  Otherwise you risk putting the app in a bad state where Google thinks the product has been purchased, but the app fails to unlock the purchased item.

Also, if you set up Corona to display runtime errors, it would make it a lot easier/faster to debug these kind of things.

http://docs.coronalabs.com/guide/basics/configSettings/index.html#debug

Anyways, I’m glad you go it working!

Hello, please someone help me. 

I don’t know why I’ve got a hundred of callback calls with invalidclient error being called continously. I don’t have any way of making it stop. I’m calling the store.finishTransaction at the end of the callback, but still it’s being called a lot of times.

Can someone help me? or hint me how can I make google stop sending it?

I had it at the end of my call as well but I found out that something was failing before it was getting called so my code wouldn’t actually make the call even though it looked like it was. Try and move the finish call above the code you’re running and see if that resolves the issue. If so it’s an issue in your code somewhere.

seems it got resolved, maybe some server mismatch between versions. I reinstalled everything and now it received a correct user cancelled message and seems everything got stable. Thanks for your help!