I kinda have it working, but why not make a clean example which could be easily adjusted and implemented?
+1, it would be very useful to have a super simple clean example on IAP.
Hi guys,
Which IAP example are you citing? The Corona sample IAP project? What would make it “simple” beyond what it does? IAP is a somewhat tricky process at the core, especially if you want to target both iOS and Google Play, because the behavior of the different stores requires a fair amount of conditional logic. I suppose that logic could be minimized if you’re targeting just one store or the other, but then if we create three sample projects (combined, iOS-only, Google Play only), it’s 3 sets of code to maintain and update, and I feel users may just get more confused by that. Just my two cents on the issue… feel free to disagree.
Regards,
Brent
Hey Brent,
Yes, I was talking about the sample IAP project. While i understand what you are saying, i find (but then again, i’m kind of a newbie coder) that the use of Factory functions + Closures + Widgets + the overall organization of the file is quite confusing.
Honestly, to me, it is not the iOS + Google Play that seems confusing, but more of the logic of the file.
It is probably very well coded for someone that reads well code, but for a beginner, this is a mess. A simple file with simple non-widget buttons (good old display.newImage) + simple function for buy and for restore could feel much clearer.
End of the day, i personally use iap.lua from code exchange because it was much clearer to understand (however, i don’t think it handles google play).
While the store logic is quite complicated, in the end most stores work the same : A few items, something that happens when you buy, something that happens if transaction fails, and a restore button (if non-consumable). No need of ‘decorum’ like loading screen and backgrounds, etc. in the sample code, just the core. If Corona is pushed to the extreme level of “code easily”, it should be just a few blank spots left to write code (a bit like the storyboard) and that’s it… Well, now take a second look at the sample, it really isn’t that =)
Add to this a nice tutorial on how to set it up and Corona IAP should be clarified for everyone.
No?
d.
IAP is fundamental to making apps so it would be nice if we could have someone really go in on the topic.
The sample app is very good in the sense that it really handles the whole spectrum of possibilities. Because it is quite robust and written in a “progressive” fashion (can’t think of a better way to describe the scripting) it is a bit hard to follow for noobs like myself.
It would be nice to also ship a very basic sample that focuses more on understanding the base process and less on being so robust/multidimensional.
My process for getting going with IAP was first to set up purchases in my iTunes Connect to be able to get the shipped sample working. Once that was straight, then I built a bare bones set up to make sure I could get IAP to work on my own terms. Unfortunately I’m still wrangling with this to get it to work not only on the Xcode simulator but also on hardware (won’t work on hardware, see my other post). Once I get past this then I’m going to implement a basic non-consumable purchase in my project.
So, looking at the sample project I can kind of see what they are saying(though i disagree with using non-widget buttons). There is a lot of UI stuff going on and, IMO, a lot of the design seems extra unnecessary for someone who just needs to know how to do a basic IAP transaction. Sure, you could pick up some tips on how to handle an app with a lot of IAP items but I would say nearly anyone who looks at the sample apps for help is just trying to cope with some basic API functions. Anything else ends up being extra code/logic you have to pick through and discard as irrelevant. Even if you are an experienced programmer it still ads overhead trying to workout new APIs.
Below I think is more what the folks above would be looking for in a simple sample app. I just pulled the basic code from the sample app. I don’t have access Corona currently so I can’t actually test it but I think minus any typos it’s fine(I wasn’t looking to write a replacement sample app, just a sample of a sample :)). It’s really just two buttons, one does a purchase and one does a restore. You can read the output in the console.
[lua]
– You must call this first in order to use the “store” API.
local store = require(“store”)
function transactionCallback( event )
– Log transaction info.
print("transactionCallback: Received event " … tostring(event.name))
print("state: " … tostring(event.transaction.state))
print("errorType: " … tostring(event.transaction.errorType))
print("errorString: " … tostring(event.transaction.errorString))
local productID= event.transaction.productIdentifier;
if event.transaction.state == “purchased” then
print("Product Purchased: ", productID)
elseif event.transaction.state == “restored” then
print(“Product Restored”, productID)
elseif event.transaction.state == “refunded” then
print(“Product Refunded”)
elseif event.transaction.state == “cancelled” then
print(“Transaction cancelled”)
elseif event.transaction.state == “failed” then
print(“Transaction Failed”
else
print(“Some unknown event occured. This should never happen.”)
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 )
end
–initialize your tranaction callback with appropriate store(google or apple)
if store.availableStores.apple then
store.init(“apple”, transactionCallback)
print(“Using Apple’s in-app purchase system.”)
elseif store.availableStores.google then
store.init(“google”, transactionCallback)
print(“Using Google’s Android In-App Billing system.”)
else
local function purchaseItem(event)
--make sure you add { } around your product id as you need to send a table value… not a string!
store.purchase( {“com.mybundle.myapp.myproduct”})
end
local function restorePurchases(event)
--no need to sepcify a product
store.restore()
end
–create a button that purchases an item
local btnPurchase= widget.newButton {
onRelease = purchaseItem,
label = “Purchase”
}
local btnRestorewidget.newButton {
onRelease = restorePurchases
label = “Purchase”
}
[/lua]
This is killer, it looks like just what I was getting at as far as having a very basic example to complement the robust one that ships with Corona. I’m going to give this a shot with my setup, thanks
@budershank, you’re a master!
thanks so much, I’ve just implemented it and it works like a charm. It’s so much more clear than the corona’s full IAP example.
cheers
+1, it would be very useful to have a super simple clean example on IAP.
Hi guys,
Which IAP example are you citing? The Corona sample IAP project? What would make it “simple” beyond what it does? IAP is a somewhat tricky process at the core, especially if you want to target both iOS and Google Play, because the behavior of the different stores requires a fair amount of conditional logic. I suppose that logic could be minimized if you’re targeting just one store or the other, but then if we create three sample projects (combined, iOS-only, Google Play only), it’s 3 sets of code to maintain and update, and I feel users may just get more confused by that. Just my two cents on the issue… feel free to disagree.
Regards,
Brent
Hey Brent,
Yes, I was talking about the sample IAP project. While i understand what you are saying, i find (but then again, i’m kind of a newbie coder) that the use of Factory functions + Closures + Widgets + the overall organization of the file is quite confusing.
Honestly, to me, it is not the iOS + Google Play that seems confusing, but more of the logic of the file.
It is probably very well coded for someone that reads well code, but for a beginner, this is a mess. A simple file with simple non-widget buttons (good old display.newImage) + simple function for buy and for restore could feel much clearer.
End of the day, i personally use iap.lua from code exchange because it was much clearer to understand (however, i don’t think it handles google play).
While the store logic is quite complicated, in the end most stores work the same : A few items, something that happens when you buy, something that happens if transaction fails, and a restore button (if non-consumable). No need of ‘decorum’ like loading screen and backgrounds, etc. in the sample code, just the core. If Corona is pushed to the extreme level of “code easily”, it should be just a few blank spots left to write code (a bit like the storyboard) and that’s it… Well, now take a second look at the sample, it really isn’t that =)
Add to this a nice tutorial on how to set it up and Corona IAP should be clarified for everyone.
No?
d.
IAP is fundamental to making apps so it would be nice if we could have someone really go in on the topic.
The sample app is very good in the sense that it really handles the whole spectrum of possibilities. Because it is quite robust and written in a “progressive” fashion (can’t think of a better way to describe the scripting) it is a bit hard to follow for noobs like myself.
It would be nice to also ship a very basic sample that focuses more on understanding the base process and less on being so robust/multidimensional.
My process for getting going with IAP was first to set up purchases in my iTunes Connect to be able to get the shipped sample working. Once that was straight, then I built a bare bones set up to make sure I could get IAP to work on my own terms. Unfortunately I’m still wrangling with this to get it to work not only on the Xcode simulator but also on hardware (won’t work on hardware, see my other post). Once I get past this then I’m going to implement a basic non-consumable purchase in my project.
So, looking at the sample project I can kind of see what they are saying(though i disagree with using non-widget buttons). There is a lot of UI stuff going on and, IMO, a lot of the design seems extra unnecessary for someone who just needs to know how to do a basic IAP transaction. Sure, you could pick up some tips on how to handle an app with a lot of IAP items but I would say nearly anyone who looks at the sample apps for help is just trying to cope with some basic API functions. Anything else ends up being extra code/logic you have to pick through and discard as irrelevant. Even if you are an experienced programmer it still ads overhead trying to workout new APIs.
Below I think is more what the folks above would be looking for in a simple sample app. I just pulled the basic code from the sample app. I don’t have access Corona currently so I can’t actually test it but I think minus any typos it’s fine(I wasn’t looking to write a replacement sample app, just a sample of a sample :)). It’s really just two buttons, one does a purchase and one does a restore. You can read the output in the console.
[lua]
– You must call this first in order to use the “store” API.
local store = require(“store”)
function transactionCallback( event )
– Log transaction info.
print("transactionCallback: Received event " … tostring(event.name))
print("state: " … tostring(event.transaction.state))
print("errorType: " … tostring(event.transaction.errorType))
print("errorString: " … tostring(event.transaction.errorString))
local productID= event.transaction.productIdentifier;
if event.transaction.state == “purchased” then
print("Product Purchased: ", productID)
elseif event.transaction.state == “restored” then
print(“Product Restored”, productID)
elseif event.transaction.state == “refunded” then
print(“Product Refunded”)
elseif event.transaction.state == “cancelled” then
print(“Transaction cancelled”)
elseif event.transaction.state == “failed” then
print(“Transaction Failed”
else
print(“Some unknown event occured. This should never happen.”)
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 )
end
–initialize your tranaction callback with appropriate store(google or apple)
if store.availableStores.apple then
store.init(“apple”, transactionCallback)
print(“Using Apple’s in-app purchase system.”)
elseif store.availableStores.google then
store.init(“google”, transactionCallback)
print(“Using Google’s Android In-App Billing system.”)
else
local function purchaseItem(event)
--make sure you add { } around your product id as you need to send a table value… not a string!
store.purchase( {“com.mybundle.myapp.myproduct”})
end
local function restorePurchases(event)
--no need to sepcify a product
store.restore()
end
–create a button that purchases an item
local btnPurchase= widget.newButton {
onRelease = purchaseItem,
label = “Purchase”
}
local btnRestorewidget.newButton {
onRelease = restorePurchases
label = “Purchase”
}
[/lua]
This is killer, it looks like just what I was getting at as far as having a very basic example to complement the robust one that ships with Corona. I’m going to give this a shot with my setup, thanks
Anyone with an idea of how to implement “addTransactionObserver” to finish previously unfinished consumable transactions?
I get the message "you have already purchased this product… and it just cancels the purchase as it wasn’t finished…
The restore only seems to work for subscriptions and non-consumables. This is related to the problem discussed here:
I may have had the same problem (well I had a bucket full);
For non-consumables and subscriptions, the restore function get callbacks to the transactionCallBack listener (which is specified in your store.init function). I get multiple callbacks if multiple purchases were made, and for a sandbox/test environment in which a subscription only lasts 30 minutes, it can be a few… Anyway, one need to check each of these callbacks for validity (especially subscriptions) which needs some special code and your store shared secret password. Look at the example by Ganesan:
https://github.com/ganesan/Auto-Renewable-In-App-Purchase-Validation-using-Corona-SDK
Find a better base64 implementation and you should have something that works. To get the values out of the response, I used the following code (it required a lot of test and error, so use at own risk). I place it in the restoreListner:
storeResponse=json.decode(event.response) print("Restore status:"..storeResponse.status) -- Must go through receipt records to find important values: local idex=0 for key, value in pairs (storeResponse) do idex=idex+1 if type (value) == "table" then if key=="receipt" then -- Third record is usually the receipt table (but no certainity of that): local idex2=0 for key, value in pairs (value) do idex2=idex2+1 if key=="purchase\_date" then RestorePurchaseDate=string.sub(value,1,19) end if key=="expires\_date\_formatted" then RestoreExpiresDate=string.sub(value,1,19) end if key=="product\_id" then RestoreProductID=string.sub(value,1) end if key=="quantity" then RestoreQuantity=tonumber(value) end end end end end -- All key values: -- 3-1. original\_transaction\_id=1000000099999999 -- 3-2. purchase\_date\_pst=2013-07-03 15:33:29 America/Los\_Angeles -- 3-3. item\_id=999999999 -- 3-4. expires\_date=1372892609000 -- 3-5. original\_purchase\_date\_ms=1372601970000 -- 3-6. bid=com.yourcompany.yourgame.subscription -- 3-7. original\_purchase\_date\_pst=2013-06-30 07:19:30 America/Los\_Angeles -- 3-8. purchase\_date\_ms=1372890809000 -- 3-9. expires\_date\_formatted=2013-07-03 23:03:29 Etc/GMT -- 3-10. web\_order\_line\_item\_id=1000000099999999 -- 3-11. original\_purchase\_date=2013-06-30 14:19:30 Etc/GMT -- 3-12. unique\_identifier= (a long hex code) -- 3-13. quantity=1 -- 3-14. purchase\_date=2013-07-03 22:33:29 Etc/GMT -- 3-15. transaction\_id=1000000099999999 -- 3-16. product\_id=com.yourcompany.yourgame.subscription -- 3-17. unique\_vendor\_identifier=FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF -- 3-18. bvrs=1.0 -- 3-19. expires\_date\_formatted\_pst=2013-07-03 16:03:29 America/Los\_Angeles
Note that storeResponse.status is either 0 or 21006 for ALL transactions depending on wheter one of the transactions are still a valid subscription or not. As you see from the code, you can check each expires_date_formatted to see if the individual transaction is actually expired or not (this is usually not required if you only have one type of subscription).
Now, for consumables you get NO callback during restore. This gets somewhat confusing if a transaction is not finished (which happens a lot during testing). Especially if you decide to switch test accounts (to get rid of an ever increasing list of subscriptions). Such unfinished transaction will give you an error about the product not being downloaded, and will only result in a cancel transaction if you try to buy the product again. After some searching I found that such unfinished transactions will actually finish once you relaunch the app and run store.init. If you switched test accounts before all unfinished transactions were completed, the device will ask you for password of the previous test account and things will look messy. It also looks like a bug will prevent the transaction from actually clearing, so you may have to:
Delete all test accounts, uninstall the app, reset your phone.
Budershank, looks good your clean example
I also used (couple months back) https://github.com/jtcreative/Corona-SDK-In-App-Purchasing-Module
It’s a bit dated with the UI and all, but the code is simple and still usable.
@budershank, you’re a master!
thanks so much, I’ve just implemented it and it works like a charm. It’s so much more clear than the corona’s full IAP example.
cheers
Anyone with an idea of how to implement “addTransactionObserver” to finish previously unfinished consumable transactions?
I get the message "you have already purchased this product… and it just cancels the purchase as it wasn’t finished…
The restore only seems to work for subscriptions and non-consumables. This is related to the problem discussed here:
I may have had the same problem (well I had a bucket full);
For non-consumables and subscriptions, the restore function get callbacks to the transactionCallBack listener (which is specified in your store.init function). I get multiple callbacks if multiple purchases were made, and for a sandbox/test environment in which a subscription only lasts 30 minutes, it can be a few… Anyway, one need to check each of these callbacks for validity (especially subscriptions) which needs some special code and your store shared secret password. Look at the example by Ganesan:
https://github.com/ganesan/Auto-Renewable-In-App-Purchase-Validation-using-Corona-SDK
Find a better base64 implementation and you should have something that works. To get the values out of the response, I used the following code (it required a lot of test and error, so use at own risk). I place it in the restoreListner:
storeResponse=json.decode(event.response) print("Restore status:"..storeResponse.status) -- Must go through receipt records to find important values: local idex=0 for key, value in pairs (storeResponse) do idex=idex+1 if type (value) == "table" then if key=="receipt" then -- Third record is usually the receipt table (but no certainity of that): local idex2=0 for key, value in pairs (value) do idex2=idex2+1 if key=="purchase\_date" then RestorePurchaseDate=string.sub(value,1,19) end if key=="expires\_date\_formatted" then RestoreExpiresDate=string.sub(value,1,19) end if key=="product\_id" then RestoreProductID=string.sub(value,1) end if key=="quantity" then RestoreQuantity=tonumber(value) end end end end end -- All key values: -- 3-1. original\_transaction\_id=1000000099999999 -- 3-2. purchase\_date\_pst=2013-07-03 15:33:29 America/Los\_Angeles -- 3-3. item\_id=999999999 -- 3-4. expires\_date=1372892609000 -- 3-5. original\_purchase\_date\_ms=1372601970000 -- 3-6. bid=com.yourcompany.yourgame.subscription -- 3-7. original\_purchase\_date\_pst=2013-06-30 07:19:30 America/Los\_Angeles -- 3-8. purchase\_date\_ms=1372890809000 -- 3-9. expires\_date\_formatted=2013-07-03 23:03:29 Etc/GMT -- 3-10. web\_order\_line\_item\_id=1000000099999999 -- 3-11. original\_purchase\_date=2013-06-30 14:19:30 Etc/GMT -- 3-12. unique\_identifier= (a long hex code) -- 3-13. quantity=1 -- 3-14. purchase\_date=2013-07-03 22:33:29 Etc/GMT -- 3-15. transaction\_id=1000000099999999 -- 3-16. product\_id=com.yourcompany.yourgame.subscription -- 3-17. unique\_vendor\_identifier=FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF -- 3-18. bvrs=1.0 -- 3-19. expires\_date\_formatted\_pst=2013-07-03 16:03:29 America/Los\_Angeles
Note that storeResponse.status is either 0 or 21006 for ALL transactions depending on wheter one of the transactions are still a valid subscription or not. As you see from the code, you can check each expires_date_formatted to see if the individual transaction is actually expired or not (this is usually not required if you only have one type of subscription).
Now, for consumables you get NO callback during restore. This gets somewhat confusing if a transaction is not finished (which happens a lot during testing). Especially if you decide to switch test accounts (to get rid of an ever increasing list of subscriptions). Such unfinished transaction will give you an error about the product not being downloaded, and will only result in a cancel transaction if you try to buy the product again. After some searching I found that such unfinished transactions will actually finish once you relaunch the app and run store.init. If you switched test accounts before all unfinished transactions were completed, the device will ask you for password of the previous test account and things will look messy. It also looks like a bug will prevent the transaction from actually clearing, so you may have to:
Delete all test accounts, uninstall the app, reset your phone.