Halloween Achievement in Pumpkin Patch Match

I guess Halloween is a perfect time for a bone-headed move. For those players of Pumpkin Patch Match, you may notice that there is an achievement that will be awarded if you play the game on October 31… today.

For some reason beyond my comprehension, the check for that achievement is coded incorrectly.  To get this achievement, you will need to do the following:

  • Play the game on October 31.
  • Get a score of 1.

Any other score will not reward the achievement at this time.

I am in the process of uploading an updated version of the game (version 1.1) to all the stores, but in the event that the game is not approved in time, please note that ending a game with a score of 1 will reward the achievement.

My apologies.

Announcing GBC Language Cabinet Plugin for CoronaSDK

GBC Language Cabinet is an easy to use way to display text in different languages. Simply specify which languages you are supporting, create or import tables of translated text, and use a basic function call to return the text in the correct language.

Several options exist to make your life even easier:

  • Text can be created with embedded keys. During the getText call, parameters can then be passed via a table. The returning text will be formatted to replace the keys with the parameters you passed. See below for examples.
  • You can import a comma-delimited file or a json file containing your text (including embedded keys!) instead of using multiple addText calls.

GBC Language Cabinet is available on the Corona Plugin Store.

Announcing GBC Data Cabinet for Corona SDK

GBC Data Cabinet, my free Corona SDK plugin, has been released on the Corona Plugin Store.

GBC Data Cabinet is used to create one or more data repositories, which is then used to store and recall data needed in your application. Data can be used throughout the application, without using global variables.

Data Cabinets can also be saved and recalled at a later time, which is ideal for keeping persistent data such as scores and other values that should not be reset between launches of the application.

For more information, check out the Corona Plugin Store, or view the plugin’s documentation.

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.