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.
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.
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.
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.
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.
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:
You can find more detailed information on how to test in-app purchases on the official webpage for testing Google in-app purchasing API:
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.