Coding and Testing In-App Purchases in Corona – Part 1

Recently, I spent some time updating one of my apps, and spent a lot of time looking over my in-app purchase (IAP) code. Corona makes this very easy to implement, but there are some minor things to watch out for, especially if you are publishing to multiple stores. This page on the Corona site is a valuable resource, and hopefully this post will give you additional information.

Testing, on the other hand, is not as easy as you would think, since each store has their own way of handing this.

In this two part blog, I will outline my recent experiences in coding and testing IAP in Corona. Part one is all about coding IAP using Corona. We will cover testing in Part 2.

Coding for Various Stores
Three Stores, Three Libraries

Let’s configure the Apple, Google, and Amazon stores. First, we need to determine which library to use.

local store = nil
local platform = system.getInfo("targetAppStore")

if platform == "apple" then
    store = require ("store")
elseif platform == "google" then
    store = require("plugin.google.iap.v3")
elseif platform == "amazon" then
    store = require ("plugin.amazon.iap")
end

Easy enough, find the target app store and load the library. Of course, make sure your build.settings file includes the Google and Amazon IAP plugins.

Next, we need to initialize the store library.  If using Composer, the scene:show or scene:create function is probably a good place for this.

if platform == "amazon" then
    store.init(StoreListener)
else
    store.init(platform, StoreListener)
end

Notice the difference?  The Amazon store does not require the store name.

That’s really all you need for set up. Next, let’s cover making purchases. This is also not too hard to code in Corona, but you need to be aware of what is returned to your listener for each store, since it is different. To get you started, we’ll build a simple store listener below.

When a user attempts to make a purchase, you need to let the store know, and you also need to listen for the store’s response.  The following code does this.

local function StoreListener(event)
    local transaction = event.transaction

    if transaction == "purchased" then
        -- successful purchase. Give user what they paid for

    elseif transaction == "cancelled" then
        -- User decided to cancel purchase

    elseif transaction == "failed" then
        -- D'oh, something went wrong. User could not purchase item.
    end

    store.finishTransaction(transaction)
end

-- Enter your purchase string here
local itemToPurchase = "com.mydomain.item"

-- make a purchase
if platform == "apple" then
    store.purchase({itemToPurchase})
else
    store.purchase(itemToPurchase)
end

Notice that the Apple store expects a table of items… even if there is only one. Amazon and Google do not support purchasing multiple items, so we pass in the actual string of the item we wish to purchase.

In the store listener, we are listening for a successful purchase, a cancel, or a failure. It’s up to you to code what you want to do when these events come in.

Notice that we must call finishTransaction to clean up, otherwise the store will not know that the transaction is complete. This is required for Apple, but no harm calling this for all other stores.

Restoring A Purchase

In the event a user had to reinstall your app, or in the event a person upgrades their phone, it’s a good idea to allow a user to restore their purchase. This is usually done when a user taps on a “Restore Purchase” button, and it’s as simple as calling store.restore() and listening for the “restored” event, with one exception. Google does not have a “restored” event. When restoring on the Google Play store, a “purchased” event is returned. So, how do you determine whether the “purchased” event is from a purchase or a restore? Let’s look at our modified store listener:

local function StoreListener(event)
    local transaction = event.transaction

    if transaction == "purchased" then
        if isGoogleRestore then
            -- Restore purchase code goes here
        else
            -- successful purchase. Give user what they paid for
        end

    elseif transaction = "restored" then
        -- Apple or Amazon restore code goes here

    elseif transaction == "cancelled" then
        -- User decided to cancel purchase

    elseif transaction == "failed" then
        -- D'oh, something went wrong. User could not purchase item.
    end

    store.finishTransaction(transaction)
end

-- define this variable 
local isGoogleRestore

-- place this in your restore button handler
if platform == "google then isGoogleRestore = true else isGoogleRestore = false end
store.restore()

Here it gets a bit tricky. For Apple and Amazon, you handle a restore by listening for a “restored” event. For Google, you can set a boolean value (isGoogleRestore) during your restore purchase code and listen for a “purchased” event. In the purchased event of your store listener, determine whether this is a Google restore or a normal Google/Apple/Amazon purchase by checking isGoogleRestore, and handle appropriately.

A Couple of Other Events

Amazon can send a “revoked” event, and Google can send a “refunded” event in cases where refunds were applied. If you listen for these, it’s again up to you to decide how to handle, but it basically involved removing the features that were previously purchased.

Consumables

Consumables are items that can be purchased over and over again (coins, power-ups, etc). You should not and can not restore consumables via the store, but a user can buy these as many times as they want.

Google treats all items as non-consumable (Apple and Amazon allow you to create these items and identify them as consumable in their dashboards). Since non-consumable items can only be purchased once, how do we allow a consumable item to be purchased again?  Easy, with this piece of code:

if platform == "google" then
    -- call this for every consumable item you have in the store
    store.consumePurchase(myConsumableItem)
end

This call may take some time, so I like to place it in my scene:show event (in Composer). You can listen for a “consumed” event in your store listener if you need to do some post-processing. If the user now decides to purchase a consumable item, Google will allow it.

Conclusion

This is the basis of coding IAP in Corona. I did not cover everything (such as loading products), but this should get you going. In a future blog post, we will discuss how to test IAP on each store.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: