LiveCode LessonsHow To - LiveCode Mobile Tasks 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

25 Comments

George Vossos

Why doesn't mobileStoreEnablePurchaseUpdates work under Livecode v6.6.2? The documentation instead refers to mobileEnablePurchaseUpdates? Same for all other Apple In-App livcode commands.

Panos

Hi George,
The mobileStoreEnablePurchaseUpdates command, as well as all mobileStore* commands/functions, were first introduced in LiveCode 6.7. So, if you use LC 6.6.2, you have to use the old in-app purchasing related commands, as described in the dictionary.

Warm regards,
Panos
--

Stephen Elkins

Trying to get this code to work ... but it just doesn't. So frustrating. I downloaded the entire LiveCode file and tried it just the way it is and it still doesn't work. Not sure what to do. Is there any way I can send somebody my code to help me get this going? I want to have the app ready to send to Apple by end of week and this is the only thing stopping me right now. Thanks!

Panos Merakos

Hi Stephen,
Thank you for your comment. The attached LC file is not supposed to work just the way it is. You have to replace the product IDs of the example with your own product IDs. These must match the ones you created in iTunes Connect (see step 1). Moreover, you have to use LC 6.7 or above, and make sure you test your in-app purchases on a physical Apple mobile device. Testing on simulator may not work. Furthermore, it is recommended to have a test account set up in iTunes Connect so that you can test your in-app purchases without spending any money.

Warm regards,
Panos
--

Stephen Elkins

Panos! Thank you so much for your quick response. Yeah, I have been trying it with the product IDs with nothing happening. That's why I tried downloading just the original LiveCode file to see if the stack would turn the green background color that was shown in the tutorial but it didn't work. However, that was in 6.6.2 not in 6.7. Downloading that now. Another question ... So, let's say my app is called com.stephenbrucedesigns.marie and my product is called Africa1. Would I put just com.stephenbrucedesigns.marie in product ID or com.stephenbrucedesigns.marie.Africa1? Sorry if these are simple questions. I'm still new to LiveCode. Thank you for your help!

Panos Merakos

Hi Stephen,
You are welcome. The product ID you use in your code must match the product ID of the in-app purchase that has been set up in iTunes Connect for the app. So, if in iTunes Connect you have put "Africa1", you must use "Africa1" in your code. Hope this helps!

Best regards,
Panos
--

Stephen Elkins

Hey Panos. So, on my iPhone, when I click on the "non-consumable" button it asks me to sign in to my account (which I add my test account). When I enter it, I get this error ... "Error occured during purchase handling: Cannot connect to iTunes Store." What does that mean and how can I fix it? Thank you so so SO very much for your help so far. Really appreciate it!

Panos Merakos

Hi Stephen,
No worries. In the standalone application settings, have you replaced the example app ID with your unique app identifier (com.stephenbrucedesigns.marie)? Moreover, it might be the case that your test account has become invalid. I had this problem as well! Try creating a new test account, and use this for your testing :)

Stephen Elkins

Hey Panos. Yep, I changed the app id to mine. I will, however, try adding a new test account. I haven't tried that yet. Thank you! I'll keep you posted in case others have this issue as well.

Panos Merakos

In case you have not done it already, you have to select a valid provisioning profile (in the standalone application settings).

Stephen Elkins

Even with setting up a new test user it still does it. I also tried it with my actual iTunes account and I still get the same error. I double and triple checked my app id. It's good. I tried different provisioning profiles. Nothing works. I'm sure it's something easy and right in front of my eyes:D

Panos Merakos

Mm that's weird. The "Cannot connect to iTunes store" error comes from Apple, not from the LiveCode API that handles in-app purchasing. If you google for this error you will see that is a quite generic and not very helpful one! In my case, the issue was fixed by adding a new test account. Note that first you have to clear any account information stored on your test device. (Settings -> Store -> Sign Out). Then, do not sign in using your test account information in the Store settings panel. Doing so may invalidate your test account.


 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.
That worked for me!

Stephen Elkins

Very weird. It should go through with my actual iTunes account, right? Now, do I have to have the actual app file uploaded to iTunes Connect? I read that you didn't have to in order to test but I'm willing to try anything at this point. Not sure what to do.

Panos Merakos

Yes it should, although you would be charged. No, i don't think it is necessary to upload the actual app file at this point. Another thing to note is that the status of your product should be "ready to submit", in order to be ready for testing. You can check this status in iTunes Connect ("Manage your apps" -> select your app -> "Manage In-App Purchases")

Stephen Elkins

You bring up an interesting point. Mine says "prepare for upload" and not "ready to submit." Do you think that could be the cause?

Panos Merakos

Yeap I am almost sure that this is the cause. If you have a look at Apple's official documentation for in-app purchasing, it is stated that "If the product is 'Ready to Submit', the product’s configuration is complete and ready for you to test the product with your app". So you have to complete all the required fields of your product details (including screenshots etc) in order to change the status to "ready to submit". This will hopefully resolve the issue :)

Stephen Elkins

Once again, thanks for you help Panos. I made a couple changes to the back end on Apple's side ...

1) The app is now "Waiting to Upload"
2) I also noticed this message at the top of my in-app purchase ... "This In-App Purchase is not currently available for testing in the sandbox environment because you have chosen to host your content with Apple, but have not delivered your content. Upload your content to test this In-App Purchase in sandbox."

So, it looks like I do need to upload the content. I think we may be on to something here. Thank you so much! I'll keep ya posted.

Stephen Elkins

Hey Panos. Making some progress. Did come across another issue. How do I create a .pkg file instead of .app in LiveCode? The in-app purchase has to be uploaded as .pkg. Thanks!

Panos Merakos

Hi Stephen,
Yeah you have made some progress! Some things to note first. You are getting these messages because you have chosen to host your in-app purchase content with Apple. This option is available from iOS version 6 and above. This is the reason you have to upload your in-app purchases as .pkg files. You cannot create a .pkg file in LiveCode. The easiest way to do that is with Xcode. It is quite straightforward and does not require any Objective-C programming. In short, all you have to do is create a product in Xcode using a In-App Purchase Content template, include your content, and upload it to iTunes Connect using the Archive process You can find detailed information about this in Apple's official documentation for in-app purchasing, or have a look at this tutorial:

http://www.techotopia.com/index.php/Configuring_and_Creating_App_Store_Hosted_Content_for_iOS_6_In-App_Purchases

Things to note:
1. After selecting "In-App Purchase Content" as the template for your new project, you have to enter the Product Name and Company Identifier such that the Bundle Identifier matches the Product ID you entered in iTunes Connect for this in-app purchase product.
2. It can take some time between uploading the archive and the iTunes Connect status being updated.

If you struggle to make things work (or if you accidentally enabled Hosting Content with Apple!), you can always undo this selection by deleting the In-App Purchase product and re-configuring it. Note that you can’t use the same product ID when you re-create the product. However, I don't think you will have any problems if you read carefully the Apple docs or the tutorial.

I will be out of office for two weeks, but keep me posted and I will reply back as soon as I return. Good luck!

Stephen Elkins

Have a great couple weeks off! Yeah, looks like I'll have to do it in XCode. Which really stinks because I did all the code for the In-Apps Purchases in LiveCode. You see ... I'm developing a kids storybook app. The different stories will be downloaded through in-app purchases. Each story has code for swipe and a page scroller. So, really stinks. Thanks for all your help.

George Vossos

Hi Panos,

Despite having followed all the above, I'm still experiencing fatal crashes when implementing Apple In-App behavior under LiveCode v6.6.2.

Looking at the XCode console log, the crash occurs after a successful call to 'mobilePurchaseSendRequest tID' but before the call to the 'purchaseStateUpdate' handler.

Is there anything glaringly wrong with the handler below?

on purchaseStateUpdate pPurchaseID, pState

switch pState

case "initialized"
break

case "sendingRequest"
break

case "error"
mobileDisablePurchaseUpdates
answer "Error occured during purchase handling:" & return & return & mobilePurchaseError(pPurchaseID)
break

case "cancelled"
mobileDisablePurchaseUpdates
answer "Purchase cancelled."
break

case "restored"
break

case "paymentReceived"
switch mobilePurchaseGet(pPurchaseID, "productID")
case "com.netlifestyle.mixtape.cpackage03"
// some business logic
break

case "com.netlifestyle.mixtape.cpackage06"
// some business logic
break

case "com.netlifestyle.mixtape.cpackage09"
// some business logic
break

end switch

mobilePurchaseConfirmDelivery pPurchaseID

mobileDisablePurchaseUpdates

// my Business Logic goes here ....
paymentReceivedProc
break

end switch

end purchaseStateUpdate

on paymentReceivedProc

answer "Thank you - payment received!"

// some other business logic

end paymentReceivedProc

Panos Merakos

Hi George,

Your code seems fine.Can you also post the code that is responsible for sending the purchase request?

Louis Eo

I found that when this lesson was created the auto renewal subscription was not allowed by Apple to all categories except magazines and newspapers. but Now apple allows all apps implementing this subscription model.

"3.1.2 Subscriptions: Apps may offer auto-renewing in-app purchase subscriptions, regardless of category on the App Store. When incorporating auto-renewable subscriptions into your app, be sure to follow the guidelines below.” - From App Review Guideline.

So here is my question. How can I restore a purchased subscription item in the iOS environment? Can I just adopt code from android in-app lesson? hope you could help me regarding this matter..Thanks for your time.

Louis

Jun

Is push notification required for in app purchase? I saw push notification is checked when I downloaded the test stack.

Elanor Buchanan

Hi Jun, no push notifications are not required. If this is checked in the sample stack it is an oversight.

Kind regards

Elanor

Add your comment

E-Mail me when someone replies to this comment