Hello! I’m tired of trying to fix this. Please, help guys, Apple still and still rejecting my app. Here is the “letter of happiness”:
We found that your app initiates the In App Purchase process in a non-standard manner.
To restore previously purchased In App Purchase products, it would be appropriate to provide a “Restore” button and initiate the restore process when the “Restore” button is tapped by the user. For discrete code-level questions, you may wish to consult with Apple Developer Technical Support. Please be sure to:
- include the complete details of your rejection issues
- prepare any symbolicated crash logs, screenshots, and steps to reproduce the issues for when the DTS engineer follows up.
For information on how to symbolicate and read a crash log, please see Tech Note TN2151 Understanding and Analyzing iPhone OS Application Crash Reports.
If you have difficulty reproducing this issue, please try testing the workflow as described in <https://developer.apple.com/library/ios/qa/qa1764/>Technical Q&A QA1764: How to reproduce a crash or bug that only App Review or users are seeing.
Here is my shop.lua code.
local tapsound = audio.loadSound( "click.m4a" ) local soundtrack = audio.loadStream( "main\_theme.m4a" ) local gameMusicChannel = audio.play( soundtrack, { channel=1, loops=-1 } ) local transManager = require "transManager" transManager.speed = 1 local ui = require("ui") local GGData = require "GGData" local allBot = GGData:new("allBot") allBot:setIfNew("allBot", "no") allBot:save() local absinth = GGData:new("absinth") absinth:setIfNew("absinth", "no") absinth:save() local beer = GGData:new("beer") beer:setIfNew("beer", "no") beer:save() local champagne = GGData:new("champagne") champagne:setIfNew("champagne", "no") champagne:save() local cognac = GGData:new("cognac") cognac:setIfNew("cognac", "no") cognac:save() local port = GGData:new("port") port:setIfNew("port", "no") port:save() local vermouth = GGData:new("vermouth") vermouth:setIfNew("vermouth", "no") vermouth:save() local champagne2 = GGData:new("champagne2") champagne2:setIfNew("champagne2", "no") champagne2:save() local xo = GGData:new("xo") xo:setIfNew("xo", "no") xo:save() local whiskey = GGData:new("whiskey") whiskey:setIfNew("whiskey", "no") whiskey:save() screenH = display.contentHeight screenW = display.contentWidth local localGroup = display.newGroup() local infoString local PriceOne local PriceTwo local PriceThree local PriceFour local PriceFive local PriceSix local PriceSeven local PriceEight local PriceNine local PriceTen --------------------- local restoreFunction = function ( event ) if event.phase == "release" then local function onCompleteX( event ) if "clicked" == event.action then local i = event.index if 1 == i then store.restore({"absinth\_bottle"}) infoString = "Restoring stuff" elseif 2 == i then -- Open URL if "Learn More" (the 2nd button) was clicked end end end -- Show alert with two buttons local alertX = native.showAlert( "Shop", "Restore Purchases?", { "Yes", "No" }, onCompleteX ) end end local restoreBtn = ui.newButton{ default="restore.png", over="restore.png", width=312, height=70, onEvent = restoreFunction, id = "AboutBtn" } restoreBtn.x = screenW/1.15 restoreBtn.y = screenH/1.07 restoreBtn.xScale = 1 restoreBtn.yScale = 1 local store = require "store" -----------IN-APP-INIT local listOfProducts = { -- These Product IDs must already be set up in your store -- Note, this simple test only has room for about 4 items, please adjust accordingly -- The iTunes store will not validate bad Product IDs -- Replace with a valid Product ID from iTunes Connect "all\_bottle", "absinth\_bottle", "beer\_bottle", "champagne\_bottle", "cognac\_bottle", "port\_bottle", "vermouth\_bottle", "champagne2\_bottle", "xo\_bottle", "whiskey\_bottle" } ---------------------------------- -- In-App area ---------------------------------- local validProducts = {} local invalidProducts = {} local unpackValidProducts = function() print ("Loading product list") if not validProducts then native.showAlert( "In-App features not available", "initStore() failed", { "OK" } ) else print( "Found " .. #validProducts .. " valid items ") infoString = ( "Found " .. #validProducts .. " valid items ") print(validProducts[1].localizedPrice) PriceOne = (validProducts[1].localizedPrice) print(validProducts[2].localizedPrice) PriceTwo = (validProducts[2].localizedPrice) print(validProducts[3].localizedPrice) PriceThree = (validProducts[3].localizedPrice) print(validProducts[4].localizedPrice) PriceFour = (validProducts[4].localizedPrice) print(validProducts[5].localizedPrice) PriceFive = (validProducts[5].localizedPrice) print(validProducts[6].localizedPrice) PriceSix = (validProducts[6].localizedPrice) print(validProducts[7].localizedPrice) PriceSeven = (validProducts[7].localizedPrice) print(validProducts[8].localizedPrice) PriceEight = (validProducts[8].localizedPrice) print(validProducts[9].localizedPrice) PriceNine = (validProducts[9].localizedPrice) print(validProducts[10].localizedPrice) PriceTen = (validProducts[10].localizedPrice) for i=1, #invalidProducts do -- Debug: display the product info native.showAlert( "Item " .. invalidProducts[i] .. " is invalid.",{ "OK" } ) print("Item " .. invalidProducts[i] .. " is invalid.") end end end local loadProductsCallback = function( event ) -- Debug info for testing print("loadProductsCallback()") print("event, event.name", event, event.name) print(event.products) print("#event.products", #event.products) validProducts = event.products invalidProducts = event.invalidProducts unpackValidProducts () end local transactionCallback = function( event ) if event.transaction.state == "purchased" then print("Transaction successful!") infoString = "Transaction successful!" if event.transaction.productIdentifier == "all\_bottle" then allBot:set("allBot", "yes") allBot:save() absinth:set("absinth", "yes") absinth:save() beer:set("beer", "yes") beer:save() champagne:set("champagne", "yes") champagne:save() cognac:set("cognac", "yes") cognac:save() port:set("port", "yes") port:save() vermouth:set("vermouth", "yes") vermouth:save() champagne2:set("champagne2", "yes") champagne2:save() xo:set("xo", "yes") xo:save() whiskey:set("whiskey", "yes") whiskey:save() end if event.transaction.productIdentifier == "absinth\_bottle" then absinth:set("absinth", "yes") absinth:save() end if event.transaction.productIdentifier == "beer\_bottle" then beer:set("beer", "yes") beer:save() end if event.transaction.productIdentifier == "champagne\_bottle" then champagne:set("champagne", "yes") champagne:save() end if event.transaction.productIdentifier == "cognac\_bottle" then cognac:set("cognac", "yes") cognac:save() end if event.transaction.productIdentifier == "port\_bottle" then port:set("port", "yes") port:save() end if event.transaction.productIdentifier == "vermouth\_bottle" then vermouth:set("vermouth", "yes") vermouth:save() end if event.transaction.productIdentifier == "champagne2\_bottle" then champagne2:set("champagne2", "yes") champagne2:save() end if event.transaction.productIdentifier == "xo\_bottle" then xo:set("xo", "yes") xo:save() end if event.transaction.productIdentifier == "whiskey\_bottle" then whiskey:set("whiskey", "yes") whiskey:save() end local function onComplete( event ) if "clicked" == event.action then local i = event.index if 1 == i then circle.alpha=0 tl = timer.performWithDelay(500, function() director:changeScene("shop", "fade") timer.cancel(tl) end, 1) end end end -- Show alert with two buttons local alert = native.showAlert( "Shop", "Thank you! Your purchase is done!", { "OK" }, onComplete) elseif event.transaction.state == "restored" then print("Restoring purchases") if event.transaction.productIdentifier == "all\_bottle" then allBot:set("allBot", "yes") allBot:save() absinth:set("absinth", "yes") absinth:save() beer:set("beer", "yes") beer:save() champagne:set("champagne", "yes") champagne:save() cognac:set("cognac", "yes") cognac:save() port:set("port", "yes") port:save() vermouth:set("vermouth", "yes") vermouth:save() champagne2:set("champagne2", "yes") champagne2:save() xo:set("xo", "yes") xo:save() whiskey:set("whiskey", "yes") whiskey:save() end if event.transaction.productIdentifier == "absinth\_bottle" then absinth:set("absinth", "yes") absinth:save() end if event.transaction.productIdentifier == "beer\_bottle" then beer:set("beer", "yes") beer:save() end if event.transaction.productIdentifier == "champagne\_bottle" then champagne:set("champagne", "yes") champagne:save() end if event.transaction.productIdentifier == "cognac\_bottle" then cognac:set("cognac", "yes") cognac:save() end if event.transaction.productIdentifier == "port\_bottle" then port:set("port", "yes") port:save() end if event.transaction.productIdentifier == "vermouth\_bottle" then vermouth:set("vermouth", "yes") vermouth:save() end if event.transaction.productIdentifier == "champagne2\_bottle" then champagne2:set("champagne2", "yes") champagne2:save() end if event.transaction.productIdentifier == "xo\_bottle" then xo:set("xo", "yes") xo:save() end if event.transaction.productIdentifier == "whiskey\_bottle" then whiskey:set("whiskey", "yes") whiskey:save() end local function onComplete( event ) if "clicked" == event.action then local i = event.index if 1 == i then circle.alpha=0 tl = timer.performWithDelay(500, function() director:changeScene("shop", "fade") timer.cancel(tl) end, 1) end end end -- Show alert with two buttons local alert = native.showAlert( "Shop", "Thank you! Your purchase is Restored!", { "OK" }, onComplete) elseif event.transaction.state == "cancelled" then print("Transaction cancelled by user.") --infoString = "Transaction canceled by user!" circle.alpha=0 elseif event.transaction.state == "failed" then print("Transaction failed, type: ", event.transaction.errorType, event.transaction.errorString) local alert = native.showAlert("Failed ", infoString,{ "OK" }) circle.alpha=0 else print("Unknown event") local alert = native.showAlert("Unknown ", infoString,{ "OK" }) circle.alpha=0 end -- Tell the store we are done with the transaction. -- If you are providing downloadable content, do not call this until -- the download has completed. store.finishTransaction( event.transaction ) circle.alpha=0 end local setupMyStore = function(event) store.loadProducts( listOfProducts, loadProductsCallback ) print ("After store.loadProducts(), waiting for callback") end ------ONE OF MY ITEMS TO BUY local whiskeyBot = display.newImageRect("whiskey.png", 106, 205) whiskeyBot.x = screenW/1.28 whiskeyBot.y = screenH/1.42 whiskeyBot.alpha=0 local whiskeyFunc = function(event) if event.phase == "ended" then circle.x = whiskeyBot.x circle.y = whiskeyBot.y circle.alpha=1 audio.play( tapsound, {channel=2}) -- Handler that gets notified when the alert closes if require("socket").connect("google.com", 80) == nil then print("No connection") local function onComplete( event ) if "clicked" == event.action then local i = event.index if 1 == i then circle.alpha=0 end end end -- Show alert with two buttons native.showAlert( "Shop", "Store is not available. Please check your internet connection.", { "OK" }, onComplete ) else local buyWhiskey = function ( product ) print ("Congrats! Purchasing " ..product) -- Purchase the item if store.canMakePurchases then infoString = "Can make purchases!" store.purchase( {"whiskey\_bottle"} ) else native.showAlert("Store purchases are not available, please try again later", { "OK" } ) end end -- Enter your product ID here buyWhiskey("whiskey\_bottle") end end end whiskeyBot:addEventListener("touch", whiskeyFunc) local whiskeyTXT = display.newText( "buy", screenW, screenH/2, "Benguiat", 27 ) whiskeyTXT:setTextColor( 90,90,90 ) whiskeyTXT.alpha=0 whiskeyTXT.x = screenW/1.28 whiskeyTXT.y = screenH/1.21 ------------------ local checkBottles = function() if allBot:get("allBot") == "no" then allBottles.alpha=1 allBottlesTXT.alpha=1 else allBottles.alpha=0 allBottlesTXT.alpha=0 end if absinth:get("absinth") == "no" then absinthBot.alpha=1 absinthTXT.alpha=1 else absinthBot.alpha=0 absinthTXT.alpha=0 end if beer:get("beer") == "no" then beerBot.alpha=1 beerTXT.alpha=1 else beerBot.alpha=0 beerTXT.alpha=0 lightBeer.alpha=0 end if champagne:get("champagne") == "no" then champagneBot.alpha=1 champagneTXT.alpha=1 else champagneBot.alpha=0 champagneTXT.alpha=0 end if cognac:get("cognac") == "no" then cognacBot.alpha=1 cognacTXT.alpha=1 else cognacBot.alpha=0 cognacTXT.alpha=0 lightport.alpha=0 end if port:get("port") == "no" then portBot.alpha=1 portTXT.alpha=1 else portBot.alpha=0 portTXT.alpha=0 end if vermouth:get("vermouth") == "no" then vermouthBot.alpha=1 vermouthTXT.alpha=1 else vermouthBot.alpha=0 vermouthTXT.alpha=0 lightvermouth.alpha=0 end if champagne2:get("champagne2") == "no" then champagne2Bot.alpha=1 champagne2TXT.alpha=1 else champagne2Bot.alpha=0 champagne2TXT.alpha=0 end if xo:get("xo") == "no" then xoBot.alpha=1 xoTXT.alpha=1 else xoBot.alpha=0 xoTXT.alpha=0 lightxo.alpha=0 end if whiskey:get("whiskey") == "no" then whiskeyBot.alpha=1 whiskeyTXT.alpha=1 else whiskeyBot.alpha=0 whiskeyTXT.alpha=0 end end checkBottles() --------DEBUG\_CONSOLE local systemText = display.newText( "0", screenW, screenH/2, "Benguiat", 16 ) systemText:setTextColor(255, 255, 255) systemText.x = screenW/1.7 systemText.y = screenH/1.08 systemText.alpha=0 local consoleUpdate = function(event) systemText.text = infoString or 0 allBottlesTXT.text = PriceTwo or "BUY" absinthTXT.text = PriceOne or "buy" beerTXT.text = PriceThree or "buy" champagneTXT.text = PriceFour or "buy" champagne2TXT.text = PriceFive or "buy" xoTXT.text = PriceSix or "buy" whiskeyTXT.text = PriceSeven or "buy" cognacTXT.text = PriceEight or "buy" portTXT.text = PriceNine or "buy" vermouthTXT.text = PriceTen or "buy" end Runtime:addEventListener("enterFrame", consoleUpdate) ------APP-TURNON----- local shopInit = function() -- Connect to store at startup, if available. if store.availableStores.apple then print("Using Apple's in-app purchase system.") infoString = "Using Apple's in-app purchase system." store.init("apple", transactionCallback) timer.performWithDelay (500, setupMyStore) elseif store.availableStores.google then print("Using Google's Android In-App Billing system.") infoString = "Using Google's Android In-App Billing system." store.init("google", transactionCallback) else print("In-app purchases is not supported on this system/device.") infoString = "In-app purchases is not supported on this system/device." end end shopInit() ------APP-TURNON-----
Thanks in advance! You will be my saviors!