×
A new build of Corona SDK is now available to subscribers. Not a subscriber? Subscribe now.
CoronaSDK 2012.821 | Released: 23 May 2012, 2:01am | What's New | Download Now

In-App Purchases

This feature allows you to support In-App Purchases. Currently, only the Apple iTunes Store is supported. In the future, other store fronts may be added.

Using In-App Purchases with the Apple iTunes Store

In-App Purchases allow users to purchase additional content. However, the Apple iTunes Store manages transaction information only! Developers cannot use the Apple App Store to deliver content. So, either you bundle content with your app when you ship it waiting to be unlocked upon purchase, or you have to work out your own system to download the data if you wish to deliver content after the fact. To support downloadable content, you may use our new Network/AsynchHTTP APIs.

For your app to work with Apple's iTunes Store, you must follow Apple's instructions for setting up provisioning on Apple's Provisioning Portal and for setting up purchasing on iTunesConnect. This is a complex process. There are many helpful tutorials available elsewhere on the web; some suggestions are listed at the end of this article. Briefly, the key steps are as follows:

  • make sure you submit you tax and banking information to Apple. In-app purchases will not work without this being cleared and you won't get any error messages telling you this is the problem.
  • on the Provisioning Portal, create a new AppID that is unique and fully qualified (e.g. com.anscamobile.NewExampleInAppPurchase) (don't use wildcards)
  • create a provisioning profile for your AppID
  • on iTunes Connect, create a new App using the same bundle identifier
  • add your purchasable items with their product identifiers and their classifications (Consumable, Non-Consumable, or Subscription)
  • add and configure a test user account to exercise your in-app purchasing code

A sample project is available here: InAppDemo.zip

Using Corona's store module

You'll also need to create code in your Corona app to handle transactions with the App Store.

Initialization / Getting Started

First, you should require the store module into your program
store = require("store")

Next, you need to call the store.init function specifying a listener to handle transaction callbacks (more on that below).

store.init( listener )

You should call store.init() soon after your program finishes launching. The reason is that the Apple App Store likes to aggressively make sure any incomplete transactions can be completed as soon as possible. If a transaction was interrupted the last time your program was run (say due to a phone call or network outage), the user should be able to continue the transaction. By calling store.init(), your app notifies the store that it is ready to handle transaction callbacks.

Getting information about available products for sale

Corona provides store.loadProducts() to retrieve information about items available for sale. This includes the price of each item, a localized name, and a localized description. However, the Apple App Store does not provide a way to query for a list of all available products. Therefore, your code must include (or generate) product identifiers for all items.

store.loadProducts( arrayOfProductIdentifiers, listener )
  • arrayOfProductIdentifiers -- A Lua array with each element containing a string which is the product identifier of the in-app item you want to know about.
  • listener -- A callback function that is invoked when the store finishes retrieving the product information.

Product IDs are what you entered in iTunes Connect. The typical convention is to use your bundle id with the item name appended on, e.g. (com.anscamobile.NewExampleInAppPurchase.MyNonConsumableItem).

The resulting event from the loadProductsCallback listener has the following properties:

  • event.products -- A Lua array with each element containing a Lua table which contains various product information like title, description, price, and the product identifier.
  • event.invalidProducts -- A Lua array with each element containing a string which is the product identifier you requested. You will only get entries here if you requested a product that isn't actually available or exists.

Each entry in the event.products array supports the following fields

  • title -- The localized name of the item.
  • description -- The localized description of the item.
  • price -- The price of an item (as a number)
  • productIdentifier -- The product identifier.

Here is a simple code example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function loadProductsCallback( event )
        print("showing products", #event.products)
        for i=1, #event.products do
                local currentItem = event.products[i]
                print(currentItem.title)
                print(currentItem.description)
                print(currentItem.price)
                print(currentItem.productIdentifier)
        end
        print("showing invalidProducts", #event.invalidProducts)
        for i=1, #event.invalidProducts do
                print(event.invalidProducts[i])
        end
end
 
arrayOfProductIdentifiers = 
{
        "com.anscamobile.NewExampleInAppPurchase.MyConsumableItem",
        "com.anscamobile.NewExampleInAppPurchase.MyNonConsumableItem",
        "com.anscamobile.NewExampleInAppPurchase.MySubscriptionItem",
}
store.loadProducts( arrayOfProductIdentifiers, loadProductsCallback )

If you need your product list to be dynamic, Apple recommends you run your own server to provide a list of up-to-date products your app can retrieve.

Typically, you would use the callback as an opportunity to create and display a UI so a user may browse and buy items.

Can Make Purchases?

iOS devices have a setting that disables purchasing. A common case for this is to prevent children from accidentally purchasing things without parents' permission. Corona provides an API to check whether purchasing is possible. Use this preemptively to avoid having your users navigate through many purchase steps only to find out at the last step that purchasing is forbidden.

store.canMakePurchases

Returns true if purchases are allowed, false otherwise.

Purchasing products

To initiate a purchase, Corona provides store.purchase().

store.purchase( arrayOfProducts )
  • arrayOfProducts -- A Lua array specifying the products you want to buy. Each element may contain a string which is the product identifier or a Lua table with the same fields as the product elements passed back to you from the event.products array in the loadProductsCallback listener.

This function will send out purchase requests to the store. The listener you specified in store.init() will be invoked when the store finishes processing the transaction.

Note: Currently, there is no explicit API to specify quantities for consumable items. However, as a backdoor, you may place the product in the array multiple times and Corona will set the quantity behind the scenes.

Transaction Listener Callback Events

Calling store.init() enables your program to handle transaction callbacks from the App Store using the listener you provide. This listener should handle all of the following circumstances:

  • An item was just purchased (via store.purchase())
  • A purchase transaction was cancelled by the user (after store.purchase() was called)
  • A purchase transaction failed for various reasons (via store.purchase())
  • An item that was purchased in a previous run of the app was interrupted (perhaps due to a phone call) and the App Store is trying to resume/finish the transaction now that it sees that the app is running again.
  • A restore already purchased items request was initiated (via store.restore(), explained below)

The resulting event from the transaction callback listener has the following property:

  • event.transaction -- An object containing the transaction.

The transaction object supports the following read-only properties

  • state -- A string containing the state of the transaction. Valid values are "purchased", "restored", "cancelled", and "failed".
  • productIdentifier -- The product identifier associated with the transaction.
  • receipt -- A unique receipt returned from the Store. It is returned as a hexadecimal string.
  • identifier -- A unique transaction identifier returned from the Store. It is a string.
  • date -- The date of when the transaction occurred.
  • originalReceipt -- A unique receipt returned from the Store from the original purchase attempt. This is mostly relevant in a case of a restore. It is returned as a hexadecimal string.
  • originalIdentifier -- A unique transaction identifier returned from the Store from the original purchase attempt. This is mostly relevant in a case of a restore. It is a string.
  • originalDate -- The date of when the original transaction occurred. This is mostly relevant in a case of a restore.
  • errorType -- The type of error that occurred when the state is "failed" (a string).
  • errorString -- A (sometimes) more descriptive error message of what went wrong in the "failed" case.

After you receive a transaction event, it is up to you to decide what to do. For example, if the user successfully purchased an item, you might record this information in a preference file that unlocks the user's ability to use the item. This file should be referenced in the future so you know that the item has been purchased in future runs. Similarly, if the content must be downloaded, you would initiate the download here.

After you finish handling the transaction, you must call store.finishTransaction() on the transaction object. If you don't do this, the App Store will think your transaction was interrupted and will attempt to resume it on the next application launch.

store.finishTransaction( transaction )

Restoring Purchased Items

Users who wipe the information on a device or buy a new device, may wish to restore previously purchased items without paying for them again. The store.restore() API initiates this process. Transactions that can be restored will be invoked on your transactionCallback listener which you registered with store.init(). The transaction state will be "restored" in this case and your app may then make use of the originalReceipt, originalIdentifier, and originalDate fields of the transaction object.

Transaction Callback Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function transactionCallback( event )
        local transaction = event.transaction
        if transaction.state == "purchased" then
                print("Transaction succuessful!")
 
        elseif  transaction.state == "restored" then
                print("Transaction restored (from previous session)")
                print("productIdentifier", transaction.productIdentifier)
                print("receipt", transaction.receipt)
                print("transactionIdentifier", transaction.identifier)
                print("date", transaction.date)
                print("originalReceipt", transaction.originalReceipt)
                print("originalTransactionIdentifier", transaction.originalIdentifier)
                print("originalDate", transaction.originalDate)
 
        elseif transaction.state == "cancelled" then
                print("User cancelled transaction")
 
        elseif transaction.state == "failed" then
                print("Transaction failed, type:", transaction.errorType, transaction.errorString)
 
        else
                print("unknown event")
        end
 
        -- Once we are done with a transaction, call this to tell the store
        -- we are done with the transaction.
        -- If you are providing downloadable content, wait to call this until
        -- after the download completes.
        store.finishTransaction( transaction )
end
 
store.init( transactionCallback )
 
-- Might try buying something here:
-- store.puchase{ "com.anscamobile.NewExampleInAppPurchase.MyNonConsumableItem" }
-- Or might try restoring here:
-- store.restore()

Additional Notes & Documentation

In App Purchases are not easy to set up. You may find that you spend more time setting things up in iTunes Connect and creating Provisioning Profiles than actually writing code. Here are some tips and links to help you along the way.

  • Create a new App ID and provisioning profile to go with it. Use fully qualified bundle identifier names like com.yourdomain.yourapp. Don't use wildcards like com.*
  • Some people claim it may take 24 hours for your in-app purchase settings to propagate through Apple's network. This means you should set up all the application stuff in iTunes Connect early so by the time you are ready to test code, you don't have to wait. Unfortunately, you won't know if the failures are due to network propagation delays or something else which is why you should get this set up early.
  • Remember to create a Test User account and, when testing, to sign out of your own account in your iOS device's Settings->Store. Don't log into the test account here; wait until you run your app and then log in as the test user when you are prompted to do so.
  • When you build your product using Corona, make sure you use the correct provisioning profile (the one you created for this app with the fully qualified bundle identifier).
  • Apple Links

    Other Links

    Replies

    Viewing options

    Select your preferred way to display the comments and click "Save settings" to activate your changes.
    Philipp's picture
    Philipp
    User offline. Last seen 4 weeks 6 days ago. Offline
    Joined: 19 Oct 2010

    The inAppDemo.zip uses
    event.transaction.originalTransactionIdentifier but the tutorial above uses event.transaction.originalIdentifier, which one is correct, or am I misunderstanding this?

    jeremyapplebaum12
    User offline. Last seen 17 hours 21 min ago. Offline
    Joined: 20 Jan 2011

    How do you do step three on the walk through link?

    Naomi's picture
    Naomi
    User offline. Last seen 9 hours 8 min ago. Offline
    Joined: 6 Jun 2011

    EDIT: I was able to fetch valid product back from the sandbox, and the above documentation is correct. I am so sorry for posting something that wasn't fully tested properly.

    1
    2
    3
    4
    5
    6
    7
    
    --what we see above and what we see in In-App Purchase demo code
    arrayOfProductIdentifiers = 
    {
            "com.anscamobile.NewExampleInAppPurchase.MyConsumableItem",
            "com.anscamobile.NewExampleInAppPurchase.MyNonConsumableItem",
            "com.anscamobile.NewExampleInAppPurchase.MySubscriptionItem",
    }

    Please ignore this post (what it suggested does not seem to be true):
    http://developer.anscamobile.com/forum/2011/06/28/loadproducts-problem

    Naomi's picture
    Naomi
    User offline. Last seen 9 hours 8 min ago. Offline
    Joined: 6 Jun 2011

    Also, remember to use Ad Hoc Distribution method to test on device. I read through the "Technical Note TN2259: Adding In App Purchase to your iOS Applications" just once before I started implementing In-App Purchase, but along the way, I found myself trying to test using dev distribution method, which absolutely failed and sent me trying to fix something that is not fixable (because what I thought was a problem was not a problem and need not and could not be fixed).

    jeremyapplebaum12
    User offline. Last seen 17 hours 21 min ago. Offline
    Joined: 20 Jan 2011

    So what your saying is I need two list of iIdentifiers, one for the product id and one for what it is?

    Also do I get the product identifier from itunes connect (manage in app) or the prov profile (it's probably itunes connect)?

    Thanks.

    Naomi's picture
    Naomi
    User offline. Last seen 9 hours 8 min ago. Offline
    Joined: 6 Jun 2011

    @jeremyapplebaum12, let's resume this discussion on forum under In-App Purchase folder.

    saravanan.vbe
    User offline. Last seen 3 weeks 4 days ago. Offline
    Joined: 16 Sep 2011

    i am fresher to corona development i develop game it have 10 level .it is free game in that i will active first three level and we want play fourth level we want buy product. so implement in app purchase .its work but i have problem in .......

    when i load app first time i will click product to buy it get error message itunes(could not connect itunes store)

    and close app and open again it work prefect i.e i make purchase again its work correct purchase corectly

    akao's picture
    akao
    User offline. Last seen 1 week 1 day ago. Offline
    Joined: 25 Feb 2011

    Does anyone know about the localization of prices? For example, when someone from Japan is playing my app, would the product[i].price return the value in US Dollar or in Japanese Yen?

    Also, anyone knows how to display the price using the correct locale? The equivalent of the following code in native iOS?

    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
    [numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
    [numberFormatter setLocale:product.priceLocale];
    NSString *stringPrice = [numberFormatter stringFromNumber:product.price];
    [numberFormatter release];

    Thanks!

    Philipp's picture
    Philipp
    User offline. Last seen 4 weeks 6 days ago. Offline
    Joined: 19 Oct 2010

    "localizedPrice" will return the localized prize, i.e. the user's currency value and symbol, like "0,79€". (Note that when you use your own font, you should try test how it handles a symbol like "€", as you might be in for some font size surprise or so.)

    I'm using below code in my games, use on your own risk :)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    function appLoadProductsCallback(event)
        if event ~= nil and event.products ~= nil then
            local price = nil
            for i = 1, #event.products do
                if event.products[i].productIdentifier == app.products[1].id then
                    price = event.products[i].localizedPrice
                    if price == nil or price == '' then price = event.products[i].price end
                    break
                end
            end
     
            local priceText = appGetSpriteByType('buttonPart', 'price')
            if price ~= nil and priceText ~= nil then
                priceText.text = price
                app.diamondsPackPriceCached = price
            end
        end
    end

    Note things like "app.products" and "appGetSpriteByType" or "app.diamondsPackPriceCached" are specific to my framework, adapt as needed. Also I'm using a bit of a defensive code there which you may wanna throw out, depending (e.g. testing for nils of localizedPrice etc.)

    don's picture
    don
    User offline. Last seen 19 hours 6 min ago. Offline
    Joined: 24 Jan 2011

    I didn't see this stated explicitly anywhere, but store.restore() does not restore consumables.

    http://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/StoreKitGuide.pdf

    page 19

    saravanan.vbe
    User offline. Last seen 3 weeks 4 days ago. Offline
    Joined: 16 Sep 2011

    i am fresher to corona development i develop game it have 10 level .it is free game in that i will active first three level and we want play fourth level we want buy product. so implement in app purchase .its work but i have problem in .......

    when i load app first time i will click product to buy it get error message itunes(could not connect itunes store)

    and close app and open again it work prefect i.e i make purchase again its work correct purchase corectly

    yone
    User offline. Last seen 1 week 2 days ago. Offline
    Joined: 24 Apr 2011

    I took 3 days trying to configure the In App Purchases, without success. Until I got to create a certificate Ad Hoc with the App ID that I have set in iTunes Connect for App.

    My mistake was wearing a certificate for App Store with correct App ID, but the AD Hoc certificate test I was used for the App ID => *

    :) :)

    mandar.l
    User offline. Last seen 3 days 19 hours ago. Offline
    Joined: 15 Aug 2011

    Hi everyone,

    Is there any way to use In-app purchase in android in corona..?

    Thanks,
    -Vaibhav

    uneni
    User offline. Last seen 7 weeks 6 days ago. Offline
    Joined: 10 Nov 2011
    saravanan.vbe
    User offline. Last seen 3 weeks 4 days ago. Offline
    Joined: 16 Sep 2011

    i am fresher to corona development i develop game it have 10 level .it is free game in that i will active first three level and we want play fourth level we want buy product. so implement in app purchase .its work but i have problem in .......

    when i load app first time i will click product to buy it get error message itunes(could not connect itunes store)

    and close app and open again it work prefect i.e i make purchase again its work correct purchase corectly

    overbeat
    User offline. Last seen 5 hours 35 min ago. Offline
    Joined: 10 Jan 2012

    Please tell more about downloadable content, how to organize everything, when you have 200-300 files in your in-app purchase pack.