Question: store.init() iOS

I wonder if it can cause problems when calling the store.init() more than once on iOS?

And more in detail if I can do this:

I have a global listener… something like _G. transListener and then call a store.init(_G.transListener) in main.lua

Now I wonder how to handle things from here on when the following was called: store.finishTransaction( event.transaction )

Do I have to init the store again by using store.init(_G.transListener) now or can I still make calls like store.purchase(product) or store.restore() ?

What when I get store.isActive() ==false… do I have to init the store then again?

You should only initialize the store once. You only need one listener function. I recommend calling store.finishTransaction() in the listener before your code ever leaves that listener function. It’s the logical place to do so since you are finished with that transaction. What you do internally (unlocking things, adding coins, etc.) has nothing to do with being done with the store transaction and calling store.finishTransaction() should not generate another transaction event.

To use other store.* API’s in other modules, just simply require the store module and then you can call things like store.purchase() at times where it makes sense in your code.

You should only get store.isActive() returning false if you have not yet initialized the store, but if you do that in main.lua, it should be active, unless the user suspends your app, goes into settings and disables store purchases and then resumes your app. If that’s the case, initializing the store again won’t change the user’s choice. It will still be inactive.

Rob

Thanks for the info.

One more thing: Is there a difference in purchasing on an iPhoneX compared to other devices? We get some player feedback they are purchasing but the game is not unlocking content even when they try to purchase again and got told they already have purchased this product. But it seems the code in the transaction listener handling the purchase or restore state was not called before! And when asking it appears to be a problem happening on iPhoneX.

UPDATE:

It seems the store.purchase is called but there is some different handling on iPhoneX. The Android version also is working fine without any problems.

Here are some more details on how I call the purchase:

I have a table with the products like:

products ={"product1","product2","product3"}

then I do this:

\_G.purchaseAProduct = function (product)          if  store.isActive and store.canMakePurchases then                     store.purchase(product) -- purchasing the first product in the products list                 else        ---     end end

I do the purchasing by calling

\_G.purchaseAProduct (products[1])

… but somehow not getting into the “purchase” or “restored” state of the listener.

One more thing: Players seem to have used touchID… is it possible this is handled differently?

It shouldn’t, but that doesn’t mean that it doesn’t. Is this being reported by production purchases or test purchases? Can you reproduce it?

Can you make a test flight build? Maybe get some testing on it?

Rob

Thank you Rob.

I have one more question (which probably can cause the problems)…

local store = require( "store" ) local json = require( "json" ) -- Transaction listener function local function transactionListener( event )     if not ( event.transaction.state == "failed" ) then  -- Successful transaction         print( json.prettify( event ) )         print( "event.transaction: " .. json.prettify( event.transaction ) )                   if event.transaction.state == "purchased" or event.transaction.state == "restored" then                         ERROR HERE IN CODE!!!                   end     else  -- Unsuccessful transaction; output error details         print( event.transaction.errorType )         print( event.transaction.errorString )     end     -- Tell the store that the transaction is finished     store.finishTransaction( event.transaction ) end -- Initialize Apple IAP store.init( transactionListener )

I normally change a scene when the transaction is finished with store.finishTransaction (event.transaction) BUT this seems not to work with players who encounter the problem. I unlock some game content after the check of event.transaction.state == “purchased” …

so my question (see code above): What happens when an error is appearing after event.transaction.state == “purchased” was checked true and then in the code right before I handle the unlocking of game content an ERROR is happening (Runtime Error).

This could be exactly the problem here, right!? Then all called purchases or restore calls would go right into the event.transaction.state == “purchased” (or restored) but never would get to the store.finishTransaction (event.transaction) part, right?

I think you’re really overcomplicating your logic here. Your current logic will have a “canceled” state actually get into your purchased code.  It’s best to have a linear if-then-elseif-end block to cover each state.

local function storeTransactionListener( event ) print( json.prettify( event )) local transaction = event.transaction if ( transaction.state == "purchased" ) then     store.finishTransaction( event.transaction ) -- do the work you need to do elseif ( transaction.state == "restored" ) then             store.finishTransaction( event.transaction ) -- do the work you need to do elseif ( transaction.state == "cancelled" ) then elseif ( transaction.state == "failed" ) then end end

If you get runtime errors in any of the if-then-elseif blocks then the rest of that block will not execute, so it’s safer to finish the transaction as soon as you can. The IAP servers have no clue about what you’re doing in your code, so there isn’t much of a reason to wait around until you complete your unlock code before you let Apple know you’re done.

Also, be careful using concatenation when printing debug messages:  

print( "event.transaction: " .. json.prettify( event.transaction ) )

Instead, do

print( "event.transaction: ", json.prettify( event.transaction ) )

If for some reason json.prettify() were to return nil, it would cause a runtime error, the second form will not crash.

Rob

Thank you for your detailed help!

You should only initialize the store once. You only need one listener function. I recommend calling store.finishTransaction() in the listener before your code ever leaves that listener function. It’s the logical place to do so since you are finished with that transaction. What you do internally (unlocking things, adding coins, etc.) has nothing to do with being done with the store transaction and calling store.finishTransaction() should not generate another transaction event.

To use other store.* API’s in other modules, just simply require the store module and then you can call things like store.purchase() at times where it makes sense in your code.

You should only get store.isActive() returning false if you have not yet initialized the store, but if you do that in main.lua, it should be active, unless the user suspends your app, goes into settings and disables store purchases and then resumes your app. If that’s the case, initializing the store again won’t change the user’s choice. It will still be inactive.

Rob

Thanks for the info.

One more thing: Is there a difference in purchasing on an iPhoneX compared to other devices? We get some player feedback they are purchasing but the game is not unlocking content even when they try to purchase again and got told they already have purchased this product. But it seems the code in the transaction listener handling the purchase or restore state was not called before! And when asking it appears to be a problem happening on iPhoneX.

UPDATE:

It seems the store.purchase is called but there is some different handling on iPhoneX. The Android version also is working fine without any problems.

Here are some more details on how I call the purchase:

I have a table with the products like:

products ={"product1","product2","product3"}

then I do this:

\_G.purchaseAProduct = function (product)          if  store.isActive and store.canMakePurchases then                     store.purchase(product) -- purchasing the first product in the products list                 else        ---     end end

I do the purchasing by calling

\_G.purchaseAProduct (products[1])

… but somehow not getting into the “purchase” or “restored” state of the listener.

One more thing: Players seem to have used touchID… is it possible this is handled differently?

It shouldn’t, but that doesn’t mean that it doesn’t. Is this being reported by production purchases or test purchases? Can you reproduce it?

Can you make a test flight build? Maybe get some testing on it?

Rob

Thank you Rob.

I have one more question (which probably can cause the problems)…

local store = require( "store" ) local json = require( "json" ) -- Transaction listener function local function transactionListener( event )     if not ( event.transaction.state == "failed" ) then  -- Successful transaction         print( json.prettify( event ) )         print( "event.transaction: " .. json.prettify( event.transaction ) )                   if event.transaction.state == "purchased" or event.transaction.state == "restored" then                         ERROR HERE IN CODE!!!                   end     else  -- Unsuccessful transaction; output error details         print( event.transaction.errorType )         print( event.transaction.errorString )     end     -- Tell the store that the transaction is finished     store.finishTransaction( event.transaction ) end -- Initialize Apple IAP store.init( transactionListener )

I normally change a scene when the transaction is finished with store.finishTransaction (event.transaction) BUT this seems not to work with players who encounter the problem. I unlock some game content after the check of event.transaction.state == “purchased” …

so my question (see code above): What happens when an error is appearing after event.transaction.state == “purchased” was checked true and then in the code right before I handle the unlocking of game content an ERROR is happening (Runtime Error).

This could be exactly the problem here, right!? Then all called purchases or restore calls would go right into the event.transaction.state == “purchased” (or restored) but never would get to the store.finishTransaction (event.transaction) part, right?

I think you’re really overcomplicating your logic here. Your current logic will have a “canceled” state actually get into your purchased code.  It’s best to have a linear if-then-elseif-end block to cover each state.

local function storeTransactionListener( event ) print( json.prettify( event )) local transaction = event.transaction if ( transaction.state == "purchased" ) then     store.finishTransaction( event.transaction ) -- do the work you need to do elseif ( transaction.state == "restored" ) then             store.finishTransaction( event.transaction ) -- do the work you need to do elseif ( transaction.state == "cancelled" ) then elseif ( transaction.state == "failed" ) then end end

If you get runtime errors in any of the if-then-elseif blocks then the rest of that block will not execute, so it’s safer to finish the transaction as soon as you can. The IAP servers have no clue about what you’re doing in your code, so there isn’t much of a reason to wait around until you complete your unlock code before you let Apple know you’re done.

Also, be careful using concatenation when printing debug messages:  

print( "event.transaction: " .. json.prettify( event.transaction ) )

Instead, do

print( "event.transaction: ", json.prettify( event.transaction ) )

If for some reason json.prettify() were to return nil, it would cause a runtime error, the second form will not crash.

Rob

Thank you for your detailed help!