iOS In App Purchase Please Help!

No, blame Apple for the noisy console log. You can stretch the window up to get more space, but you just have to be diligent in hunting for the errors.

Rob

Hi Rob, me again :frowning:

Still not working. I can’t seem to find anything wrong in the console. I read somewhere that when sandbox testing on device the id should not be verified, is this the case? The only problem is that if I do not verify it just stays hanging if I push Not Now button.

I cannot believe this is the most frustrating thing. Has no one experienced this problem? I have already tested over ten user test accounts, always deleting them to have fresh new ones just in case that might affect too.

If anyone finds the solution to this I will buy them a beer and send it sandboxed :stuck_out_tongue: Please help someone :stuck_out_tongue:

I also tried a swift iap program and the same I happening. Something tells me it must be a problem with test user account, verification or something connected to that. 

Oh I even tried tests on different stores, I tried US, because apparently I read somewhere that this person was only successful with his tests on the US test user account.

IAP=EVIL

Thanks

Neither Apple, Google or Amazon make IAP testing easy, though Amazon is probably the easiest to do.

Other than Corona’s API calls talking to the store, all of the setup, testing, accounts, when you need to login/out of the app store, etc. are all standard for Apple. In other words, if you can find a tutorial on IAP testing it should work just fine for a Corona built app.

Apple changes the rules periodically, so I would use Google and search for Testing Apple IAP tutorial or some such and make sure to use only stuff written in the last year or so.

I’m building a game in my spare time now and I’ve implemented IAP. I’ve got testing problems too, but I can load products from the server and initiate purchase calls. I’m pretty sure my IAP is setup correctly on iTunes Connect since I can get everything via loadProducts(). But testing purchases gets into test accounts and I’m not sure that even I have that correct.

So lets start there. Can you successfully fetch your products from the server using store.loadProducts()?

Rob

Hi Rob thanks for replying.

loadProducts() is working as it should, I can get titles, descriptions, prices etc… and it is exactly the information I inputted in iTunes for IAP.

However, when I go to purchase and enter my sandbox credentials which I only use here of course, I get the following:

Apple ID Verification

Open Settings to continue signing in

with “bla.bla.bla@gmail.com”.

Not Now | Settings

Apparently I should not be getting this verification screen prompt.

If I press Not Now, nothing happens and it stays hanging there. If I press Settings which I shouldn’t because I should not be getting this screen on the first place, I go through verification process making you re-enter a new password etc, but it does not work either. After verifying this happens:

The Apple ID you entered

couldn’t be found or your

password was incorrect.

Please try again.

Cancel | Retry

If I were to enter incorrect info I would get incorrect user or password etc. So it does recognise it in a way.

And as I stated in my initial post I have tried absolutely everything, I have tried again today, more gmail accounts and more test users, different stores, USA, CANADA, UK totally frustrating, this does not work.

I also tried a swift program for IAP, exactly the same thing.

:((((

Thanks

Have a look in the Corona marketplace for the IAP Badger plugin - the documentation and support make this a really easy way to implement IAP!

I am wondering if all the Corona users only use the IAP Badger and no one was able to get a nicely “buy full version”-IAP running…? If someone has a clean, simple code that works well, please would you be so kind and share it with the others?

For almost every topic on Corona I can find so many samples and tutorials, just on IAP it is quite rare and I couldn’t get anything running except the IAP Badger…

IAP Badger is just a wrapper around our store.* API calls. Many people implement IAP with success with IAP Badger and without. 

IAP is pretty simple in concept. You initialize the system, you get a list of products if you wish to show the prices and descriptions in local currency/language. If not you can always hard code the values in the app.

You set up some screen that shows what you have to sell and provide a buy button for the item. That buy button calls store.purchase(). When the results of that button push with the various stores completes your app is notified about the result. Depending on the result you can then take actions for whatever that result is. For instance if some one bought a 100 gem pack, you give them 100 gems. If they unlocked levels 11-20, then you unlock levels 11-20. If they cancelled, out, you don’t do anything. 

Since the listener function for IAP tends to be in main.lua or a dedicated store module, you frequently need to know when things happen (like getting 100 gems). The listener function in main.lua may not have direct access to your gem counter. You have to be come comfortable with the various methods of passing data around between modules.

All three main stores (Apple, Google, Amazon) are similar but different. Internally we’ve made them as close as we can, but things like store.loadProducts() will return different data types since each store has their own unique values to send back (this mostly impacts loadProducts).

Now where IAP gets challenging is setting up the stores in the first place. Apple, Google and Amazon are all different. But as long as you use the same productIdentifiers in all three stores, you save yourself a ton of work. Each store’s testing requirements are also quite different and can be challenging – but all of that is out of our control.

Rob

Yes, in theory it seems to be pretty straightforward, but then there are many details to watch out for.

Maybe there are some things I still don’t understand about IAP:

  1. REQUIRE: Some say that you need to include a pause after

store = require(“plugin.google.iap.v3”)

(for Google) and the store init, for example like this:

timer.performWithDelay( 1000, function()
    store.init( “google”, StoreListener );
end )

  1. INIT: The Corona documentation says to simply call:

store.init( StoreListener )

But in some example codes, I can read this:

store.init( “google”, StoreListener )

(and for iOS the same with “apple”). So, is this extra “google”/“apple” thing needed or not?

  1. LOADPRODUCTS: store.loadProducts() is completely optional, yes? It is only to get information, but I don’t need to call anything like that to get my product into the store? The store.purchase(“product.id”) is all that is needed to buy after the store initialisation is correctly done?

  2. BUY BUTTON: I am aware of

store.purchase( {“apple.product.id”} )

and

store.purchase( “google.product.id” )

(with and without curley brackets)… although it would be of course great if one day this could be unified in Corona. :slight_smile:

  1. TRANSACTION STATE: After the purchase, some write that they also include a delay of 500 miliseconds to give the listener some time before checking the state of event.transaction… Is that right?

  2. FINISH: After checking for the state and giving the user what he wants, I call

store.finishTransaction(transaction)

and only after that I can lead the user to a new scene, like:

composer.gotoScene(“thankyou”)

…correct?

Answers to these questions would already greatly help me, because otherwise there are just too many different possibilities to try out one by one…

Thanks a lot!! :slight_smile:

  1. Dropping the init call in a timer may be beneficial because the app can be busy doing other things at startup. Not required, but doesn’t hurt.

  2. Looking at the documentation for store.init() in the main API docs as well as the two plugins .init() functions, they don’t show a provider needed any more. Just call store.init( listenerFunction ).

  3. You don’t have to have loadProducts. You should if you’re going to want to appeal to people in other countries. It is not a requirement for store.purchase.

  4. Apple wants a table, Google and Amazon want a string. I think we’ve changed all of them to take either. But doing the table method with a single string is the safest way.

  5. You should be able to check the state of event.transaction immediately. It’s a data table passed to the function. 

  6. You should always call finshTransaction(). However the function will finish even if you’re going to another scene. It certainly will not hurt to call finishTransaction first.

Rob

Thank you so much for the clarification, Rob! I will now update my IAP function slightly and try this out. Hope it finally works. :slight_smile:

If so, I will post my final code here for others to copy. If it doesn’t work, I will post it anyway :wink: to find the errors…

OK, this code now worked well for me both on Android and iOS:

------------------------------------- set IAP variables local btn local store local meinText local platform (...) local textOptions = { parent = sceneGroup, text = "Buy the full version!", x = 20, y = 100, width = 266, font = native.systemFont, fontSize = 16, } meinText = display.newText (textOptions) ------------------------ Start IAP-Funktionen ----------------------------- ------------------------------------- choose correct store platform = system.getInfo( "targetAppStore" ) if platform == "apple" then store = require( "store" ) elseif platform == "google" then store = require( "plugin.google.iap.v3" ) elseif platform == "amazon" then store = require( "plugin.amazon.iap" ) end ------------------------------------- store listener local function StoreListener( event ) if event.transaction.state == "purchased" then -- successful purchase OR Google restore -- (code to unlock the full version) store.finishTransaction(event.transaction) composer.gotoScene( "thankyou" ) elseif event.transaction.state == "restored" then -- Apple or Amazon restore -- (code to unlock the full version) meinText.text = "Some text about success with the restore" store.finishTransaction(event.transaction) elseif event.transaction.state == "cancelled" then -- User decided to cancel purchase meinText.text = "Some text that the process has been cancelled" store.finishTransaction(event.transaction) elseif event.transaction.state == "failed" then -- Something went wrong. User could not purchase item. meinText.text = "Some error text" store.finishTransaction(event.transaction) end end ------------------------------------- wait 1 sec, then init store timer.performWithDelay( 1000, function() store.init( StoreListener ) end ) ------------------------------------- user pressed the button to buy local function buyFullVersion() -- make a purchase btn.alpha = 0 -- hide button if store.isActive == true then meinText.text = "Please wait..." end if platform == "apple" then if store.canMakePurchases then store.purchase({"nameFullVersionApple"}) else meinText.text = "Error: no connection to the app store" end else store.purchase("nameFullVersionGoogle") end end ------------------------------------- button to buy full version btn = widget.newButton ( { defaultFile = "buy1.png", overFile = "buy2.png", left = 20, top = 180, onEvent = buyFullVersion } ) sceneGroup:insert(btn)

Sorry guys for my late reply.

In the end I was not doing anything wrong :frowning:

In-fact my code was correct the first time round, it was just a problem with apple sandbox environment and passwords. :( 

So many hours there punching into an un-breakable wall! Haha.

Anyway my app got accepted and now available in iTunes with iAP :slight_smile:

If anyone does need help I am here to help!

Thanks.

One question regarding this:

  1. Looking at the documentation for store.init() in the main API docs as well as the two plugins .init() functions, they don’t show a provider needed any more. Just call store.init( listenerFunction ).

But is it okay to still use it?

You should use:

store.init( transactionListener )

I don’t know if we made it backwards compatible or not. I would have hoped we would have made the call backwards compatible, but if you’re looking at it, why not go ahead and update the call to the modern way?

Rob

Thanks for the info Rob!