I changed that and it is still not working, I still get two windows opening at the same time and making the purchase fail…
I’ve worked on it again and still did not fix this error…
Could anyone please send me a sample code which works on a device?
Ed is on to something. First you are creating your display objects in scene:show() instead of scene:create(). I understand why people want to do this but it’s really problematic. In this case, you have to understand:
scene:show() gets called twice every time a scene is entered.
It gets gets called once while the scene is off screen and it gets called a second time after the scene has been transitioned on the screen. Every scene:show() function really should read:
function scene:show( event ) local sceneGroup = self.view local phase = event.phase if event.phase == "will" then -- the scene is about to transition on screen -- do stuff here before the scene transitions. -- If you insist on creating objects in scene show they should be created -- inside this if block else -- the "did" phase, the scene is on screen at this point -- start timers, transitions, physics etc. end end
Anything not inside that if-then-else gets run twice. That means in your code, your buttons get created twice on top of each other. Its possible that when you tap your button it falls through to object below it and the second copy of the button is getting pressed.
Now for creating things in scene:show(). People want to do this because two reasons. One is due to a lovely programming term called “scope”. The second one is due to the way Composer wants to cache created objects, i.e. scene:create() doesn’t get called every time the scene is shown.
The second reason shouldn’t be an issue, but the first one is in particular for newer developers. I’m not going to attempt to explain “scope” here. We have a great tutorial on it. https://coronalabs.com/blog/2015/06/16/tutorial-scope-for-beginners/
But the sum of this is, languages like C are two pass compilers. Its normal to have the main() function at the top, and the functions called from main() below it in code. Two pass compilers make a first run to collect the names of all the symbols and it goes back in and fills in all the addresses. Lua however, is in effect a one pass compiler. Names are not know until they are defined the first time. This means your main function (i.e. scene:show(), scene:create()) have to be at the bottom of the code and any functions that need called are defined above them. If you do this you wont need to have functions inside scene:show().
I know this has little to do with your immediate problem, but it has to do with how you’re using scene:show() and that could be creating your problems.
You said you changed that. It would really help to see your latest code.
Thanks for your reply and tried to follow it for my new code. I am not sure I placed the right code at the right place but I tried to follow the tutorial.
Here is my new code which still makes two windows open:
local composer = require( "composer" ) local scene = composer.newScene() local widget = require( "widget" ) local myData = require("myData") local json = require( "json" ) local iap = require("plugin.iap\_badger") local spinner=nil --Create the catalogue local catalogue = { --Information about the product on the app stores products = { --removeAds is the product identifier. --Always use this identifier to talk to IAP Badger about the purchase. fullGameNoAds = { --A list of product names or identifiers specific to apple's App Store or Google Play. productNames = { apple="goldrush.fullgame.4774.5689", google="goldrush.fullgame.4774.5689", amazon="goldrush.fullgame.4774.5689"}, --The product type productType = "non-consumable", --This function is called when a purchase is complete. onPurchase=function() iap.setInventoryValue("unlock", true) end, --The function is called when a refund is made onRefund=function() iap.removeFromInventory("unlock", true) end, } }, --Information about how to handle the inventory item inventoryItems = { unlock = { productType="non-consumable" } } } --Called when any purchase fails local function failedListener() --If the spinner is on screen, remove it if (spinner) then spinner:removeSelf() spinner=nil end end --This table contains all of the options we need to specify in this example program. local iapOptions = { --The catalogue generated above catalogue=catalogue, --The filename in which to save the inventory filename="example1.txt", --Salt for the hashing algorithm salt = "something tr1cky to gue55!", --Listeners for failed and cancelled transactions will just remove the spinner from the screen failedListener=failedListener, cancelledListener=failedListener, --Once the product has been purchased, it will remain in the inventory. Uncomment the following line --to test the purchase functions again in future. It's also useful for testing restore purchases. --doNotLoadInventory=true, --debugMode=true, } --Initialise IAP badger iap.init(iapOptions) --------------------------------- -- -- Making purchases -- --------------------------------- --The functionality for removing the ads from the screen has been put in a separate --function because it will be called from the purchaseListener and the restoreListener --functions local function fullGameNoAds() --Remove the advertisement (need to check it's there first - if this function --is called from a product restore, it may not have been created) print("okForTransaction2") myData.transition.ok = "true" loadsave.saveTable( myData.transition, "transition.json", system.DocumentsDirectory ) composer.removeScene("mainmenu") composer.gotoScene("mainmenu") end --Called when the relevant app store has completed the purchase local function purchaseListener(product ) --Remove the spinner spinner:removeSelf() spinner=nil --Remove the ads fullGameNoAds() --Save the inventory change iap.saveInventory() --Give the user a message saying the purchase was successful native.showAlert("Info", "Your purchase was successful", {"Okay"}) print("okForTransaction1") end --Purchase function --Most of the code in this function places a spinner on screen to prevent any further user interaction with --the screen. The actual code to initiate the purchase is the single line iap.purchase("removeAds"...) buyUnlock=function() --Place a progress spinner on screen and tell the user the app is contating the store local spinnerBackground = display.newRect(960,960,1920,1920) spinnerBackground:setFillColor(1,1,1,0.75) --Spinner consumes all taps so the user cannot tap the purchase button twice spinnerBackground:addEventListener("tap", function() return true end) local spinnerText = display.newText("Contacting " .. iap.getStoreName() .. "...", 540,700, native.systemFont, 80) spinnerText:setFillColor(0,0,0) --Add a little spinning rectangle local spinnerRect = display.newRect(540,960,100,100) spinnerRect:setFillColor(0.25,0.25,0.25) transition.to(spinnerRect, { time=4000, rotation=360, iterations=999999, transition=easing.inOutQuad}) --Create a group and add all these objects to it spinner=display.newGroup() spinner:insert(spinnerBackground) spinner:insert(spinnerText) spinner:insert(spinnerRect) --Tell IAP to initiate a purchase iap.purchase("fullGameNoAds", purchaseListener) print("okForTransaction0") end local function restoreListener(productName, event) --If this is the first product to be restored, remove the spinner --(Not really necessary in a one-product app, but I'll leave this as template --code for those of you writing apps with multi-products). if (event.firstRestoreCallback) then --Remove the spinner from the screen spinner:removeSelf() spinner=nil --Tell the user their items are being restore native.showAlert("Restore", "Your items are being restored", {"Okay"}) end --Remove the ads if (productName=="goldrush.fullgame.4774.5689") then fullGameNoAds() end --Save any inventory changes iap.saveInventory() end --Restore function --Most of the code in this function places a spinner on screen to prevent any further user interaction with --the screen. The actual code to initiate the purchase is the single line iap.restore(false, ...) local function restorePurchases() --Place a progress spinner on screen and tell the user the app is contating the store local spinnerBackground = display.newRect(960,960,1920,1920) spinnerBackground:setFillColor(1,1,1,0.75) --Spinner consumes all taps so the user cannot tap the purchase button twice spinnerBackground:addEventListener("tap", function() return true end) local spinnerText = display.newText("Contacting " .. iap.getStoreName() .. "...", 540,700, native.systemFont, 80) spinnerText:setFillColor(0,0,0) --Add a little spinning rectangle local spinnerRect = display.newRect(540,960,100,100) spinnerRect:setFillColor(0.25,0.25,0.25) transition.to(spinnerRect, { time=4000, rotation=360, iterations=999999, transition=easing.inOutQuad}) --Create a group and add all these objects to it spinner=display.newGroup() spinner:insert(spinnerBackground) spinner:insert(spinnerText) spinner:insert(spinnerRect) --Tell IAP to initiate a purchase --Use the failedListener from onPurchase, which just clears away the spinner from the screen. --You could have a separate function that tells the user "Unable to contact the app store" or --similar on a timeout. --On the simulator, or in debug mode, this function attempts to restore all of the non-consumable --items in the catalogue. iap.restore(false, restoreListener, failedListener) end myData.creditsWindowOpened = "false" local function handleButton2( event ) if ( "ended" == event.phase ) then if myData.sttingsWindowOpened == "false" then if myData.settings.soundEffects == "true" then audio.play(mySoundEffect) end composer.gotoScene ("Defies menu") end end end local function handlePurchase( event ) if ( "ended" == event.phase ) then if myData.sttingsWindowOpened == "false" then if myData.settings.soundEffects == "true" then audio.play(mySoundEffect) end print("ok") buyUnlock() end end end function scene:create( event ) local sceneGroup = self.view local background = display.newImageRect("Main menu screen.png", 1080, 1920) background.x = 540 ; background.y = 960 sceneGroup:insert( background ) local title = display.newImage ("Title.png") title.x = 650; title.y = 350 sceneGroup:insert( title ) local titleCoin = display.newImage ("Title coin.png") titleCoin.x = 125; titleCoin.y = 350 sceneGroup:insert( titleCoin ) local mySoundEffect = audio.loadSound("Sound effect.mp3") -- Add the other windows later print( "\nmainmenu: create event" ) end function scene:show( event ) local sceneGroup = self.view print( "mainmenu: show event" ) local phase = event.phase if ( phase == "will" ) then -- Called when the scene is still off screen (but is about to come on screen). if myData.transition.ok == "true" then --Otherwise add a tap listener to the button that unlocks the game print("ok1") print(iap.getInventoryValue("unlock")) print("ok2") local defiesBtn = widget.newButton { width = 552, height = 244, defaultFile = "Defies button.png", overFile = "Defies button pressed.png", onRelease = handleButton2 } defiesBtn.x = 540; defiesBtn.y = 1050 sceneGroup:insert( defiesBtn ) else print("ok1") print(iap.getInventoryValue("unlock")) print("ok2") local defiesLockedBtn = widget.newButton { width = 552, height = 244, defaultFile = "Defies locked.png", overFile = "Defies locked pressed.png", onRelease = handlePurchase } defiesLockedBtn.x = 540; defiesLockedBtn.y = 1050 sceneGroup:insert( defiesLockedBtn ) end else end end function scene:hide() if btnAnim then transition.cancel( btnAnim ); end print( "mainmenu: hide event" ) end -- Called prior to the removal of scene's "view" (display group) function scene:destroy( event ) print( "((destroying mainmenu's view))" ) end --------------------------------------------------------------------------------- -- END OF YOUR IMPLEMENTATION --------------------------------------------------------------------------------- -- "create" event is dispatched if scene's view does not exist scene:addEventListener( "create", scene ) -- "show" event is dispatched whenever scene transition has finished scene:addEventListener( "show", scene ) -- "hide" event is dispatched before next scene's transition begins scene:addEventListener( "hide", scene ) -- "destroy" event is dispatched before view is unloaded, which can be scene:addEventListener( "destroy", scene ) --------------------------------------------------------------------------------- return scene
Thanks in advance for your replies
That looks better and you will be able to support yourself in the long term. However, I don’t see a reason why you would be getting two calls to the purchase function. Can you capture the console log for the run. I’d like to see the values printed as you do this.
At this point, the previous scene could be evoking this scene twice, or the issue is with iap.badger. Let’s look at the console output and see what’s going on.
Rob
Here is the console output:
--------- beginning of system --------- beginning of main D/Corona (21007): Controller.startTimer(): Abort because it hasn't been set! V/Corona (21007): \> Class.forName: network.LuaLoader V/Corona (21007): \< Class.forName: network.LuaLoader V/Corona (21007): Loading via reflection: network.LuaLoader I/Corona (21007): Platform: SM-G531F / ARM Neon / 5.1.1 / Vivante GC7000UL / OpenGL ES 3.0 / 2015.2731 V/Corona (21007): \> Class.forName: plugin.fuse.LuaLoader V/Corona (21007): \> Class.forName: CoronaProvider.licensing.google.LuaLoader V/Corona (21007): \< Class.forName: CoronaProvider.licensing.google.LuaLoader V/Corona (21007): Loading via reflection: CoronaProvider.licensing.google.LuaLoader E/Corona (21007): Invalid public key E/Corona (21007): java.lang.IllegalArgumentException: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.generatePublicKey(LicenseChecker.java:121) E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.\<init\>(LicenseChecker.java:92) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader.initLicenseChecker(LuaLoader.java:173) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader.init(LuaLoader.java:143) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader$InitWrapper.invoke(LuaLoader.java:418) E/Corona (21007): at com.ansca.corona.JavaToNativeShim.nativeResize(Native Method) E/Corona (21007): at com.ansca.corona.JavaToNativeShim.resize(JavaToNativeShim.java:372) E/Corona (21007): at com.ansca.corona.graphics.opengl.CoronaGLSurfaceView$CoronaRenderer.onSurfaceChanged(CoronaGLSurfaceView.java:374) E/Corona (21007): at com.ansca.corona.graphics.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1612) E/Corona (21007): at com.ansca.corona.graphics.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1378) E/Corona (21007): Caused by: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:155) E/Corona (21007): at com.android.org.conscrypt.OpenSSLRSAKeyFactory.engineGeneratePublic(OpenSSLRSAKeyFactory.java:47) E/Corona (21007): at java.security.KeyFactory.generatePublic(KeyFactory.java:172) E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.generatePublicKey(LicenseChecker.java:112) E/Corona (21007): ... 9 more E/Corona (21007): Caused by: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.android.org.conscrypt.NativeCrypto.d2i\_PUBKEY(Native Method) E/Corona (21007): at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:153) E/Corona (21007): ... 12 more I/Corona (21007): ok V/Corona (21007): \> Class.forName: plugin.google.iap.v3.LuaLoader V/Corona (21007): \< Class.forName: plugin.google.iap.v3.LuaLoader V/Corona (21007): Loading via reflection: plugin.google.iap.v3.LuaLoader I/Corona (21007): I/Corona (21007): mainmenu: create event I/Corona (21007): mainmenu: show event I/Corona (21007): ok1 I/Corona (21007): nil I/Corona (21007): ok2 I/Corona (21007): main loaded I/Corona (21007): mainmenu: show event I/Corona (21007): ok I/Corona (21007): okForTransaction0 D/Corona (21007): Controller.startTimer(): Abort because it hasn't been set!
Any ideas?
I still can’t find any solutions…
None of your print statements printed twice. This tells me it isn’t in your code. Check with @happymongoose the author of IAP badger for further support.
You probably should fix that licensing issue before you proceed though.
Rob
Ok.
What is the licensing issue?
E/Corona (21007): Invalid public key E/Corona (21007): java.lang.IllegalArgumentException: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.generatePublicKey(LicenseChecker.java:121) E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.\<init\>(LicenseChecker.java:92) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader.initLicenseChecker(LuaLoader.java:173) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader.init(LuaLoader.java:143) E/Corona (21007): at CoronaProvider.licensing.google.LuaLoader$InitWrapper.invoke(LuaLoader.java:418) E/Corona (21007): at com.ansca.corona.JavaToNativeShim.nativeResize(Native Method) E/Corona (21007): at com.ansca.corona.JavaToNativeShim.resize(JavaToNativeShim.java:372) E/Corona (21007): at com.ansca.corona.graphics.opengl.CoronaGLSurfaceView$CoronaRenderer.onSurfaceChanged(CoronaGLSurfaceView.java:374) E/Corona (21007): at com.ansca.corona.graphics.opengl.GLSurfaceView$GLThread.guardedRun(GLSurfaceView.java:1612) E/Corona (21007): at com.ansca.corona.graphics.opengl.GLSurfaceView$GLThread.run(GLSurfaceView.java:1378) E/Corona (21007): Caused by: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:155) E/Corona (21007): at com.android.org.conscrypt.OpenSSLRSAKeyFactory.engineGeneratePublic(OpenSSLRSAKeyFactory.java:47) E/Corona (21007): at java.security.KeyFactory.generatePublic(KeyFactory.java:172) E/Corona (21007): at com.google.android.vending.licensing.LicenseChecker.generatePublicKey(LicenseChecker.java:112) E/Corona (21007): ... 9 more E/Corona (21007): Caused by: java.lang.RuntimeException: error:0D07209B:asn1 encoding routines:ASN1\_get\_object:too long E/Corona (21007): at com.android.org.conscrypt.NativeCrypto.d2i\_PUBKEY(Native Method) E/Corona (21007): at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:153) E/Corona (21007): ... 12 more
Licensing is used to make sure your app isn’t hacked. It looks like you only have it partially setup. See:
Ok, I will have a look at that, thank you very much for your help.
What do you mean when you say: “Everything works fine in debug mode but as soon as I remove the debug mode”.
What debug mode? Please provide a good description of what you’re doing?
Line 67 of my second code posted is written: --debugMode=true.
In fact, when I turn this to true, there is a window opening (it only opens once and not twice) and saying:
“Purchased initiated for item: “Product ID”. What responses would you like to give?”
Then, we can press “failed”, “cancelled” and “successeful” buttons.
And, when I press “successeful”, everything works fine, as expected.
But then, when I comment debugMode=true to test the purchase in real conditions, I have these two windows opening.
I hope I have been precise enough.
That’s a parameter to IAP Badger. Please contact the plugin provider @happymongoose for help with that. We have no visibility into what that flag does.
Your problem might be in your fullGameNoAds listener here:
composer.removeScene("mainmenu") composer.gotoScene("mainmenu")
You are currently in mainmenu. And now you try to remove the current scene and recreate it. This is a problem. What are you trying to do exactly? Try removing these lines and see if you still get the double popup.
Thanks for your reply. I removed it a few days ago and I still get the issue.