IAP Badger

A simple, unified, IAP library for Corona SDK

iap_badger: Plugin API Docs

If you find this module helpful, it would be amazing if you could download and rate one of our games (they're all free to download).

 
Type Library
Corona Store iap_badger
Keywords iap, in app purchase, store, transaction, monetization, trolley, cart
See also Corona store API, Google IAP, Amazon IAP

Overview

The iap_badger plugin can be used in your Corona project. It provides:

  • A simplified set of functions for processing in app purchases (IAP)
  • The ability to write a single piece of IAP code that works across Apple's App Store, Google Play and Amazon.
  • Makes Google and Amazon stores appear to follow the purchase/restore model adopted by Apple.
  • A testing mode, so your IAP functions can be tested on the simulator or a real device without having to contact an actual app store.
  • Simplified product maintenance (adding/removing products from an inventory triggered by purchases)
  • Built in loading/saving of the inventory
  • Products can have different names across the range of stores (so an upgrade called 'COIN_UPGRADE' in iTunes Connect could be called 'coins_purchased' in Google Play) without the need for additional code
  • Ability to handle consumable and non-consumable product types
  • Some basic hashing and security functions to protect the inventory file

For an extended tutorial that explains how to set up and use IAP Badger, click here.

To get to the simplest possible code for making an IAP purchase, scroll down to Sample code (simplest possible IAP purchase).

IAP Badger is wrapper class written in pure lua for Corona's store libraries and the Google and Amazon plug-ins.

Syntax

local iap = require "plugin.iap_badger"

Functions

iap_badger.addToInventory()
iap_badger.emptyInventory()
iap_badger.emptyInventoryOfNonConsumableItems()
iap_badger.getInventoryValue()
iap_badger.getLoadProductsCatalogue()
iap_badger.getStoreName()
iap_badger.getTargetStore()
iap_badger.init()
iap_badger.inventoryItemCount()
iap_badger.isInInventory()
iap_badger.isInventoryEmpty()
iap_badger.isStoreAvailable()
iap_badger.loadInventory()
iap_badger.loadProducts()
iap_badger.printInventory()
iap_badger.purchase()
iap_badger.removeFromInventory()
iap_badger.restore()
iap_badger.saveInventory()
iap_badger.setCancelledListener()
iap_badger.setFailedListener()
iap_badger.setInventoryValue()

Properties

Project Configuration

Corona Store Activation

In order to use this plugin, you must activate the plugin at the Corona Store.

SDK

When you build using the Corona Simulator, the server automatically takes care of integrating the plugin into your project.

All you need to do is add an entry into a plugins table of your build.settings. The following is an example of a minimal build.settings file:

settings =
{
    plugins =
    {
        -- includes IAP Badger as a plug in
        ["plugin.iap_badger"] =
        {
            -- required
            publisherId = "uk.co.happymongoose",
        },
    },      
}

To include the module, use:


local iap = require("plugin.iap_badger")

Note: there is also a version of IAP Badger held on a repository at Github. If you use include IAP Badger using this approach, there is no need for the above code. Just include the IAP Badger library with the rest of your project files. The code is provided under an MIT license, so you're free to fork it and do what you like with it.

Platform-specific Notes

Google Play

If you are connecting to Google Play, you will need to set up your build.settings to include the Google IAP v3 plugin.


    plugins =
    {
        --Google in app billing v3
        ["plugin.google.iap.v3"] =
        {
            -- required
            publisherId = "com.coronalabs",
            supportedPlatforms = { android = true },
        },  
    }

You will also need to enable the BILLING permission in build.settings.


    android =
    {
        usesPermissions =
        {
            "com.android.vending.BILLING",
        },
    },

Your app's key must be added to the license table in config.lua. You will find the key for your app in Google Play Console.


    application = 
    {
        license =
        {
            google =
            {
                key = "Your key",
            },
        },
    }

For more information, read this page.

Amazon

If you are connecting to Amazon, you will need to set up your build.settings to include the Amazon IAP v2 plugin.


    plugins =
    {
        --Amazon IAP
        ["plugin.amazon.iap"] =
        {
            publisherId = "com.coronalabs",
            supportedPlatforms = { ["android-kindle"]=true }
        },

    }

Resources

Sample code (simplest possible IAP purchase)

The follow piece of code gives the simplest possible example of how to handle the purchase of a in-app product.

It will correctly handle an IAP for a product called "removeAds" across iOS, Google Play and Amazon.


--Load IAP Badger
local iap = require("iap_badger")

--Create the catalogue
local catalogue = {

    --Information about the product on the app stores
    products = {     
        --removeAds is the product identifier.
        removeAds = {
                --A list of product names or identifiers specific to apple's App Store or Google Play.
                productNames = { apple="remove_ads", google="REMOVE_BANNER", amazon="Banner_Remove"},
                --The product type
                productType = "non-consumable"
        }
    }
}

--This table contains all of the options we need to specify in this example program.
local iapOptions = { catalogue=catalogue }

--Initialise IAP badger
iap.init(iapOptions)

--Called when the relevant app store has completed the purchase
--Make a record of the purchase using whatever method you like
local function purchaseListener(product )
    print "Purchase made"
end

iap.purchase("removeAds", purchaseListener)

Sample Code (full)

The following pieces of code are full examples of how to code IAP purchases, including restore products, that you can use to base your own code on. The examples here include full user interface code, including presenting the user with spinners and removing them from the screen. Simpler examples are presented in the documentation for individual functions.

In both of the examples below, debugMode has been set to true, so the device will not attempt to make real purchases from the app store.

Both of these examples assume you are using the plug-in version of IAP Badger on Corona's servers (rather than manually including the library from Github).

Example 2

Using IAP Badger to purchase an IAP for removing advertisements from an app, including all UI code. Also includes a restore products function.

The full example project can be downloaded here.


--Example2.lua
--
--Simple example of using IAP Badger to purchase an IAP for removing advertisements from an app.
--Also includes a restore products function

---------------------------------
-- 
-- Declarations
--
---------------------------------

--Progress spinner
local spinner=nil
--Buy button group
local buyGroup=nil
--Advertisment group
local adGroup=nil

--Forward declaration for buyUnlock function
local buyUnlock=nil

---------------------------------
-- 
-- IAP Badger initialisation
--
---------------------------------

--Load IAP Badger
local iap = require("plugin.iap_badger")

--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.
        removeAds = {
                --A list of product names or identifiers specific to apple's App Store or Google Play.
                productNames = { apple="remove_ads", google="REMOVE_BANNER", amazon="Banner_Remove"},
                --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 removeAds()    
    --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)
    if (adGroup) then
        adGroup:removeSelf()
        adGroup=nil
    end
    --Change the button text
    buyGroup.text.text="Game unlocked"
    buyGroup:removeEventListener("tap", buyUnlock)
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
    removeAds()
    --Save the inventory change
    iap.saveInventory()
    --Give the user a message saying the purchase was successful
    native.showAlert("Info", "Your purchase was successful", {"Okay"})
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(160,240,360,600)
    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() .. "...", 160,180, native.systemFont, 18)
    spinnerText:setFillColor(0,0,0)
    --Add a little spinning rectangle
    local spinnerRect = display.newRect(160,260,35,35)
    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("removeAds", purchaseListener)

end

---------------------------------
-- 
-- Restoring purchases
--
---------------------------------

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=="removeAds") then removeAds() 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(160,240,360,600)
    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() .. "...", 160,180, native.systemFont, 18)
    spinnerText:setFillColor(0,0,0)
    --Add a little spinning rectangle
    local spinnerRect = display.newRect(160,260,35,35)
    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

---------------------------------
-- 
-- Main game code
--
---------------------------------

--Remove status bar
display.setStatusBar( display.HiddenStatusBar )

--Background
local background = display.newRect(160,240,360,600)
background:setFillColor({type="gradient", color1={ 0,0,0 }, color2={ 0,0,0.4 }, direction="down"})

--Draw "buy" button
    --Create button background
    local buyBackground = display.newRect(160, 400, 150, 50)
    buyBackground.stroke = { 0.5, 0.5, 0.5 }
    buyBackground.strokeWidth = 2
    --Create "buy IAP" text object
    local buyText = display.newText("Remove ads", buyBackground.x, buyBackground.y, native.systemFont, 18)
    buyText:setFillColor(0,0,0)
    --Place objects into a group
    buyGroup = display.newGroup()
    buyGroup:insert(buyBackground)
    buyGroup:insert(buyText)
    buyGroup.text=buyText

--If the user has purchased the game before, change the button
if (iap.getInventoryValue("unlock")==true) then
    buyText.text="Game unlocked"
else
    --Otherwise add a tap listener to the button that unlocks the game
    buyGroup:addEventListener("tap", buyUnlock)
end

--Draw "restore" button
    --Create button background
    local restoreBackground = display.newRect(160, 330, 180, 50)
    restoreBackground.stroke = { 0.5, 0.5, 0.5 }
    restoreBackground.strokeWidth = 2
    --Create "buy IAP" text object
    local restoreText = display.newText("Restore purchases", restoreBackground.x, restoreBackground.y, native.systemFont, 18)
    restoreText:setFillColor(0,0,0)
    --Add event listener
    restoreText:addEventListener("tap", restorePurchases)

--If the user hasn't unlocked the game, display an advertisement across the top of the screen
if (iap.getInventoryValue("unlock")~=true) then
    --Create button background
    local adBackground = display.newRect(160, 75, 300, 75)
    adBackground:setFillColor( 1, 1, 0 )
    adBackground.stroke = { 0.5, 0.5, 0.5 }
    adBackground.strokeWidth = 2
    --Create "buy IAP" text object
    local adText = display.newText("Advertisment here", adBackground.x, adBackground.y, native.systemFont, 18)
    adText:setFillColor(0,0,0)
    --Assemble objects into a group
    adGroup = display.newGroup()
    adGroup:insert(adBackground)
    adGroup:insert(adText)
end

Example 3

Using IAP Badger to purchase a two consumable in-app products (coin packs of different sizes), including full UI.

The full example project can be downloaded here.


--Example3.lua
--
--Simple example of using IAP Badger to purchase an IAP for buying coins.

---------------------------------
-- 
-- IAP Badger initialisation
--
---------------------------------

--Load IAP Badger
local iap = require("plugin.iap_badger")

--Progress spinner
local spinner=nil
--Text object indicating how many coins the user is currently holding
local coinText=nil

--Forward declaration
local buyCoins10=nil

--Create the catalogue
local catalogue = {

    --Information about the product on the app stores
    products = {    

        --buy50coins is the product identifier.
        --Always use this identifier to talk to IAP Badger about the purchase.
        buy50coins = {
                --A list of product names or identifiers specific to apple's App Store or Google Play.
                productNames = { apple="buy50coins", google="50_coins", amazon="COINSx50"},
                --The product type
                productType = "consumable",
                --This function is called when a purchase is complete.
                onPurchase=function() iap.addToInventory("coins", 50) end,
                --The function is called when a refund is made
                onRefund=function() iap.removeFromInventory("coins", 50) end,
        },

        --buy100coins is the product identifier.
        --Always use this identifier to talk to IAP Badger about the purchase.
        buy100coins = {
                --A list of product names or identifiers specific to apple's App Store or Google Play.
                productNames = { apple="buy100coins", google="100_coins", amazon="COINSx100"},
                --The product type
                productType = "consumable",
                --This function is called when a purchase is complete.
                onPurchase=function() iap.addToInventory("coins", 100) end,
                --The function is called when a refund is made
                onRefund=function() iap.removeFromInventory("coins", 100) end,
        },
    },

    --Information about how to handle the inventory item
    inventoryItems = {
        coins = { productType="consumable" }
    }
}

--Called when a purchase fails
local function failedListener()

    --If the spinner is on screen, remove it
    if (spinner) then 
        spinner:removeSelf()
        spinner=nil
    end

end

local iapOptions = {
    --The catalogue generated above
    catalogue=catalogue,
    --The filename in which to save the inventory
    filename="example2.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.
    --doNotLoadInventory=true
    debugMode=true
}

--Initialise IAP badger
iap.init(iapOptions)

local function purchaseListener(product)
    --Remove the spinner
    spinner:removeSelf()
    spinner=nil

    --Any changes to the value in 'coins' in the inventory has been taken care of.
    --Update the text object to reflect how many coins are now in the user's inventory
    coinText.text = iap.getInventoryValue("coins") .. " coins"

    --Save the inventory change
    iap.saveInventory()

    --Tell user their purchase was successful
    native.showAlert("Info", "Your purchase was successful", {"Okay"})

end

--Purchase function
buyCoins=function(event)

    --Place a progress spinner on screen and tell the user the app is contating the store
    local spinnerBackground = display.newRect(160,240,360,600)
    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() .. "...", 160,180, native.systemFont, 18)
    spinnerText:setFillColor(0,0,0)
    --Add a little spinning rectangle
    local spinnerRect = display.newRect(160,260,35,35)
    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 - the product name will be stored in target.product
    iap.purchase(event.target.product, purchaseListener)

end

---------------------------------
-- 
-- Main game code
--
---------------------------------

--Remove status bar
display.setStatusBar( display.HiddenStatusBar )

--Background
local background = display.newRect(160,240,360,600)
background:setFillColor({type="gradient", color1={ 0,0,0 }, color2={ 0,0,0.4 }, direction="down"})

--Draw "buy 50 coins" button
    --Create button background
    local buy50Background = display.newRect(240, 400, 150, 50)
    buy50Background.stroke = { 0.5, 0.5, 0.5 }
    buy50Background.strokeWidth = 2
    --Create "buy IAP" text object
    local buy50Text = display.newText("Buy 50 coins", buy50Background.x, buy50Background.y, native.systemFont, 18)
    buy50Text:setFillColor(0,0,0)

    --Store the name of the product this button relates to
    buy50Background.product="buy50coins"
    --Create tap listener
    buy50Background:addEventListener("tap", buyCoins)

--Draw "buy 100 coins" button
    --Create button background
    local buy100Background = display.newRect(80, 400, 150, 50)
    buy100Background.stroke = { 0.5, 0.5, 0.5 }
    buy100Background.strokeWidth = 2
    --Create "buy IAP" text object
    local buy100Text = display.newText("Buy 100 coins", buy100Background.x, buy100Background.y, native.systemFont, 18)
    buy100Text:setFillColor(0,0,0)

    --Store the name of the product this button relates to
    buy100Background.product="buy100coins"
    --Create tap listener
    buy100Background:addEventListener("tap", buyCoins)

--Text indicating how many coins the player currently holds

--Get how many coins are currently being held by the player
local coinsHeld = iap.getInventoryValue("coins")
--If no coins are held in the inventory, nil will be returned - this equates to no coins
if (not coinsHeld) then coinsHeld=0 end

coinText = display.newText(coinsHeld .. " coins", 160, 20, native.systemFont, 18)
coinText:setFillColor(1,1,0)

Support

More support is available from the Happy Mongoose Company:

Compatibility

Platform Supported
iOS Yes
Android Yes
Android (GameStick) No
Android (Kindle) Yes
Android (NOOK) No
Android (Ouya) No
Mac App No
Win32 App No
Windows Phone 8 No
Corona Simulator (Mac) Yes
Corona Simulator (Win) Yes


This website uses cookies. Click here to see our privacy policy. Created using the Responsive Grid System.