Google Play v3

Well I know for certain that store.loadProducts() does call it’s listener.  I just tested it in the game I’m working on.  The call back for the loadProducts call is a different function.

local function loadProductsListener( event )     print("In loadProductsListener")     local products = event.products     for i=1, #event.products do         print(event.products[i].title)         print(event.products[i].description)         print(event.products[i].localizedPrice)         print(event.products[i].productIdentifier)     end     for i=1, #event.invalidProducts do         print("invalid", event.invalidProducts[i])     end end         store.init( "google", transactionCallBack )         if store.canLoadProducts then             print("Loading products!")             store.loadProducts( productList, loadProductsListener )             timer.performWithDelay(2000, function() store.restore(); end)         else             print("Can't load products")         end

When you try to buy something you already own you should get the message: In-app billing error: Unable to buy item, Error response: 7:Item Already Owned in your console log.  Your transaction call back   I don’t know why you’re crashing.  Is there more to the error?

Also keep in mind you cannot test with your developer account.  You have to use a test account.

Rob

Hi @Rob Miracle

I just had the same situation. 

When I got the message from console,

In-app billing error: Unable to buy item, Error response: 7:Item Already Owned .

The app crashed without any logs.

There is always a console log on the device that you get to with “adb logcat”.   If you are filtering only on Corona generated messages, i.e.: 

adb logcat Corona:v \*:s

then please leave the Corona:v *:s off and just run adb logcat because that filtering only shows your print messages and Corona generated crashes.  If its in some other intent like Google Play, you won’t see those messages.

In-app billing error: Unable to buy item, Error response: 7:Item Already Owned

is a perfectly normal message.  You cannot buy items already owned.  But if you’re crashing there has to be more messaging to go on.  If not brute force debug and add in some print statements.  See where you’re getting to in your code.

Rob

I put the consume API into a timer, hope it could be better. But why should I do this?

Its possible that the store API isn’t in a state to call another store API.  It’s similar to while you can’t do something things in physics while in the middle of a collisions handler.

Rob

I just use the consume API in the state “purchased”, sometimes it’s ok, sometime is not. I just wonder how could I know when it would be in the right state.

Now, I’m using the code below, put the consume API into a timer, try to fix the issue, but I’m not sure whether it would be ok in every case:

 if transaction.state == “purchased” then

        LOG(“Transaction succuessful!”)

    if MobileStore.paymentMode == 1 then – google iab v3

    …

        timer.performWithDelay(10, function() store.consumePurchase( { transaction.productIdentifier }, transactionCallback); end)

       

    end

elseif transaction.state == “consumed” then

    LOG( “Transaction consumed!” )

elseif  transaction.state == “restored” then

    LOG(“Transaction restored (from previous session)”)

elseif transaction.state == “cancelled” then

    LOG(“User cancelled transaction”)

elseif transaction.state == “failed” then

    LOG(“Transaction failed, type:”, transaction.errorType, transaction.errorString)

else

    LOG(“unknown event”)

end

– Once we are done with a transaction, call this to tell the store

– we are done with the transaction.

– If you are providing downloadable content, wait to call this until

– after the download completes.

store.finishTransaction( transaction )

It shouldn’t hurt to have it in a timer.  It could help the issues this thread was talking about.

Rob

ok, thanks Rob. 

It’s good to take care the forum by you. Fast and best reply~  :)

Sometimes, shit happened again, catch the log this time.

E/IABUtil/Security(19324): Signature verification failed.

W/IabHelper(19324): In-app billing warning: Purchase signature verification **FAILED**. Not adding item.

W/dalvikvm(19324): threadid=21: thread exiting with uncaught exception (group=0x41a96ba8)

W/System.err(19324): java.lang.NullPointerException

W/System.err(19324): at plugin.google.iap.v3.LuaLoader$6.onQueryInventoryFinished(LuaLoader.java:373)

W/System.err(19324): at plugin.google.iap.v3.util.IabHelper$2.run(IabHelper.java:622)

W/System.err(19324): at java.lang.Thread.run(Thread.java:841)

W/FlurryAgent(19324): Error logged: uncaught

I/System.out(19324): WARNING: Could not load font Helvetica. Using default.

W/FlurryAgent(19324): End session with context: com.ansca.corona.CoronaActivity@4220cf10 count:0

We’re experiencing a strange problem with the “already owned” consumables, and it doesn’t happen every time.

The first step is to cut off the internet connection between pressing buy, and seeing the next google popup - this should cause it to show the “no internet connection” message.

When we try and make the purchase again and see this message, the callback to lua has:

table: 0x7be9e488 { 02-09 17:23:19.215: I/Corona(13878): [name] =\> "storeTransaction" 02-09 17:23:19.215: I/Corona(13878): [transaction] =\> table: 0x7be9e488 { 02-09 17:23:19.215: I/Corona(13878): [state] =\> "failed" 02-09 17:23:19.215: I/Corona(13878): [errorString] =\> "User canceled. (response: -1005:User cancelled)" 02-09 17:23:19.215: I/Corona(13878): [isError] =\> true 02-09 17:23:19.215: I/Corona(13878): [errorType] =\> -1005 02-09 17:23:19.215: I/Corona(13878): } 02-09 17:23:19.215: I/Corona(13878): }

instead of error string saying:

Unable to buy item (response: 7:Item Already Owned) 

It means we have no way of differentiating between when a user has genuinely cancelled a transaction, or if this has happened. We can’t make the assumption that this error has happened and grant them the items anyway, otherwise it would be really easy for user’s to exploit.

However there is a “solution” but we don’t understand why it is working. If we purchase a different consumable item successfully and then call restore, the original “owned” purchase that was stuck in limbo appears as expected in our restorable items. So then we can consume it and make it available for purchase again.

Any ideas what could be happening here?

Edit:

Just found this on stackoverflow: http://stackoverflow.com/a/26682883/1182532

It seems that the IABhelper java code will return error code -1005 instead of 7, it’s hardcoded to do that.

So now my question is: is that in fact still the case, or have Google fixed this and now the Corona plugin need to be updated? And also, why would making a second purchase suddenly add the first purchase to the restorable items data returned by Google?

The latest Google Play IAP plugin is now returning the “already owned” code which is great!

When we detect this happening we call store.consumePurchase() like we would with a regular purchase, but for some reason the consume doesn’t seem to be processed by Google.

The plan was that if we detect the purchase is already owned, we reward the user with the item (as we normally would with a purchase) and then consume it. But as things are now, the consume fails so the user would actually be able to get lots of free items for just one purchase, because the “already owned” message keeps appearing.

 

If you wait long enough (I think about 15 mins but I’m not sure yet), then suddenly the IAP item becomes purchasable. It’s as if the consume request does go through, but with a huge delay. 

 

I’m not sure whether the issue lies in Google’s API or in the plugin. Has anyone at Corona ever noticed anything like this?

Hi Alan,

Well, with IAP in general, basically nothing on the server side (Apple or Google) happens “instantly”. This is just the nature of IAP, and you have to assume that purchases may take some time to be processed.

I’m not sure how you are “detecting the purchase is already owned” so I can’t provide any specific assitance on that point at the moment.

Take care,

Brent

The GP IAP plugin was updated 2 days ago. 

When we make a consumable purchase which Google determines is “already owned” because it was not previously consumed, Google returns “Error code 7: item already owned”. This is then passed to the plugin, which passes it to our IAP listener, and that’s how we determine it is already owned. Note that this is referring to consumables, as non-consumables can be checked easily by calling restore().

This is the part of our listener where that would be captured:

elseif event.transaction.state == "failed" then      if tostring(event.transaction.errorType) == "-1050" or tostring(event.transaction.errorType)=="-1005" or event.transaction.errorString == "User canceled. (response: -1005:User cancelled)" then         if storeCallback then              storeCallback(nil, "Purchase cancelled, you have not been charged")         end     elseif (tostring(event.transaction.errorType) == "7" or event.transaction.errorString == "Unable to buy item (response: 7:Item Already Owned)") then      storeCallback(lastProductID, "Item Already Owned")     end end

The storeCallback function is in our shop scene, which is where it then takes the lastProductID, checks which item that is, rewards it to the user, and then calls store.consumePurchase()

For a regular purchase, the consume function does happens instantly for all intents and purposes. I can make a purchase, then the same again, and again…

When a network interruption causes a purchase to become stuck in this way, calling consume is not working correctly, and you have to wait some length of time before it resolves itself. When store.consumePurchase is called during this time for this purchase, there is no callback to the store’s listener to say whether it has succeeded or failed. To me this means that either Google is failing to process it there and then, or the plugin is failing to send to Google / return to lua.

Can I get you to file a bug report on this?  You will need a small sample app that can demonstrate the issue.  Package it and any thing needed to build the app in a .zip file including config.lua, build.settings for SDK builds, project files for Enterprise builds, etc.

Please post the case id # back here when you get the email from the bug tracker.

Thanks

Rob

Danny Chan pointed me to a workaround. It turns out that the Google Play Store app caches information locally on the device, and this was causing the issue. Clearing the cache allowed the item to be consumed the next time we tried it, and then the item can be purchased again. 

Not a perfect solution, but also a Google problem rather than a Corona one it seems.

Thanks Danny (and everyone else at Corona)!

I got V3 working with full in game currency and remove ads plus a full store i wish i could give it to you all for free. But I have college bills to pay $4.99 http://romangaming.com/short-codes

I just got the following message, testing for a non-consumable: “Transaction failed:    7    Unable to buy item (response: 7:Item Already Owned)” … but I’m sure I don’t own the item. ??? Is this possible?

 

With Google IAP V3, you have to consume your purchases before you can buy them again.  See:  store.consumePurchase() as part of the Google IAP V3 plugin documentation:

http://docs.coronalabs.com/plugin/google-iap-v3/consumePurchase.html

If this is something you want the people to be able to buy again immediately you can call the consumePurchase() as part of your transaction call back for the purchase.

Rob

Thx for your fast feedback Rob!

I still don’t get it. In the doc it is written

"Note that some items are designed to be purchased only once and you should not consume them. For example, if a purchase unlocks a new world within a game, it should be ineligible for future consumption. In the Google IAP portal, these type of purchases are set up as managed products"

That’s the type of item I have in my app and I still get the message:

“Transaction failed:    7    Unable to buy item (response: 7:Item Already Owned)”

I just tested the restore function and it is restoring the item.

I’m sure I didn’t get the item before, but this is not possible, right? Is there a way to test if I somehow got the item or when I have done a possible purchase before?

Any help welcome!

There are basically two types of items that can be purchased.  Consumable and Non-Consumable.  Non-Consumable (or Managed in Google Lingo) you buy once.  You can never buy them again.   This is for things like “Unlock levels 21-40”, “Get upgraded powers”, “turn of ads”.   Consumable’s are for things like coins, gems, potions, energy, etc.  For Apple, Amazon and Google V2, you can just buy these whenever.  They are never “restored” when store.restore() is called, etc.  For Google V3, they changed the rules and even non-Consumables are now considered “Managed” and you have to consume them before you can buy them again.

If it shows up during a restore and you get this error when trying to purchases it, you have at some point in the past purchased it.

Rob