How do I implement in-app purchases in LiveCode - Amazon Appstore?
You can download the sample stack from this url: https://tinyurl.com/ydbn9qzj
Note: You need LiveCode 6.7+ to follow this lesson.
1. Creating an in-app purchase
In order to use the in-app purchase with LiveCode, we must first create it in the Amazon Appstore Developer Portal. From the front page of Amazon Appstore Developer Portal, go to Dashboard, select the app for which you wish to create an in-app purchase, click "In-App Items”, and click “Add a consumable”/ "Add an Entitlement"/ "Add a Subscription", depending on the type of the product you want to add. 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 (or an entitlement, according to Amazon's terminology\P \P \ and a subscription purchase.
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. Then add code that allows us to interface with the Amazon Appstore 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.month", "subs" mobileStoreMakePurchase "com.runrev.sampleapp.sub.month", "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 & mobilePurchaseError(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 else if pProductID is "com.runrev.sampleapp.sub.month" then set the cSubPurchased 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 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 the Amazon Appstore Developer Portal 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 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 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 offerPurchasedProducts 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 Amazon that payment has been received, finalizing the in-app purchase process on the Amazon Appstore’s side.
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 Amazon Appstore Developer Portal.
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 Amazon 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.
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. The mobileStoreConsumePurchase command is called with the consumable product identifier. This command removes the consumable item from the user's inventory, so that it will not be contained in the list returned by mobileStorePurchasedProducts function.
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 for non-consumable and subscription products. The app is no longer responsible to retaining what the user has bought. All the purchased items of this type (non-consumables and subscriptions\Pcan 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. One simple way of doing this is simply to add some code to our setProps which writes 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 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")&"/consumablescount.txt") end saveConsumablesCount
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 local tPath put (specialFolderPath("documents")& "/consumablescount.txt")into tPath if there is a file tPath then set the cConsumablesCount of this stack to url("file:" & tPath) end if end loadConsumablesCount
on preOpenStack loadConsumablesCount end preOpenStack
For restoring non-consumable or subscription items, we could use a restore button. It 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 Amazon 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 Amazon Appstore for the non-consumable item.
3. The Amazon Appstore responds and asks for the user’s password. The user types their password.
4. The Amazon Appstore responds that the purchase is successful.
5. purchaseStateUpdate message is called with parameters (“1”, ”nonconsumable_product_name”, “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 itemagain, they get an “Already Owned” message. The purchaseStateUpdate message is called with parameters (“1”, ”nonconsumable_product_name”, “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.
Note : When you set up your subscription products in the Developer Portal, you maybe noticed that you were asked twice to specify the product identifier (or the SKU, according to Amazon's terminology). The first SKU is called parent SKU, and is the same for all different (if any\Psubscription periods. This could be something of the form com.yourcompany.yourapp.sub. The second time that you are asked to specify the SKU, you have to use a different one, which is tied to the particular duration of the subscription you just added. This could be something of the form com.yourcompany.yourapp.sub.month. This SKU is called child SKU. You can add more child SKUs (i.e com.yourcompany.yourapp.sub.4months\Pfor the same subscription item, that have a different subscription period. In the FAQ of Amazon developers website, we can see :
Q: Why do I get a different SKU back when I initiate a purchase for my subscription SKU?
A: A subscription is comprised of a non-buyable parent SKU and one or more child term SKUs. This setup prevents users from purchasing multiple subscriptions of the same product. The non-buyable parent represents the product being purchased and is the SKU returned in the purchase response. The child SKUs represent the different subscription terms and are used to initiate the purchase. Subscription terms and charges are handled by Amazon and your app only needs to check whether a subscription is valid.
This means that in the purchaseStateUpdate message we have to handle both SKUs (parent and child SKU\P: The child SKU is returned with purchaseStateUpdate message when making a subscription purchase for the very first time, and the parent SKU is returned when we restore previous purchases.
5. Testing purchases
For Amazon API, you need two things to test your in-app purchases:
1. The Amazon SDK Tester. This is a developer tool that allows users of the Amazon Mobile App SDK (ie the Amazon API that is used for in-app purchases\P to test their implementation in a production-like environment before submitting it to Amazon for publication. This will allow developers to construct test cases that cover all responses generated by the Amazon Mobile App SDK APIs, and give them confidence their apps will be published and run correctly. You must install it in the Android device that will be used for testing.
2. A .json text file that will contain the IAP item information used to respond to IAP API calls. This file should be written to the "mnt/sdcard" directory on the device that will be hosting the SDK Tester.
You can find very detailed information on how to use the Amazon SDK Tester in the official Amazon website for developers :