How do I implement in-app purchases in LiveCode - Google Play Store?

This lesson shows you how to handle in-app purchasing in LiveCode, for apps distributed through the Google Play Store. It is assumed that you have already uploaded a signed .apk file in the Google Play Developer Console.

You can download the sample stack from this URL: https://tinyurl.com/ya6mwptr

Note: You need LiveCode 6.7+ to follow this lesson.

1. Creating an in-app purchase

1. Creating an in-app purchase

In order to use the in-app purchase with LiveCode, we must first create it in the Google Play Developer Console. From the front page of Google Play Developer Console, go to “All Applications", select the app for which you wish to create an in-app purchase, click "In-App Products”, and click “Add new product” . 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, a non-consumable and a subscription purchase.

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

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

First create three buttons. One button for consumable, one button for non-consumable and one button for subscription purchase. Then add code that allows us to interface with the Google Play Store 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

 

Add the following to our non-consumable and subscription purchase buttons:

on mouseUp
   mobileStoreEnablePurchaseUpdates
   mobileStoreSetProductType "com.runrev.sampleapp.nonconsumable", "inapp"
   mobileStoreMakePurchase "com.runrev.sampleapp.nonconsumable", "1", "This belongs to me"
end mouseUp

 

on mouseUp
   mobileStoreEnablePurchaseUpdates
   mobileStoreSetProductType "com.runrev.sampleapp.sub", "subs"
   mobileStoreMakePurchase "com.runrev.sampleapp.sub", "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" and the cConsumablesCount of this stack is 0 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 
   else if pProductID is "com.runrev.sampleapp.sub" then
      set the cSubPurchased 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 subscription items.  This distinction is necessary, since the underlying Google API for in-app purchases uses different methods, depending on the item type. Failure to specify the type of the item can result in rejection of your purchase request from the Google Play Store.  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 the Google Play Developer Console 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 iOS apps. 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 user is prompted to enter Google ID details in order to buy the product (remember to use the test account details when testing this feature, or you may be charged). A 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. In this case we only want to take action if the result is paymentReceived, indicating that Google has received payment for the purchase, or restored, indicating that the restoration request was successful. In all other cases, a message is printed to the screen, giving details on what is happening.

In case of an error, mobileStorePurchaseError can be used 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. This is not essential in all circumstances, but is good practice. mobileStoreDisablePurchaseUpdates is used in cases where the item is already owned by the user, or there is no item with the specified identifier in the store listing, or the user cancels the purchase. In case of payment being received, or the purchase being restored, the app can take action to finalize the purchase or the restoration process. This app permits multiple in-app purchases and we use the pProductID parameter with offerPurchasedProduct to find identify which in-app purchase is being being processed. We can then take appropriate action for the in-app purchase that is being purchased or restored, and use the mobileStoreConfirmPurchase command to confirm to Google that payment has been received, finalizing the in-app purchase process on the Google Play Store side.

Note: The bundle identifier that you create the standalone application with, as specified in the standalone application settings, must match the bundle identifier tied to the in-app purchases in Google Play Developer Console.

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. For the most recent Google API (v3), the previous sentence is almost true. This is because Google in-app billing v3 API has an additional restriction that ensures a consumable product is consumed before another instance can be purchased. Consume means that the purchase is removed from the user's inventory of purchased items, allowing the user to buy that product again. For this reason, we need a “consume product” button.

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 Google 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. The subscription purchase is used to change the color of a button.

For the non-consumable and the subscription 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"
   end if
end cNonConsumablePurchased

 

setProp cSubPurchased pValue
   set the cSubPurchased of this stack to pValue
   if pValue then
      set the backgroundColor of button "sub"  to "0,0,255"
   end if
end cSubPurchased

 

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 consume and activate the consumable. Remember that Google API for in-app purchases does not allow you to make subsequent purchases, until the previous one is consumed.

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
   mobileStoreConsumePurchase "com.runrev.sampleapp.consumable"
   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 'uses up' a consumable each time the user clicks the button, and only lets 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

Our custom properties revert to whatever values they are at build-time, each time the app is closed. One could therefore assume that custom properties have to be saved explicitly, allowing us to re-load them every time the app is opened. This is not the case with the new Google in-app purchasing API. The app is no longer responsible to retaining what the user has bought. All the purchased items can be restored, not only in the particular device where the app was installed, but in every device the user owns. This can be done using a “restore” button.

Note: As a general rule, consumable products cannot be restored. Therefore, the app should have a mechanism to "remember" the consumable purchases of the user. However, since the new Google in-app purchasing API does not distinguish between consumables and non-consumables, and treats them all as "managed" products, it does restore consumable purchases. This is not the case when dealing with other store APIs such as Amazon, Samsung or Apple,  where the app is responsible for remembering the consumable purchases of the user.

Our restore button should have this code:

on mouseUp
   mobileStoreEnablePurchaseUpdates
   mobileStoreRestorePurchases
end mouseUp

Here, mobileStoreRestorePurchases command queries the user’s inventory of purchased items, and returns them in the purchaseStateUpdate pPurchaseID, pProductID, pState message. The purchaseStateUpdate message is called as many times as the number of items the user owns, each time with a different pPurchaseID and pProductID, but always with the same pState : "restored”. In that case, the flow should be exactly the same as when the user successfully purchases the product for the very first time, with the difference that this time no actual payment occurs!

In other words, what mobileStoreRestorePurchases does, is simulating a successful purchase flow without charging the user’s credit card for all the items that the user has already purchased. The underlying Google in-app purchasing API remembers what the user has bought, and does not allow them to buy an item that they already own, even if they do not call mobileStoreRestorePurchases. To make this clearer, imagine the following scenario for our example app:

1. User launches the app for the very first time.

2. They click on the non-consumable button, and send a purchase request to the Google Play Store for the non-consumable item.

3. The Google Play Store responds and asks for the user’s password. The user types their password.

4. The Google Play Store responds that the purchase is successful.

5. purchaseStateUpdate message is called with parameters (“1”, ”com.runrev.sampleapp.nonconsumable”, “paymentReceived”).

6. The app provides the user with the purchased item, i.e. the background becomes green.

Now, if the user exits and re-launches the app, the background is not green. But if they select to purchase the non-consumable item again, they get an “Already Owned” message. The purchaseStateUpdate message is called with parameters (“1”, ”com.runrev.sampleapp.nonconsumable”, “alreadyEntitled”). If they want to get the purchased item, turning the background green, all they need to do is click on the “restore” button, which calls mobileStoreRestorePurchases.

5. Testing purchases

5. Testing purchases

In brief, there are two ways to test your purchases without being charged :

1. Using a test user account.

2. Using static responses.

Using a test user account

In the Google Play Developer Console main page, go to “Settings”.  Then, in the field “Gmail accounts with testing access”, type a valid gmail address. This account has testing access, and is not charged when making a purchase through your app.

Note: You have to log out from your current account in your android device, and log in again using the test account details. Google does not allow testing subscriptions using test accounts. This means that if you buy a subscription item using your test account, you are charged.

Using static responses

To test your implementation with static responses, make an In-app Billing request using an item that has a reserved product ID. Each reserved product ID returns a specific static response from Google Play. No money is transferred when you make In-app Billing requests with the reserved product IDs. Also, you cannot specify the form of payment when you make a billing request with a reserved product ID.

There are four reserved product IDs for testing static In-app Billing responses:

1. android.test.purchased

2. android.test.refunded

3. android.test.canceled

4. android.test.item_unavailable

You can find more detailed information on how to test in-app purchases on the official webpage for testing Google in-app purchasing API:

http://developer.android.com/google/play/billing/billing_testing.html

6. Important notes

1. In the Standalone Builder Settings, make sure you have entered your public key correctly, without using spaces. You can find your public key for this application in Google Play Developer Console. From the main page of Google Play Developer Console, select "All applications", then select the application for which you created in-app purchase products. Then, go to "Services and APIs", and copy the string under "YOUR LICENCE KEY FOR THIS APPLICATION". Finally, paste it in relevant field in the Standalone Application Settings.

2. Make sure that your .apk is signed, and upload it in the Google Play Developer Console as "Published". Google has recently changed some terms, and your application has to be at "Published" state in order to test in-app purchases, and not at "Draft".

3. Make sure the version number you set in Google Play Developer Console matches the version number in the Standalone Application Settings.

4. Make sure your in-app products have the "Active" status, or else you get a "Product does not exist in the store" error.

5. Install the .apk to the device using the adb command. If you install it to the device using Development--> Test target--> Test from the LiveCode menu, you get an error "The item that you were attempting to purchase could not be found". This happens because the .apk is recognized as a different one from the one you have uploaded in Google Play Developer Console, thus the API cannot find your in-app products.

6. Make sure your Google Play version is 3.10.10 or higher, since :

     Purchasing and querying managed in-app items requires a Google Play client version 3.9.16 or higher.

     Purchasing and querying subscription items requires a Google Play client version 3.10.10 or higher.

7. Keep in mind that Google needs some time for the changes to take effect when you update or add new information in the Google Play Developer Console. This can be from 15 minutes to 4 hours.

19 Comments

Trevix

Is it now in Google Play under the menu "Monetization setup"?
You can find your public key for this application in Google Play Developer Console. From the main page of Google Play Developer Console, select "All applications", then select the application for which you created in-app purchase products. Then, go to "Services and APIs", and copy the string under "YOUR LICENCE KEY FOR THIS APPLICATION". Finally, paste it in relevant field in the Standalone Application Settings.

Panos Merakos

Hello Trevix,

This is no longer needed in LiveCode 9.6.5+, where a new version of the Google In-App Billing Library is used. I'll file a bug report to remove this field from the Standalone Settings.

Cheers,
Panos

Trevix

Hello. Struggling with iOS in app purchase (IAP), I am trying now with Google.

On Google console:
1- created a test user account (the one used on my hardware Android 7 cell phone) on "Setup/License testing"
2- created 2 IAP products (for my published app) with ProductID "002rf" and "001rf" (for consumable and non consumable) I set them to active
3- created a Subscription with productID "003sub"

On the App
4- My published app has the following identifier "com.trevix.segnapunto" and already has IAP flagged on Google
5- I replaced the identifier in your test stack standalone setting with my identifier
6- I signed the standalone setting with my key
7- I set the version number with the same version number of the published app
8- the IAP is flagged on Google
9- on the consumable button I replaced the string "com.runrev.sampleapp.consumable" with "com.trevix.it.segnapunto.002rf"
10- on the non-consumable button I replaced the string "com.runrev.sampleapp.consumable" with "com.trevix.it.segnapunto.001rf"
11- I did the same for all recurrences of the strings all over the stack
12- I created an APK
13- I connected the phone with USB and uploaded the apk using ADB (actually I am using a small app called "VRsideloader". Very convenient)
14- I launch the app, press non-consumable purchase
15- nothing happens. Same for consumable. Did not test the sub.

Can you please give me some suggestions on what could be wrong?

One more thing: I have a pro-pack on LC9.6.6rc1. Can I enable the remote debugger extension in order to check the IAP?

Thanks so much

Trevix

Correction on point 7:
I set the version number to the same version number of the app available in internal testing (which is "Available to internal testers")

Panos Merakos

Hello Trevix,

The IAP product ID in the LC script must match the ID of the IAP as it appears in the Google Console listing, so in the sample stack code:

- on the consumable button, and everywhere else it is mentioned, you have to replace the string "com.runrev.sampleapp.consumable" with JUST "002rf"

Similarly for the other produce IDs.
Hope this helps.

Cheers,
Panos

Trevix

It works.
Questions:
- The payload (in your stack "This belong to me"): it is not clear its purpose or where it can be found during or after purchase
- in order to test, how can I reset Non consumable purchase (so that is seems that I never bought it)?
- mobileStoreMakePurchase "The quantity should always be 1 for non iOS apps". Ok but in the following google window I am able to buy more then 1 and if I chose 2 or more I would expect the quantity be written to the field "consumablescount" (but it is alway 1). This is something i don't understand
Thanks

Panos Merakos

- If I remember correctly, the "payload" param was used with the old version of the Google Play IAP library. It does nothing in the current one, but we have left it for backwards compatibility.
- For testing purposes, you can still call "mobileStoreConsumePurchase" on the non-consumable item. This will allow you to buy it again.
- Hmm this is a good question. Being able to buy more that one items in one go was not supported in the past, so I believe we have to add functionality for being able to know the quantity the user had chosen in the google purchase window.

Hope this helps
-

Trevix

You wrote:
"For testing purposes, you can still call "mobileStoreConsumePurchase" on the non-consumable item. This will allow you to buy it again. "
After running this command with the correct non-consumable ID, I get a OS dialog (Error: you already own this article) and, following, the answer dialog from the purchaseStateUpdate script.
Is there no way to cancel, for testing purpose, the non-consumable purchase?

Trevix

Reading here, it seems that on Android the only way is doing a refund on google console.
https://stackoverflow.com/questions/38835452/how-to-cancel-an-in-app-test-purchase-on-android

Trevix

After a few test buy on the app and refund on google console I got the following email:
We have blocked requests for canceling subscriptions and issuing refunds for Google Play Console account 'TerzaBase SRL' due to a sudden rise in volume of both these requests.
So, the refund way is not really useful

Panos Merakos

Hello Trevix,

Which google account are you using for testing in-app purchases? Is this gmail account included in the "license testers"? https://developer.android.com/google/play/billing/test

Using a "license tester" account will show a test credit card when making a purchase, so there won't be an actual charge in your own credit card.

Now, RE testing non-consumable purchases, I have just tested and I confirm that ONLY for testing purposes you can use "mobileStoreConsumePurchase" on the non-consumable item. If you still get the "you already own this item" error message, try to restore purchases first, i.e. add a new button and do:

on mouseUp
mobileStoreEnablePurchaseUpdates
mobileStoreRestorePurchases
end mouseUp

and then try to call "mobileStoreConsumePurchase" on the non-consumable item. Hopefully, this time your existing non-consumable purchase will be reset, and you will be able to buy it again, for testing purposes only.

Hope this helps.

Cheers,
Panos

Trevix

Very funny.
I tested subscription on Android phone and in the subscription window it asked me to accept (liberally translated):
Starting from today 1,19 €/5 min
...
...
You will be charged automatically the amount of 1,18 € every 5 minutes until you will cancel the subscription
...
Jeeee! Every 5 minutes!!! I don't even know that this was possible.
The setup for sub on Google console is 1€ weekly, Free trial enabled (5 days).
What could be wrong?

Panos Merakos

Hello Trevix,

I guess the gmail account you're using for testing is included in the "license testers".
https://developer.android.com/google/play/billing/test

Using a "license tester" account will show a test credit card when making a purchase, so there won't be an actual charge in your own credit card.

License testers can rapidly test subscription features, meaning that the renewal times will be much shorter, to test the functionality quickly:

Production subscription period Test subscription renewal
1 week --> 5 minutes
1 month --> 5 minutes
3 months --> 10 minutes
6 months --> 15 minutes
1 year --> 30 minutes

Hope this helps.

Cheers,
Panos

Samuele

Hi, is there a way to do that for android but without google play store, only for personal use, for example a donations app. thanks!

Panos Merakos

Hello Samuele,

Unfortunately no, there is no support to get payments outside the Google Play Store from a LC Android app.

Kind regards,
Panos
--

Trevix

I want to offer two subscriptions; monthly and yearly. They unlock all the items in the app but yearly offers a discounted price. The thing is when I want to upgrade from monthly (or yearly) or downgrade, I see that I have two subscriptions and the old one won't be canceled.

On Apple they have subscription groups which seems to work fine They handle the upgrade/downgrade between subscriptions, even handle the prorating of costs if you change mid-subscription period which is awesome.

What could I do with Android?
Trevix

Panos Merakos

Hello Trevix,

Unfortunately Google Play Console does not offer this functionality. It is possible to handle upgrades/downgrades between subscriptions, but this can only be done programmatically, and currently LC does not support this. So, in this case, I think the best you could do is to not allow upgrades/downgrades while an existing subscription is still active. Keep in mind that when a user cancels their subscription, they should be entitled to access the subscription content until the end of the current subscription period. This means that "mobileRestorePurchases" will still return the "cancelled" subscription until the current subscription period is over.

Kind regards,
Panos

trevix

One of the stronger plus of LiveCode is the fact of being multi platform. You build a standalone that will run on different OS.
The slight differences between doing subscriptions with apple and google, are driving me crazy.
I think that the two lessons should be incorporated in one only example that will work for both platforms.
I this is not possible, since the various purchase commands (like "mobileStoreRestorePurchases") are called on both platform, I think I need to duplicate the scripts on two different substacks, adjusting the small differences for each platform.
What would be the best method to do this? Loading two libraryies at launch?

Panos Merakos

Hello Trevix,

No need to have 2 separate libraries. I would suggest to avoid code duplication if possible.
I think the best approach is to have just one library, and use high level commands, that will be the same for both platforms.
And you can differentiate per platform only when it is necessary, e.g.

on mouseUp
makePurchase
end mouseUp

on makePurchase
if the platform is "android" then
makePurchaseAndroid
else if the platform is "iphone" then
makePurchaseiOS
end if
and makePurchase

You get the idea. Hope this helps.

Kind regards,
Panos
--

Add your comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.