iap-featIn the mobile development world, studios have been looking for more models to monetize their apps. This doesn’t just impact games, as business app developers are finding it more challenging to monetize as well.

There are several ways to make money with your app, including:

  1. Charge for it.
  2. Offer a limited free version and a paid full-featured version; you can use the “lite” app to drive customers to the paid one.
  3. Offer it for free and use ads to monetize.
  4. Offer it for free but charge for features.

Each version has its upsides and downsides:

  • Paid: This option has two pitfalls: first, people are now spoiled on “free,” and second, paid apps will typically reside below free apps in any ranking service, limiting your app’s visibility.
  • Free-to-play: This model worked well for many years, but it tends to clutter the app stores and some vendors have been discouraging this approach as there are better ways to accomplish the same goal.
  • Ad-funded: Using advertising is an appealing option, but you need a large install base and an app that people will use frequently for the ads to pay off. For many indie titles, the competition is stiff. Also, if you’re a game developer, keep in mind that games have a fairly short shelf life.
  • “Freemium”: You can offer the app for free and then charge for additional features using In-App Purchases (IAP). In this case, the app is free, but has some features that the customer needs to pay for before they can use them. This can be a way to accomplish option #2 — a free (but limited) game – and once you pay for it, you get the entire game. Another use of freemium is a full-blown economic system where your app has some in-game currency and players can earn this currency through play. If they need more, they can pay real-world money to get more in-game currency. Finally, you could use freemium to have a donation system: if the player enjoys the app, they may consider supporting the development for some amount of money.

In-App Purchase Options

Corona SDK currently offers IAP through three vendors:

  1. Apple’s App Store
  2. Google Play (V2)
  3. Google Play (V3)
  4. Fortumo (NOOK)
  5. Amazon In-App Purchase
  6. OUYA

For Apple, Google and Amazon, accessing IAP is done through the Corona Store API. Apple and Google are built into Corona’s core, and Amazon will be a third party plugin. Fortumo is also a third party plugin, but it does not use the store API.

Because each vendor’s “eco-system” varies from the others, there are some things that simply cannot be 100% cross-platform compatible. Part of working with IAP is learning the differences between the vendors and adapting your code to those differences.

Purchase Types

There are generally three classes of things users can purchase:

  1. One-time purchases that last the life of the app, like unlocking the full version of the app, or downloading new content once.
  2. One-time purchases that get “consumed.” Once they’re used, the app user needs to buy more. This is most commonly used for in-app currency.
  3. Subscriptions where the app user pays a monthly fee to receive some ongoing service. Subscriptions can be auto-renewing or non-renewing.

Each vendor uses different terminology for these purchases. Please view this chart to help translate each vendor’s language:

Apple Google Amazon Fortumo OUYA
One Time, Lasts Forever Non-Consumable Managed Product Entitlement Single Item Entitlement
One Time; Gets Used Consumable Unmanaged Product Consumable Virtual Credits Consumable
Subscription 1. Auto-Renewable Subscription
2. Non-Renewable Subscription
Subscription Subscription n/a n/a


Before you can use IAP, you must set up products in each vendor’s developer portal. This topic is too complex to detail in this tutorial, but in general terms, you have to:

  • Create each product you want to sell, giving it a unique ID*.
  • Submit the items for approval to the appropriate store. Both Apple and Amazon also require screenshots of your for sale screens.
  • For Apple and Google, set up unique test accounts. Amazon has a different method of testing.

* For all of these vendors, you can specify a unique ID. Google and Apple calls it Product ID. Amazon calls it a SKU. In all three cases, it’s recommended that you use the “reverse domain name” system that is used in your Bundle ID/App Identifiers. If you maintain consistent identifiers, it will make cross-platform implementation easier.

An example of this format is:


Which may translate to products like these:


Google will require you to upload your .apk with the proper permissions installed before you can add products to their store portal (although you can upload an Alpha build in which you do not need to make the app live).

These are the required permissions for Android:

android =
   usesPermissions =


Several vendors are implemented using Corona SDK’s Plugin system.  The following require you to include extra code in your build.settings to work:

Plugins are added to your build.settings file.  Each vendor has a slightly different requirement, but you can generally copy and paste the required code from the documentation page linked above.  See our build.settings tutorial for more information on adding plugins.

The store.* API

Apple, Google and Amazon are accessed trough Corona SDK’s built-in IAP library, the store module.

NOTE:Because Fortumo and OUYA have their own API, they will not be covered in this tutorial.

For Apple and Google Play V2, Include it in your app using a require() statement as such:

local store = require( "store" )

Since Google Play V3 and Amazon are implemented via a plugin you have to require them slightly differently:

local store = require("store")
if store.target == "google" then
    store = require( "plugin.google.iap.v3" )
if store.target == "amazon" then
    store = require( "plugin.amazon.iap" )
NOTE:Google Play V3 also requires licensing code be added to your config.lua as well. Please see the documentation for that information.

Of course you need to do this in each module/scene where you’ll be accessing the store. From there, three main API calls will be used (there are few others which we’ll cover later):

  • store.init()
  • store.purchase()
  • store.restore()

The store.init() call lets you define which store you wish to work with, while providing an event listener function to handle the results from interactions with the store.

To purchase things, use store.purchase() — simply pass to the function what you want to buy, and then when the transaction with the app’s user is done, the function you defined in store.init() will be called with the results of the transaction. Your app would then respond accordingly (unlock features, add to your “coin” count, etc.).

In the event that the app user deletes your app or wipes their device and re-installs your app, they are entitled to re-obtain their purchases that were non-consumable. It’s your responsibility to restore these purchases, which Corona allows via the store.restore() call.

NOTE: Consumable items like coins, gems, etc. are not restored. None of the vendors will respond to requests to restore these.

The other functions in the store.* API includes:

  • store.availableStores — used to see if a store is valid for your device.
  • store.canLoadProducts — check to see if loading products is allowed, which it’s not for Google Play V2.
  • store.canMakePurchases — parents can turn off purchasing for their kids.
  • store.finishTransaction() — very important call, used in your event listener function.
  • store.isActive — a way to confirm the store was initialized properly.
  • store.loadProducts() — a function to return information about products, including localized descriptions, names, and prices. This is not supported on Google Play V2 but is with Google Play V3.
  • store.consumePurchase() — a Google Play V3 function to enable re-purchasing consumable items.
  • store.target — returns the value of the store selected in the build screen.

Sample Store Code

Let’s look at a sample API store project. Before we do, you’ll need a few things to support your app. First, you will need the ability to load and save settings data so you can track if the person has paid for their unlock, the current coins count, etc. Let’s include that in a utility.lua file that we’ll include with our code. This is a lot of code to digest, but it’s the minimal structure.


--we need a few support functions
local M = {}
local json = require("json")

function M.saveTable(t, filename)
   local path = system.pathForFile( filename, system.DocumentsDirectory)
   local file = io.open(path, "w")
   if file then
      local contents = json.encode(t)
      file:write( contents )
      io.close( file )
      return true
      return false

--simple load table function
function M.loadTable(filename)
   local path = system.pathForFile( filename, system.DocumentsDirectory)
   local contents = ""
   local myTable = {}
   local file = io.open( path, "r" )
   if file then
      local contents = file:read( "*a" )
      myTable = json.decode(contents);
      io.close( file )
      return myTable
   print(filename, "file not found")
   return nil

function M.makeTimeStamp(dateString, mode)
    local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d*)%:?(%d*)";
    local xyear, xmonth, xday, xhour, xminute, xseconds, xoffset, xoffsethour, xoffsetmin
    local monthLookup = {Jan = 1, Feb = 2, Mar = 3, Apr = 4, May = 5, Jun = 6, Jul = 7, Aug = 8, Sep = 9, Oct = 10, Nov = 11, Dec = 12}
    local convertedTimestamp
    local offset = 0
    if mode and mode == "ctime" then
        pattern = "%w+%s+(%w+)%s+(%d+)%s+(%d+)%:(%d+)%:(%d+)%s+(%w+)%s+(%d+)"
        local monthName, TZName
        monthName, xday, xhour, xminute, xseconds, TZName, xyear = string.match(dateString,pattern)
        xmonth = monthLookup[monthName]
        convertedTimestamp = os.time({year = xyear, month = xmonth,
        day = xday, hour = xhour, min = xminute, sec = xseconds})
        xyear, xmonth, xday, xhour, xminute, xseconds, xoffset, xoffsethour, xoffsetmin = string.match(dateString,pattern)
        convertedTimestamp = os.time({year = xyear, month = xmonth,
        day = xday, hour = xhour, min = xminute, sec = xseconds})
        if xoffsetHour then
            offset = xoffsethour * 60 + xoffsetmin
            if xoffset == "-" then
                offset = offset * -1
    return convertedTimestamp + offset

return M


local store = require( "store" )
local utility = require( "utility" )

local mySettings
local restoring

mySettings = utility.loadTable("settings.json")

if mySettings == nil then
   mySettings = {}
   mySettings.isPaid = false
   utility.saveTable(mySettings, "settings.json")

local function transactionCallback( event )

   print("In transactionCallback", event.transaction.state)
   local transaction = event.transaction
   local tstate = event.transaction.state
   --Google does not return a "restored" state when you call store.restore()
   --You're only going to get "purchased" with Google. This is a work around
   --to the problem.
   --The assumption here is that any real purchase should happen reasonably
   --quick while restores will have a transaction date sometime in the past.
   --5 minutes seems sufficient to separate a purchase from a restore.
   if store.availableStores.google and tstate == "purchased" then
      local tdate = math.floor( transaction.date /1000 )
      local timeStamp = utility.makeTimeStamp(transaction.date,"ctime")
      if timeStamp + 360 < os.time() then  -- if the time stamp is older than 5 minutes, we will assume a restore.
         print("map this purchase to a restore")
         tstate = "restored"
         print("I think tstate is ", tstate)
         restoring = false

   if tstate == "purchased" then
      print("Transaction succuessful!")
      mySettings.isPaid = true
      utility.saveTable(mySettings, "settings.json")
      native.showAlert("Thank you!", "Your support is greatly appreciated!", {"Okay"})
      store.finishTransaction( transaction )
   elseif  tstate == "restored" then
      print("Transaction restored (from previous session)")
      mySettings.isPaid = true
      utility.saveTable(mySettings, "settings.json")
      store.finishTransaction( transaction )
   elseif tstate == "refunded" then
      print("User requested a refund -- locking app back")
      mySettings.isPaid = false
      utility.saveTable(mySettings, "settings.json")
      store.finishTransaction( transaction )
   elseif tstate == "revoked" then -- Amazon feature
      print ("The user who has a revoked purchase is", transaction.userId)
      --Revoke this SKU here:
      mySettings.isPaid = false
      utility.saveTable(mySettings, "settings.json")
   elseif tstate == "cancelled" then
      print("User cancelled transaction")
      store.finishTransaction( transaction )
   elseif tstate == "failed" then
      print("Transaction failed, type:", transaction.errorType, transaction.errorString)
      store.finishTransaction( transaction )
      print("unknown event")
      store.finishTransaction( transaction )

   print("done with store business for now")

local function loadProductsListener( event )
   print("In loadProductsListener")
   local products = event.products
   for i=1, #event.products do
   for i=1, #event.invalidProducts do

if system.getInfo("targetAppStore") == "amazon" then
   store = require "plugin.amazon.iap"
   store.init( transactionCallback )
   print("The currently logged in user is: ", store.getUserId())
   store.loadProducts({"com.yourcompany.yourapp"}, loadProductsListener)
   if store.availableStores.apple and not mySettings.isPaid then
      timer.performWithDelay(1000, function() store.init( "apple", transactionCallback); end)
   if store.availableStores.google and not mySettings.isPaid then
      timer.performWithDelay( 1000,
         store.init( "google", transactionCallback );
         restoring = true;
      end )

Workflow and Notes

The basic workflow for your app involves:

  1. Determine the store your app is running on and initialize the store using store.init().
  2. If you think the app has been reinstalled (i.e. there is no settings file, etc.), then (for Google and Amazon) you can call store.restore() when your app starts because it is silent. Apple will prompt your app to login to the iTunes App Store when your app starts. It’s best to delay the restore until later. Apple wants you to have a button specifically to restore purchases.
  3. For Apple, Google Play V3 and Amazon, you can use store.loadProducts() to get a list of your items using localized names and prices. Google Play V2 does not offer this with the version of IAP that Corona SDK uses. When Google shows the purchase dialog, the localized values will be shown then. Many people will just hard code their items in a custom display.
  4. Offer a purchase button or display that allows the customer a chance to purchase your items. This should lead to a call to store.purchase().
  5. Inside the event handler function, perform whatever activities you need to unlock your app or process your purchase.  For Google Play V3, this might be a good time to call store.consumePurchase() if the items is something like coins or gems that are being added to the app’s inventory immediately and can’t be tracked separately.  Items like potions in a fantasy game, or purchasable power up’s can be consumed when they are actually used.
  6. Make sure to call store.finishTransaction() at the end, regardless of the transaction state. Failing to call this function can cause your app to receive too many transactions!

In addition, each vendor’s store variances create situations that you need to adapt to. Let’s look at a couple of key situations:

  1. Consumables are never restored. You will never get a “restored” state that contains a consumable item. The reason is that these items are considered “used up” and there is no need to restore them. You will only get restored items for non-consumable items.
  2. Google does not provided a “restored” state. Even if you call store.restore(), Goggle considers these “purchased” items. Thus, you need to determine the difference between “purchased” and “restored” if that’s important for the app. To handle this, you should store the transaction’s receipt and, when you receive a purchase request, you should compare against previous purchases and see if you found a matched receipt (this accounts for apps being deleted and re-installed). Note that you can’t save your receipts locally — instead, you have to setup an online method to store and retrieve these receipts. While this is not a trivial task, comparing these digital receipts (which are complex, encrypted chunks of data) makes it difficult for someone to fake a receipt during the restore phase. This is the best practice to help mitigate piracy. The code above does not, for brevity. What it does, however, is attempt to map purchases to restores when Google is involved.
  3. If there is nothing to be restored, the event listener will not be called, so you should plan for this condition accordingly. If there is nothing to restore, there is nothing to do. Many people get snagged by this because they are looking for a “trigger” to say the process was complete. You should use a timer with a reasonable timeout to trigger your effect if there is no work to do.
  4. Amazon and Google allow refunds, but they handle it differently. Google can generate a “refunded” state. If you see this state come through, you should have your app lock whatever feature that product ID unlocked. Of course, this only happens for non-consumable items.  Products that are are consumed cannot be refunded. For Amazon, the state is “revoked”.   Apple does not support refunds.
  5. Apple triggers a store login for restores. The observant reader may have noticed that the store.init() sections above do not have a store.restore(). Google and Amazon will silently trigger restorations if the user is logged into the respective app store. Apple however, will prompt for the user’s password if they have not accessed the billing system in the last few minutes. Because of this, you don’t want a modal dialog to interfere with your users getting into the app and enjoying it. You can defer this restore until the user takes an action, until the place in your code where you actually need to determine their past purchases (or perhaps provide the user a “Restore past purchases” button in the interface). Apple will reject apps that do not call store.restore() in a proper manner.

Making Purchases

Making purchases is fairly easy. Simply pass your product identifier to the store.purchase() function:

local function removeAds( event )

   if event.phase == "began" then
   elseif event.phase == "ended" then
      if system.getInfo("targetAppStore") == "amazon" or system.getInfo("targetAppStore") == "google" then
         store.purchase( "com.acme.superrunner.upgrade" )
         store.purchase( { "com.acme.superrunner.upgrade" } )
   return true

local buyBtn = display.newImageRect("images/buy_button.png", 614, 65)
group:insert( buyBtn )
buyBtn.x = display.contentCenterX - 465
buyBtn.y = 430
buyBtn:addEventListener( "touch", removeAds )

This code should be fairly straightforward and would be typically found in the scene where your store interface is set up. The one “gotcha” here is that both Apple and Google Play V2 allow you to purchase multiple items at once. Therefore, store.purchase() accepts an array of product ID strings.  Amazon and Google Play V3, however, only allows purchasing one item at a time, so you only pass a single string. This is a case that you need to code for — while most users only purchase one thing at a time, it needs to be an array of items, even if just one is being passed in.

NOTE: You cannot use both Google Play V2 and V3 at the same time.  The code above is assuming you are using V3.  If you are using V2, simply remove the extra test in the if statement to see if you are targeting the Google store.

Testing In-App Purchase

This tutorial can’t go in-depth about this topic because it’s too complex and too “vendor specific,” but here are a few tips to help you out:


  • You need specific test accounts which you can create in iTunes Connect. They are standard Apple IDs, so you will need to have a unique email address for each account. If you have your own web hosting service, you can set up aliases like “iap01@yourdomain.com” and “iap02@yourdmain.com” that send emails to your primary email address as a “catch all.” Note that Apple will make you validate the email by clicking on a link within it, so you must be able to receive it.
  • You must log out from iTunes on your device before you run the test app. This test app can be provisioned with either a Development or AdHoc profile to access the IAP sandbox. When prompted, log in with your test account.
  • You get “one shot” to buy your item because Apple remembers that your account has purchased that item. If you need to test again, you need a different account. There is no way to reset these purchases.
  • If you see “you need to submit your app and reject the binary,” you can disregard that advice.  Currently, you just need to submit your items for review — but it does take a little time for your items to propagate through Apple’s servers.


  • Google requires specific test accounts as well. They are regular email addresses that you provide in the portal. The trick is getting your device to no longer be “you” but rather to be one of your test accounts. Google provides some product IDs that you can use instead of your own IDs. You can test a product that “always succeeds” and one that “always fails” and do this as many times as you need to. However, once you start using your actual Product IDs, you get one shot, just like Apple, and there’s no way to reset them other than using a different account.
  • Google’s system is sensitive to developers. For instance, if you request too many restore() requests in too short of a time, they will block your account for several weeks, assuming there is suspicious activity tied to it.


Amazon’s IAP plugin is still in beta and is subject to change.  We recommend you visit the Amazon plugin forum for up-to date information on this plugin’s use.

  • Besides the differences listed above, their testing process involves downloading their IAP App Development Kit. This contains a .apk file that you can side-load onto your device which intercepts your app’s purchases during testing. You can then use your existing account (or other accounts) to test.
  • You must also download a JSON file from their developer portal which contains your products and side-load this onto the device as well. Their IAP test app uses the JSON file instead of actually talking back to Amazon’s servers.
  • From the IAP test app, you can reject purchases, which gives you a good chance to test “revoked” purchase states.

In Summary…

As you can see, IAP is a key part of the monetization puzzle. While some people prefer to utilize a paid app approach, using IAP with a “freemium” model can be a great way to monetize your apps and games.

Share this post....Share on Facebook0Share on Google+2Tweet about this on TwitterShare on LinkedIn0
  1. I wouldn’t recommend saving the IAP settings into a json file. This is easily edited even without jail breaking your device. Any popular game will have this publicized in short time.

    Some form of obfuscation would be smart, the more the better.

  2. Rob, awsome post.
    But i have one question, didn’t google change policies about in-app? And now you can only buy them via google? So Fortumo wont be supported anymore?

    In-app purchases:
    Developers offering virtual goods or currencies within a game downloaded from Google Play must use Google Play’s in-app billing service as the method of payment.
    Developers offering additional content, services or functionality within another category of app downloaded from Google Play must use Google Play’s in-app billing service as the method of payment, except:
    where payment is primarily for physical goods or services (e.g. buying movie tickets, or buying a publication where the price also includes a hard copy subscription); or
    where payment is for digital content or goods that may be consumed outside of the application itself (e.g. buying songs that can be played on other music players).
    Developers must not mislead users about the applications they are selling nor about any in-app services, goods, content or functionality they are selling.

  3. Nice post Rob! I got it working now, but I have one last problem left to fix, I have this error: “field ‘day’ missing in date table” I know it is somewhere within the utility.lua file, but I can’t pinpoint where it is. I am kinda new to corona so I am having a hard time adapting to all of the codes. Thank you in advance.

  4. Have a quick question…. great tutuorial btw.. When you code if available.stores.google and…….if available.stores.apple and…….

    —-“not storyboard.settings.isPaid”—–

    wouldn’t you have to in this case write “not mySettings.isPaid” instead or does either way work?

  5. Hi Rob, thanks for the great overview.

    I have a question, though. At the beginning of the Main.lua file you have the isPaid boolean saved to the utilities module in a json file. However at the end you have it checking storyboard.settings for a the isPaid boolean.

    Which one is the better method? Or am I missing something…

  6. Scott, Steve… good catch. I pulled this code from one of my apps that was using storyboard as a way to pass values between modules. We now recommend using a module dedicated to your shared data and I guess I missed scrubbing that reference when cleaning it up for the blog post, so let me go fix it. You can do either.

  7. Thanks for this tutorial Rob.
    In the tutorial, after making a purchase you check the tstate value on your transactionCallback function. Ok.
    If tstate is equal to “purchased” value you put mySettings.isPaid = true and you save it. Ok
    In this case, there is no “real” data exchange in the transaction. Only a flag which determine if I can unlock a functionality which is already on my app.
    What about if in my in-App purchase I have for example 10 mp3 files which are NOT already on my app?
    How Can I manage real data from the transaction, my mp3 files?
    Where should I save these real data and how can I do that?
    Thanks for your help
    Best regards

  8. When you get the tstate == “purchased” value, in that if block, you can then start a network.download() of the content and set a flag when it’s been successfully downloaded.


  9. Thanks Rob.
    I create an In-App Purchase Item,
    I choose “Hosting Content with Apple”
    I did a package .pkg with xCode, containing my sound files, and I uploaded it with application loader to Apple.
    I can see it in the “in-app Purchase details” of my item on iTunes Connect.
    I would like to make a network.download() call but I don’t know what is the HTTP request URL for my package…
    Where Apple provides the URL for an in-app Purchase item?
    Thanks for your help

    • I have no experience with Apple hosting of IAP products. You would have to ask on their forums or talk to their Tech Support to find the URL. I suspect though that they may have a Native API Call that we don’t support so that you don’t need to know the URL. In which case, it’s very likely you won’t be able to use Apple’s hosting with our product.


  10. Hi Rob
    I have another question :)
    I use your code above to test In-App Purchase.
    I don’t understand why but the line just below in my code :

    timer.performWithDelay(1000, function() store.init( “apple”, transactionCallback); end)

    causes a call to transactionCallback function with a tstate equal to “purchased”
    As if a call to store.purchase was made, but I don’t do it.
    Have I missed something?
    Do you have an idea ?
    Thanks for your help

  11. Mark Steelman says:

    How secure is this settings.json file that you are storing the info in? I don’t see the file in iTunes but I don’t have a jail broken device.

    I have an in app paid for currency and would prefer to make my game playable off line. However, I may need to make it validate online every time if players can simply edit this file and give themselves all the game currency they please.

    • I think in the case for this tutorial, I didn’t include any encryption on the file. Most mortals won’t be able to see the file. Any hacker worth their weight will be able to see it. Just because you don’t jail break, a cheater won’t hesitate to jailbreak their phone.

      You can do things to obscure your data. Don’t name the variable “coins”. Give it some random key and add in a bunch of other random keys and change their values. You could also use encryption to encrypt it. We support the crypt.* API calls as well as the SSL plugin for Pro and Enterprise subscribers.

      Another technique would be to calculate an MD5 hash of your json string before you write it out, save it, and then save the MD5 hash. When you read the file back in, convert the inbound string to an MD5 hash and compare it to your saved MD5 hash. If it changed, you’ve been hacked.

      I’m not a security expert, so your mileage may vary.

  12. I am having trouble figuring out what edition of the SDK I need to do IAP through iTunes and Google Play. Since they are built into the Corona SDK ‘core’ does that mean they are included in the Starter edition? I realize the IAP plugins for Amazon and others are Basic edition only but can IAP be done with iTunes and Google Play using the Starter edition?

    • Hi Douglas. If you want to use IAP you must be either a Basic, Pro or Enterprise subscriber. Starter accounts do not have access to the IAP plugins or core IAP features (store.*).

  13. Hi Rob –

    I’m failing to see the ‘trigger’ that tells my code to go ahead and add the coins for a successful purchase. Regardless if the purchase was successful or not, it looks like the code still tells to add 25 coins, right? What flag or trigger do I need to call or input that says not to add coins unless the transaction is successful?


    function buyFunction (event)
    if system.getInfo(“targetAppStore”) == “amazon” or system.getInfo(“targetAppStore”) == “google” then
    store.purchase( “com.domainname.appname.25coinpurchase” )
    coins = coins + 25
    store.purchase( { “com.domainname.appname.25coinpurchase” } )
    coins = coins + 25
    toolbox.coins = coins
    coinPurchasePanel.Text.text = “You currently have “..coins..” coins.”

    buy:addEventListener(“tap”, buyFunction)

    • Since you’re into the point of posting code, you should take this to the forums. Comments do not handle code very well.

      But in general you should not add the coins until you get a successful purchase acknowledgement in your transaction call back. The way you have that coded, I can click the purchase button but cancel the purchase and you’ve already given me the coins. If you delay that until your transaction call back for a purchase and verify the item they bought, then you can safely add the coins and if its done with Google IAP V3, then you can then call store.consumePurchase() at that point to allow the player to buy more.


  14. I am new to android development. I have a question plz help me.
    Is it necessary to use in app billing when we want create app which is first pay and after that its available to user?
    means i setup merchant account and all the methods of payment and in developer console put my app as paid.
    so is process is complete or want to add in_app billing?
    Basically i just want to create app which is buy from google play first time after that itis free to user.

  15. So, at what point during the code sample on buying the item would, for example, I add 100 coins to the users account,
    I put the code below, where during that code would i add the part about, for example, coins = coins + 100
    local function removeAds( event )

    if event.phase == “began” then
    elseif event.phase == “ended” then
    if system.getInfo(“targetAppStore”) == “amazon” or system.getInfo(“targetAppStore”) == “google” then
    store.purchase( “com.acme.superrunner.upgrade” )
    store.purchase( { “com.acme.superrunner.upgrade” } )
    return true

    local buyBtn = display.newImageRect(“images/buy_button.png”, 614, 65)
    group:insert( buyBtn )
    buyBtn.x = display.contentCenterX – 465
    buyBtn.y = 430
    buyBtn:addEventListener( “touch”, removeAds )

  16. You should award the coins in the transaction call back once you confirm the purchase was made. Any time before that they can cancel the purchase and you’ve already awarded them the coins.

  17. ok, so this is my first time doing IAP and i am still a bit confused.

    First, what is “mySettings.isPaid = true (or sometimes false)” and
    if mySettings == nil then
    mySettings = {}
    mySettings.isPaid = false
    utility.saveTable(mySettings, “settings.json”)

    again, is mySettings.isPaid a example product, that i would change for each call back.
    The call back part is what is confusing me. On my iPad, when i build the app, I can get a do a test purchase of 1500 coins, and have it say my purchase was successful and everything. But, i don’t actually get coins. In the main.lua file, in the event call back, where do i list what happens for example when i call store.purchase(com.example.1500Coins), when i call the generic call back from main.lua, there is no where in my code that would add 1500 coins to the users account. In this tutorial, do we need to edit the main.lua file? I did not edit the main.lua file at all. I keep hearing about the call back function, does there have to be a different call back function for each IAP, and if so, how do you trigger the proper callback function for each IAP. Also, where do we have to put the product lists, or do we not have to do that for Corona sdk

    Thanks, sorry for the long question, but IAP are confusing me a lot.

  18. This example isn’t so much about coins but about unlocking your app. Many people will make their app free and for an in app purchase you unlock all the apps features. The mySettings.isPaid is just a flag to indicate if your customer has made an in app purchase that unlocks the whole app. Later in your code when you need to see if a feature is available to a paid customer you can do an:

    if mySettings.isPaid then
    — enable the paid feature

    In the case of coins, gems and other consumable items, you would have something like:


    In the transactionCallback, you would do something like:

    mySettings.coins = mySettings.coins + 1000

    assuming the purchase adds 1000 coins to their count in the transition.purchased event.

  19. When the transaction is cancelled on the popup that asks for your fingerprint, I can’t repeat the purchase, even with another product and not even after calling store.init again. What am I missing?

    • Okay, it was my own very dumb mistake and has nothing to do with the store, don’t mind. Was checking a boolean and forgot to reset that.

  20. Thanks for this article, it’s been a great help.

    One comment:
    It looks like: tdate in the transactionCallback is never used.
    Is the transaction.date in milliseconds or seconds?

    • Rob Miracle says:

      Its in milliseconds on Google Play and seconds on iOS I think, or visa versa. The sample in the tutorial doesn’t really use it, but it can be useful if you’re managing your own verification system. Also Google Play doesn’t support a separate event for restores, they come in as purchases. I used the date to see how old the transaction date is, with the assumption than any transaction over 5 minutes old would be a restore. I doubt someone could buy an in-app purchase, delete the app, reinstall it and try to restore the purchases in under 5 minutes.

  21. I am using composer so i am a little confused with the setting this up in main.lua.

    It appears (from the example) to be localised and you recommend setting it up this way. How would i access the listener from another scene in composer if it is local to main.lua?

    eg i load a splash scene then it goes to the title scene. i want to implement the IAP in the title scene, but if I’m understanding scope properly the title.lua file cannot access the listener function in main.lua, so the button i create in the title scene will not work

    • Rob Miracle says:

      You don’t need to access the listener. When you call store.purchase() or store.restore() when those functions complete, they call your listener function. You can use a data table to have values that are common to all modules. Your listener can set a flag that unlocks a level or increases a coin count. Your other modules can then look at that data and make decisions accordingly. See: http://coronalabs.com/blog/2013/05/28/tutorial-goodbye-globals/

  22. Cheers Rob.
    Can I ask what the purpose of the local restoring variable is for? I see you set it to false when you do the five minute restored assumption filter for google and then set it to true after store.init for google. But what does this do?

  23. On some of my computer I have a probleme.
    Runtime error
    ?:0: attempt to index global ‘network’ (a nil value)
    stack traceback:
    ?: in function ‘downloadManifest’
    ?: in function ‘downloadQueuedManifests’
    ?: in function ‘?’
    ?: in function
    (tail call): ?
    ?: in function
    ?: in function

    I haven’t find the solution of this probleme any where?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>