LiveCode LessonsLiveCode 6.7 In App PurchasingHow do I implement in-app purchases in LiveCode - Apple AppStore?

How do I implement in-app purchases in LiveCode - Apple AppStore?

1. Creating an in-app purchase

In order to use the in-app purchase with LiveCode, we must first create it in iTunes Connect. From the front page of iTunes Connect, go to "Manage Your Applications", select the app for which you wish to create an in-app purchase, click "Manage In-App Purchases", and click "Create New". From there, follow the instructions to create the type of in-app purchase you wish to use. In the stack for this lesson, we are using a consumable and a non-consumable purchase.

2. Setting up a stack to access in-app purchases

2. Setting up a stack to access in-app purchases

First create two buttons. One button for consumable, and one button for non-consumable purchase. Then add code that allows us to interface with iTunes Connect and request the in-app purchases for download.

To our consumable purchase button, add the following code:

on mouseUp

  mobileStoreEnablePurchaseUpdates

  mobileStoreSetProductType "com.runrev.sampleapp.consumable", "inapp"

  mobileStoreMakePurchase "com.runrev.sampleapp.consumable", "1", "This belongs to me"

end mouseUp

 

And similarly, add the following to our non-consumable purchase button:

on mouseUp

  mobileStoreEnablePurchaseUpdates

  mobileStoreSetProductType "com.runrev.sampleapp.nonconsumable", "inapp"

  mobileStoreMakePurchase "com.runrev.sampleapp.nonconsumable", "1", "This belongs to me"

end mouseUp

 

Finally, add the following code to the stack script:

on purchaseStateUpdate pPurchaseID, pProductID, pState

  switch pState

     case "paymentReceived"

        answer "payment received!"

        offerPurchasedProduct pProductID

        mobileStoreConfirmPurchase pProductID

        mobileStoreDisablePurchaseUpdates

        break

     case "error"

        answer "Error occured during purchase handling:" & return & return & mobileStorePurchaseError(pPurchaseID)

        mobileStoreDisablePurchaseUpdates

        break

     case "invalidSKU"

        answer "Invalid SKU."

        mobileStoreDisablePurchaseUpdates

        break

     case "alreadyEntitled"

        answer "Already Owned."

        mobileStoreDisablePurchaseUpdates

        break

     case "restored"

        answer "restored"

       offerPurchasedProduct pProductID

        mobileStoreConfirmPurchase pProductID

        mobileStoreDisablePurchaseUpdates

        break

     case "cancelled"

        answer "Purchase Cancelled:" && pProductID

        mobileStoreDisablePurchaseUpdates

        break    

  end switch

end purchaseStateUpdate

 

on offerPurchasedProduct pProductID

  if pProductID is "com.runrev.sampleapp.consumable" then

     set the cConsumablesCount of this stack to (the cConsumablesCount of this stack + 1)

  else if pProductID is "com.runrev.sampleapp.nonconsumable" then

     set the cNonConsumablePurchased of this stack to true

  end if

end offerPurchasedProduct

The mobileStoreEnablePurchaseUpdates command allows you to monitor the status of each in-app purchase request, using the built-in message, purchaseStateUpdate. This is handled to check the status of the in-app purchase.

The mobileStoreSetProductType command is used to set the type of the in-app purchase. The type can either be “inapp” for consumable and non-consumable items, or “subs” for subscriptions.  This distinction is not always necessary, since it is only used by the underlying Google API for in-app purchases, because it uses different methods, depending on the item type. However, it is recommended that you always use this command, even for non-Google stores.  The mobileStoreSetProductType command is used with the identifier and the type of the in-app purchase we are requesting. These values must match the identifier and the type of an in-app purchase that has been set up in iTunes Connect for the app.

The mobileStoreMakePurchase command is then used with three parameters, denoting the in-app purchase identifier we are requesting, the requested quantity of the item and the developer payload. The quantity should always be “1” for non-consumable and subscription items. The developer payload is a string that contains some extra information about the order.  This command sends the request and begins the purchasing process. The purchaseStateUpdate message is then generated as the purchasing process takes place.  This is sent with three parameters denoting the product identifier, the purchase identifier and the state of the specific purchase that the message is regarding. In the handler, the state of the purchase is checked using the parameter pState. Here we take action if it returns paymentReceived, indicating that payment for the purchase was received, or restored, indicating that the restoration request was successful. In all other cases, we print a message to the screen giving details on what has happened.

In case of an error, we can use mobileStorePurchaseError with the purchase ID to find the details of the error and then use the mobileStoreDisablePurchaseUpdates command to indicate that the purchase process is complete. In cases where the item is already owned by the user, or in cases where there is no item with the specified identifier in the store listing, or in cases of the user cancelling the purchase, we use mobileStoreDisablePurchaseUpdates again. In case of payment being received, we take action in our app to finalize the purchase process. As there are multiple possible in-app purchases in this app, we use the pProductID parameter with offerPurchasedProduct method, to find out which in-app purchase we are dealing with. We can then take appropriate action for the in-app purchase that has been purchased, and use the mobileStoreConfirmPurchase command to confirm to Apple that payment has been received, or that the restoration operation has been successful, finalizing the in-app purchase process on Apple’s side.

The bundle identifier that you create the standalone application with (as specified in the standalone application settings\Pmust match the bundle identifier tied to the in-app purchases in iTunes Connect.

3. Handling in-app purchases once they have been purchased

3. Handling in-app purchases once they have been purchased

The above step handles a purchase. In this app, we set a custom property to indicate to the app that the purchase has been made. With the non-consumable and the subscription purchase we set a custom property to true, but with the consumable in-app purchase, the user can purchase it as many times as they like. We increment a custom property to indicate how many times a purchase has been made.

We can now use these custom properties to take action based on the in-app purchases the user makes. Handling the in-app purchase process records the purchases with Apple and transfers money. For this app, we are using the non-consumable purchase to change the background color of the stack and change the text of a field. The consumable purchase is used to make a graphic flash on and off briefly.

For the non-consumable purchase, we can use a setProp to implement this:

setProp cNonConsumablePurchased pValue

  set the cNonConsumablePurchased of this stack to pValue

  if pValue then

     set the backColor of this stack to 0,255,0

     put "PURCHASED" into fld "purchased"

  else

     set the backColor of this stack to 255,255,255

     put "NOT PURCHASED" into fld "purchased"

  end if

end cNonConsumablePurchased

For the consumable purchase, we want the user to be able to activate it whenever they want rather than immediately. For this we use two handlers, a setProp and a button. The setProp indicates to the user how many consumable purchases they have remaining, and the button is used to activate the consumable.

Our setProp will look like this:

setProp cConsumablesCount pValue

  set the cConsumablesCount of this stack to pValue

  put pValue into fld "consumablescount"

end cConsumablesCount

 

And our button should have this code:

on mouseUp

  if the cConsumablesCount of this stack > 0 then

     set the cConsumablesCount of this stack to (the cConsumablesCount of this stack - 1)

     repeat with x = 1 to 10

        wait 100 millisecs with messages

        if the backcolor of grc "consumablegrc" is 255,255,255 then

           set the backcolor of grc "consumablegrc" to 0,0,255

        else

           set the backcolor of grc "consumablegrc" to 255,255,255

        end if

     end repeat

     set the backcolor of grc "consumablegrc" to 255,255,255

  else

     answer "you need to purchase a consumable first!"

  end if

end mouseUp

This will let use 'use up' a consumable each time the user clicks the button, and only let them carry out the action we want if they have purchased a consumable in the first place.

4. Making purchases persistent

4. Making purchases persistent

One problem that you may have noticed, is that it is not possible to save a stack that is running under iOS (due to technical restrictions imposed by Apple). Therefore, our custom properties will revert to whatever values they are at build-time, each time the app is closed. We therefore need to somehow save our custom properties, so that we can re-load them every time we open up the app on our mobile device.

A first thought would be to use a restore button.

Our button should have this code :

on mouseUp

  mobileStoreEnablePurchaseUpdates

  mobileStoreRestorePurchases

end mouseUp

Here, the command mobileStoreRestorePurchases will trigger a callback to the purchaseStateUpdate message. The purchaseStateUpdate message will be called as many times as the number of non-consumable items we have already purchased, each time with a different productID and purchaseID, but always with the same state, restored. In that way, we can offer again (this time for free\P to the user the products they have already bought. However, this approach is not that good, for the following reasons:

1. Consumable products cannot be restored (for our example, this means that purchaseStateUpdate will be not be called with productID = "com.runrev.sampleapp.consumable"). This is because consumable products are not meant to be available in any device other than the device where they where originally purchased. Thus, we need a mechanism to store locally that the user has purchased a number of consumable products.

2. Non-consumable products can be restored, but is recommended by Apple not to use the restoration operation that often. Restoring purchases prompts for the user’s AppStore credentials, which interrupts the flow of your app. Because of this, do not automatically restore purchases, especially not every time your app is launched. Thus, we could again use a mechanism to store locally the non-consumable purchases of the user, and use the restore button only in cases where the user switches devices.

One simple way of doing this is adding some code to our setProps which will write the value of the custom property to a location outside of the .app bundle. For example, this could be an SQLite Database or a text file. In this case, we will write a text file to the documents folder. We add the following handlers to our stack script and call them at the end of our setProps for each custom property as follows:

on saveConsumablesCount

  put the cConsumablesCount of this stack into url("file:" & specialFolderPath("documents"\P \ & "/consumablescount.txt")

end saveConsumablesCount

 

on saveNonConsumablePurchased

  put the cNonConsumablePurchased of this stack into url("file:" & specialFolderPath("documents"\P \ & "/nonconsumablepurchased.txt")

end saveNonConsumablePurchased

 

setProp cNonConsumablePurchased pValue

  set the cNonConsumablePurchased of this stack to pValue

  if pValue then

     set the backColor of this stack to 0,255,0

     put "PURCHASED" into fld "purchased"

  else

     set the backColor of this stack to 255,255,255

     put "NOT PURCHASED" into fld "purchased"

  end if

  saveNonConsumablePurchased

end cNonConsumablePurchased

 

setProp cConsumablesCount pValue

  set the cConsumablesCount of this stack to pValue

  put pValue into fld "consumablescount"

  saveConsumablesCount

end cConsumablesCount

 

And we then simply load these values back in each time we open the app, by adding the following code:

on loadConsumablesCount

  put (specialFolderPath("documents"\P \ & "/consumablescount.txt"\P \ into tPath

  if there is a file tPath then

     set the cConsumablesCount of this stack to url("file:" & tPath)

  end if

end loadConsumablesCount

 

on loadNonConsumablePurchased

  put (specialFolderPath("documents"\P & "/nonconsumablepurchased.txt"\P into tPath

  if there is a file tPath then

     set the cNonConsumablePurchased of this stack to url("file:" & tPath)

  end if

end loadNonConsumablePurchased

 

on preOpenStack

  loadNonConsumablePurchase

  loadConsumablesCount

end preOpenStack

We now have a system which can control our in-app purchases simply by setting the value of the relevant custom properites, without having to worry about manually saving or loading them, or losing them.

Note: You have to include some mechanism in your app to let the user restore their purchases across different devices, or else your app will be rejected by Apple.

5. Testing purchases

From the front page of iTunes Connect, go to "Manage Users", select "Test User", and click "Add New User". From there, follow the instructions to create a new test user account. You will need a test user for each territory you want to test the app in.\L Then clear any account information stored on your test device. (Settings -> Store -> Sign Out).

Do not sign in using your test account information in the Store settings panel. Doing so may invalidate your test account.\L\L\L Then launch your app and make a purchase. A window pops up and prompts you to sign in. Choose "Sign in using existing Apple ID", and use your test account credentials to sign in. You will not be charged for any purchases you make using your test user account.

If you notice unusual behaviour in the purchase flow, for example a "Cannot connect to iTunes store" error, then try creating and using a new test user account.

6. A note about subscription items in iOS

You may noticed in this lesson that we did not use any subscription item, as we did in the other in-app purchasing lessons for android stores. This is because iOS handles subscription items differently. There are two kinds of subscriptions:

1. Auto-renewable subscriptions, where automated billing occurs every fixed period.

2. Non-renewable subscriptions that are not available after the period expires. These are like short-term items. The user can buy an item again after it has expired.

Apple has placed strict rules around auto-renewable subscriptions, meaning their usage is (usually\P exclusive to Newsstand apps.

As a result of this restriction, the subscription item that was used in the other 3 in-app purchase lessons for the android stores is not ideally suited for the Apple AppStore. We can use that subscription item for testing purposes, to see how automated billing occurs, but it is most likely going to be rejected by Apple in the app approval process, because it does not fall into the category of products that are suitable for auto-renewable subscriptions. This means that if we want to use the same example for this lesson, we have to implement it as a non-renewable subscription.

Unlike auto-renewable subscriptions, where subscription durations and renewals are handled through the StoreKit framework, this is the underlying framework that is used when interacting with the Apple AppStore, non-renewable subscriptions require the developer to do all the heavy lifting. Which means that the developer is responsible for:

1. Checking the purchase date of the non-renewable subscription item and making sure that the item will only be available during this period. This could be implemented by locally storing the purchase time in the app and checking each time when the app launches if the period has expired.

2. Making the non-renewable subscription item accessible from every device the user owns. This means that locally storing the purchase time is not sufficient. The solution could be to store the purchase time - as well as the user's email address on a server, and checking each time when the app launches if the period has expired.

You can find more information on subscription items for iOS on the official website:

Subscription Items for iOS