IAP help: Problem with testing and question about restore()

Hi all,
I’m in the middle of testing, my first, IAP app.
Thanks to all the great info on the Ansca web site I’ve got a lot up and running already. Thanks!!

Ive got one problem and one question.

store.restore()
When you call this the transaction.state in the transactionCallback routine is set to “restored”.
The transaction table comes with info about the restored IAP.
But what happens if a user has, say, 10 IAP bought and wants to restore them?
Is the transactionCallback called 10 times then, each with it’s own transaction.productIdentifier, etc?
Or is there a table in which the transaction table for each of the 10 purchases is stored?

Test buying isn’t working
My app show my one test IAP that I’ve created on iTunes Connect. Great!
So when I tap it (logged out of my normal iTunes account), the app calls the store.purchase(id) routine. iTunes ask a confirmation, so I tap Buy.
Then I have to enter my credentials.
After I enter my test user (made in iTunes Connect) account stuff iTunes says it needs verification and I have to press Continue (it’s in Dutch at the moment so I’m not sure if Continue is the correct English equivalent :slight_smile: ). It also states that I’m in the Sandbox.
So I enter my password again.
Then it switches to the App Store and drops my app to the background.
In the App store I get a rather empty message box stating [Environment: Sandbox] with Cancel and Continue. On the background I see a floating empty window on top of the App Store.
Continue asks me my password again. This keeps repeating.

Any ideas what is going wrong?

Cheers [import]uid: 123200 topic_id: 26259 reply_id: 326259[/import]

HI, I am also interested about your first question about restore…It’s a good one :slight_smile:

Regarding the second, can you provide some code?
Heres a working sample of code to buy and list some stuff from appstore

[code]
store = require(“store”)

local screenW = display.contentWidth
local screenH = display.contentHeight

local ScreenText = display.newText(“PRODUKT INFO:”, 50, 100, 500,400, native.systemFont, 14)
ScreenText:setTextColor(255, 255, 255)
ScreenText:setReferencePoint(display.TopLeftReferencePoint);

local StatusText = display.newText(“STATUS:”, 50, 300, 500,400, native.systemFont, 14)
StatusText:setTextColor(255, 255, 255)
StatusText:setReferencePoint(display.TopLeftReferencePoint);

function transactionCallback( event )
local transaction = event.transaction
if transaction.state == “purchased” then
print(“Transaction succuessful!”)
StatusText.text = “Transaction succuessful!”

elseif transaction.state == “restored” then
StatusText.text = “Transaction restored (from previous session)”
print(“Transaction restored (from previous session)”)
print(“productIdentifier”, transaction.productIdentifier)
print(“receipt”, transaction.receipt)
print(“transactionIdentifier”, transaction.identifier)
print(“date”, transaction.date)
print(“originalReceipt”, transaction.originalReceipt)
print(“originalTransactionIdentifier”, transaction.originalIdentifier)
print(“originalDate”, transaction.originalDate)

elseif transaction.state == “cancelled” then
print(“User cancelled transaction”)
StatusText.text = “User cancelled transaction”

elseif transaction.state == “failed” then
print(“Transaction failed, type:”, transaction.errorType, transaction.errorString)
StatusText.text = “Transaction failed, type:”, transaction.errorType, transaction.errorString

else
StatusText.text = “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.

–Uppdatera databasen och kräv en omstart av spelet!

store.finishTransaction( transaction )
end

store.init(“apple”, transactionCallback)

function loadProductsCallback( event )
print(“showing products”, #event.products)
local str = “”
ScreenText.text = #event.products
for i=1, #event.products do
local currentItem = event.products[i]
str = str … (currentItem.title … ", "… currentItem.price … " SEK, " …currentItem.productIdentifier … “\n”)
print(currentItem.description)
print(currentItem.price)
print(currentItem.productIdentifier)
end
print(“showing invalidProducts”, #event.invalidProducts)
for i=1, #event.invalidProducts do
print(event.invalidProducts[i])
end
ScreenText.text = str;
end

arrayOfProductIdentifiers =
{
“”,""
}

store.loadProducts( arrayOfProductIdentifiers, loadProductsCallback )

local buy = function( event)
if event.phase==“ended” then
store.purchase{ “” }
end
end

local restore = function( event)
if event.phase==“ended” then
store.restore();
end
end

local buyText = display.newText(“BUY”, screenW/2 - 50, 600, native.systemFont, 30)
buyText:setTextColor(18, 112, 23)
buyText:addEventListener(“touch”,buy)

local RestoreText = display.newText(“RESTORE”, screenW/2-80, 800, native.systemFont, 30)
RestoreText:setTextColor(194, 67, 11)
RestoreText:addEventListener(“touch”,restore)

[/code]
[import]uid: 81188 topic_id: 26259 reply_id: 106424[/import]

Thanks jkrassman!

Yeah, I’ve pretty much got the same code.
The code is a bit stripped (to keep it “short”), still a bit long, sorry about that.

Cheers!

  
module(..., package.seeall)  
---------------------------------------------------------------------------------  
-- purchase.lua  
---------------------------------------------------------------------------------  
local store = require ( "store" )  
local widget = require ( "widget")  
local accessFiles = require( "files" )  
local scene = display.newGroup()  
local button, buy, box = {}, {}, {}  
local doButtons, response, transactionCallback, scrollViewer, firstRestore  
local validProducts, invalidProducts = {}, {}  
local listOfProducts =   
{  
 "com.dutchottie.appname.iap\_product\_name"  
}  
  
--This handles the buttons  
--Now only store.restore()  
function doButtons( event )  
 local name = event.target.name  
 if event.phase == "ended" then  
 if name == "restore" then  
 response.text = "PURCHASE: Restoring purchases"  
 -- call iTunes store to restore the purchases  
 store.restore()  
 elseif name == "menu" then  
 --leave purchase screen  
 end  
 end  
 return true  
end  
  
local function makeButton( x, y, width, height, alpha, name, text, listener )  
end  
  
local function processPurchase( event )  
 local id = event.target.id --represents the index for listOfProducts[] to buy  
 response.text = "Event phase = " .. event.phase .. " id=" .. event.target.id  
 if event.phase == "moved" then  
 scrollViewer:takeFocus( event )  
 elseif event.phase == "ended" then  
 box[id]:setFillColor(116,156,164)  
 response.text = "Buying number: " ..id .. " is called: " .. listOfProducts[id]  
 store.purchase( { listOfProducts[id] } )  
 end  
 return true  
end  
  
local function loadProductsCallback( event )  
 response.text = "PURCHASE: loading products"  
 if not validProducts then  
 --Show Alert error and close screen  
 local function onComplete( event )  
 --quit screen  
 end  
 end  
 native.showAlert( "In App features not available", "initStore() failed", { "OK" }, onComplete )   
 else  
 validProducts = event.products  
 invalidProducts = event.invalidProducts   
 if #validProducts == 0 then  
 --Debug lines  
 response.text = "Trying to display invalid products"  
 response.text = event.invalidProducts[1]  
 --  
 else  
 --Debug lines  
 response.text = "Products loaded. Total: " .. #validProducts .. " / " .. #invalidProducts  
 local res2 = display.newText("Product: " .. validProducts[1].title, 0, 0, native.systemFont, 30)  
 res2.x = halfX; res2.y = deviceHeight \* 0.15  
 scene:insert(res2)  
 --  
 -- The scrollview holds all the available IAP products  
 scrollViewer = widget.newScrollView{  
 left = halfX - 400,  
 top = (deviceHeight \* 0.35),  
 width = 800,  
 height = 320,  
 bgColor = {150},  
 maskFile = "images/catmask.png"  
 }  
 scene:insert(scrollViewer)  
  
 local lineHeight = 80  
 for i = 1, #validProducts do  
 --Show product  
 local myText = display.newText(validProducts[i].title, 0, 0, native.systemFont, 30)  
 myText:setReferencePoint(display.CenterLeftReferencePoint)  
 myText.x = 10; myText.y = (lineHeight \* 0.5) + (lineHeight \* (i - 1))  
 scrollViewer:insert(myText)  
 --Show pricing  
 local myText2 = display.newText( validProducts[i].localizedPrice, 0, 0, native.systemFont, 30)  
 myText2:setReferencePoint(display.CenterLeftReferencePoint)  
 myText2.x = scrollViewer.width - 400; myText2.y = (lineHeight \* 0.5) + (lineHeight \* (i - 1))  
 scrollViewer:insert(myText2)  
 --DEbug Buy button  
 buy[i] = display.newRect(0, 0, 100, 50) --display.newText("Buy", 0, 0, native.systemFont, 25)  
 buy[i]:setReferencePoint(display.CenterLeftReferencePoint)  
 buy[i].x = scrollViewer.width - 200; buy[i].y = (lineHeight \* 0.5) + (lineHeight \* (i - 1))  
 buy[i].id = i  
 scrollViewer:insert(buy[i])  
 --Show if it's already installed   
 local installed = display.newText("Installed", 0, 0, native.systemFont, 25)  
 installed:setReferencePoint(display.CenterLeftReferencePoint)  
 installed.x = scrollViewer.width - 50  
 installed.y = (lineHeight \* 0.5) + (lineHeight \* (i - 1))  
 scrollViewer:insert(installed)  
 --Is installed? change color of buy button and text "installed"  
 for j = 1, #\_G.purchased do  
 if validProducts[i].productIdentifier == \_G.purchased[j] then  
 installed:setTextColor(0, 255, 0)  
 buy[i]:setFillColor(0)  
 else   
 --Not installed set listener to the Buy button  
 installed:setTextColor(255, 0, 0)  
 buy[i]:setFillColor(255, 0, 0)  
 buy[i]:addEventListener("touch", processPurchase)  
 end  
 end  
 end  
 end  
 end  
 return true  
end  
  
-------  
--This is called from an other screen and is the main entry point of this module  
-------  
function showPurchase( )  
 local title = display.newText("Buy new question packs", 0, 0, native.systemFontBold, 35)  
 title:setReferencePoint(display.CenterReferencePoint)  
 title.x = halfX; title.y = deviceHeight \* 0.3  
 scene:insert(title)  
  
 --Debug line on-screen  
 response = display.newText("store response", 0, 0, native.systemFont, 30)  
 response.x = halfX; response.y = deviceHeight \* 0.05  
 scene:insert(response)  
  
 --Just a quick button routine  
 makeButton( halfX - 175, 600, 350, 78, 1, "restore", "Restore purchases", true)  
  
 store.loadProducts( listOfProducts, loadProductsCallback )  
end  
  
function transactionCallback( event )  
 local infoString   
 if event.transaction.state == "purchased" then  
 infoString = "Transaction successful! Bought: " .. event.transaction.productIdentifier  
 --This is just a debugging line that's viible on screen  
 response.text = infoString  
 --This write a file that stores the purchased extensions  
 accessFiles:addPurchase(event.transaction.productIdentifier, false)  
 elseif event.transaction.state == "restored" then  
 infoString = "Restoring transaction:" ..  
 "\n Original ID: " ..event.transaction.originalTransactionIdentifier ..  
 "\n Original date: "..event.transaction.productIdentifier  
 response.text = infoString  
 --This is assuming the "restored" is called automatically for each purchase  
 if firstRestore then  
 firstRestore = false  
 --Clear the file that keeps the purchased options  
 accessFiles:addPurchase(event.transaction.productIdentifier, true)  
 else  
 --Write the bought purches to file  
 accessFiles:addPurchase(event.transaction.productIdentifier, false)  
 end  
 elseif event.transaction.state == "cancelled" then  
 infoString = "Transaction cancelled by user."  
 response.text = infoString  
 elseif event.transaction.state == "failed" then   
 infoString = "Transaction failed, type: ",   
 event.transaction.errorType, event.transaction.errorString  
 response.text = infoString  
 else  
 infoString = "Unknown event"  
 response.text = infoString  
 end  
end  
  
print("PURCHASE: Initializing")  
firstRestore = true  
store.init(transactionCallback)  

[import]uid: 123200 topic_id: 26259 reply_id: 106427[/import]

I can report that issuing a store.restore() call results in your callback function being invoked once for each product being restored. Each product is sent to your callback as its own transaction. They are not bundled into a table for you to unpack. There does not appear to be any mechanism in the transaction to identify how many such products are being restored.
[import]uid: 44647 topic_id: 26259 reply_id: 106477[/import]

Hi toby2

Thanks for your answer. So it’s what I expected :slight_smile:
[import]uid: 123200 topic_id: 26259 reply_id: 106602[/import]

Okay I just ran the app in the Simulator.
After 2 logins to iTunes after calling the store.purchase(productid) I see the transaction fails because the app says it cannot connect to iTunes.
Just seconds before it loaded, successfully, the IAP I have defined in iTunes Connect.
So the network connection works just fine.

Any ideas where the problem could be?

Running the same version on my iPad means the App Store opens after the 2 logins (1 login and 1 verification login).

Thanks!
[import]uid: 123200 topic_id: 26259 reply_id: 106611[/import]

Hi again, It is hard to follow your code. Try my sample code in a new app and fill in your iap id, to see how that behaves. Then we can take it further from there!

Joakim [import]uid: 81188 topic_id: 26259 reply_id: 106613[/import]

Hi Joakim,

Thanks.
I just did what you asked. I used your code, inserted my productID stuff and ran it in the Simulator (Xcode).
It connects to the store.
It reads the one IAP that I’ve defined including the correct price.
After I click BUY it asks my username/password for which I use my test account as defined in iTunes Connect.
Then it asks for verification, so I re-enter the password.
Then is reports “failed” with error type: Unknown and description: Unable to connect to iTunes Store.

My test account seems to work (I can buy games on the App store and the Game Center works fine) and all dialog boxes report it’s running in Sandbox mode.
Internet connection obviously works too, otherwise it would not be able to retrieve the IAP data.

The app is not yet uploaded but is defined in iTunes Connect and I’ve enabled the test IAP as well. So apart from uploading the app and subsequently reviewing it plus the first IAP, everything should be in order.
[import]uid: 123200 topic_id: 26259 reply_id: 106615[/import]

Well after reading and re-reading a few forum threads in IAP.
I decided to create a new test user and set it for the US App Store.
My previous test account was set for the Netherlands.

Now it works!
Not sure though, and not very smart of me, if it’s the new test account or the locale that made the difference.
Oh well, as long as is does.

Thanks all for your help. Hopefully I won’t be bothering you again with this :slight_smile:

Cheers
[import]uid: 123200 topic_id: 26259 reply_id: 106626[/import]

Great, that you solved that issue. " Unknown" errors is the curse to deal with :slight_smile:

Keep it up!

Joakim [import]uid: 81188 topic_id: 26259 reply_id: 106637[/import]