2015年6月16日 星期二

[Corona SDK] How to implement In-App Purchases (IAP)?

It seems that In-App Purchases (IAP) feature is more popular nowadays.
That is, the App itself is free. 
However, if you want to get no-ads version, get all rounds, add some features or retrieve some weapons and so on, you will need to pay then.
This is what called In-App Purchases (IAP).

Below, we will consider about Android and iOS both.
Let's check the common codes shown below first, and discuss them later:
local store
local currentProductList = nil

local appleProductList = {
    {"com.gmail.myTest.DemoApp.GetFullVersion"}
}

local googleProductList = {
    "com.gmail.mytest.demo_app.getfullversion",
}
local function storeTransaction( event )
    local transaction = event.transaction

    if ( transaction.state == "purchased" ) then
     print("transaction.state == purchased")
        if(fullVersion == false) then
         fullVersion = true
     end
    elseif ( transaction.state == "restored" ) then
     --iOS only
     print("transaction.state == restored")
        print( "productIdentifier:", transaction.productIdentifier )
        print( "originalReceipt:", transaction.originalReceipt )
        print( "originalTransactionIdentifier:", transaction.originalIdentifier )
        print( "originalDate:", transaction.originalDate )
       if(fullVersion == false) then
         fullVersion = true
     end
    elseif ( transaction.state == "refunded" ) then
     --refunds are only supported by the Android
     print("transaction.state == refunded")        
        print( "productIdentifier:", transaction.productIdentifier )
        if(fullVersion == true) then
         fullVersion = false
     end
    elseif ( transaction.state == "consumed" ) then
     --consumed are only supported by the Android
     print("transaction.state == consumed")        
        print( "productIdentifier:", transaction.productIdentifier )
  if(fullVersion == true) then
   fullVersion = false
  end
  elseif event.transaction.state == "cancelled" then
   --For iOS, if user cancels the IAP action, it will come to here 
   print("transaction.state == cancelled")
  elseif event.transaction.state == "failed" then 
   --[[
   -1003: unknown
   -1005: user canclled,For Android, if user cancels the IAP action, it will come to here
  -7: already owned
  8: item not owned
   ]]
   print("transaction.state == failed")
   print("Transaction failed, type:"..tostring(event.transaction.errorType)..",-1005")
   print("Transaction failed, error:"..tostring(event.transaction.errorString)) 
    end    

    store.finishTransaction( event.transaction )
end


if ( isAndroid ) then
    store = require( "plugin.google.iap.v3" )
    currentProductList = googleProductList
    store.init( "google", storeTransaction )     
else
    store = require( "store" )
    currentProductList = appleProductList
    store.init( "apple", storeTransaction )
end 

--put the following codes somewhere, such as when user press some button 
if(store.canMakePurchases) then
 store.purchase( currentProductList[1] )
end

Android
First, you need to add your App in Play Store Publish.
After that, enter the need added App and then add the In-App products.
There are three types of IAP products for google IAP: managed, unmanaged and subscription.
Managed: It is for one-time shopping, e.g. changed from lite version to full version.
For this kind of products, Google will store the purchase information.
If user change phones or re-install the App, they can restore those managed products from Google server if they are using the same accounts.
Unmanaged: It is for those products which can be purchased again and again, e.g. weapons or blood
values of roles.
The Google server will not store the purchases records.
That is, the developers need to have some ways to store such information themselves.
Subscription: Let's not discuss it currently.

After that, we need to fill the IAP item id in googleProductList, as shown in above codes.

In Play Store Publish, go to the "Services & APIs" page and get the license key for this application.
It is a very long string. Just copy it and paste to the key field in config.lua:
application = 
{
 content = 
 { 
  width = 640,
  height = 960, 
  scale = "zoomStretch",
 },
 license =
    {
        google =
        {
            key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        },
    },
}


Next, we need to add the plugins and permissions in build.settings:
settings =
{
 plugins =
    {
        ["plugin.google.iap.v3"] =
        {
            publisherId = "com.coronalabs",
            supportedPlatforms = { android=true }
        },        
    },
 android =
    {
        usesPermissions =
        {
            "com.android.vending.BILLING",
        },
    },
}

Purchase
To purchase, we only need to call store.purchase( currentProductList[1] ).
However, we need to upload the built apk file to Play Store Publish first.
The version code of  uploaded apk file should be the same as what apk you want to test.
You can modify your codes and build many times, just remember to use the same version code.
We can upload to ALPHA testing or BETA testing.
If there is no apk file with same version code in Play Store Publish, you will get error message telling that the IAP function is not provided in this version.
In above codes, we check store.canMakePurchases before calling store.purchase( currentProductList[1] ). 
store.canMakePurchases is for iOS only. It will be true always for Android platform.

After purchasing successfully, the callback function storeTransaction will enter transaction.state == "purchased".
We can modify the related authorization there.

Test
We are not allow to test IAP function with the same account.
We need to get another account for testing.
If the account in your phone is the same as your developer account, you need to back to factory default and set new account.

We can go to Play Store Publish, find the setting page and then add the test account.
When you purchase your IAP products with this testing account, it will not spend you any money.

During our test, we are not sure if the problems are caused by wrong coding, or by wrong setting in Play Store Publish.
Google suggest us that we should do what called "Static Responses" test first.
That is, we can replace the items in googleProductList to be following items:
"android.test.purchased"
"android.test.canceled"
"android.test.refunded"
"android.test.item_unavailable"
These items are defined by Google.
We don't need to set anything IAP items in Play Store Publish in advance.
We don't need to upload our apk file either.
We'd better use them to test our IAP related codes first.

Refund
Google allow user to perform refund after purchasing.
Basically, we just need change the related authorization in  transaction.state == "refunded" of callback function storeTransaction.

Comsuming items
This is Google specific feature.
If your item is managed type, you may need to re-purchase after you modify some error codes.
We can consume the purchased record in Google server:
store.consumePurchase( currentProductList[1], storeTransaction )
The callback function storeTransaction should enter transaction.state == "consumed" then.
After that, we can perform store.purchase( currentProductList[1] ) again.
If you don't do store.consumePurchase(), you will get the error message in transaction.state == "failed", which tells you that the item is already owned.

Restore
If users have ever bought the managed products, they may install your App in different devices or re-install your App with the same account.
We need to restore the related authorization by following method:
store.restore( )
After calling it, the callback function storeTransaction will enter transaction.state == "purchased".
Google does not ask when or how we should do restore.
We can perform restore automatically or add a restore button.
If user did not buy it before, nothing will happen after calling store.restore( ) .
As mentioned above, doing purchase again will get error message "already owned", instead of entering transaction.state == "purchased" or transaction.state == "restored" 
We have to call store.restore( ) somewhere.

iOS
You have to visit iTunes Connect to add your new App.
We can add IAP products after adding new App successfully.
There are five types of IAP products provide by Apple.
Consumable products are similar with unmanaged products mentioned above.
Non-Consumable products are similar with managed products mentioned above.

Similarly, we need to fill the IAP item id in appleProductList of our codes.
Note that appleProductList is in table format, which is different from googleProductList.

Purchase
To purchase, we need call store.purchase( currentProductList[1] ).
We have to check store.canMakePurchases because iOS devices have a setting that disables purchasing.
After purchasing successfully, the callback function storeTransaction will enter transaction.state == "purchased".
We can modify the related authorization there.

Test
Our App has not been published to Store.
When we do IAP test, we will get the error message telling that the App cannot connect with iTunes Store.
To test it, we need to setup the sandbox environment.
Visit iTunes Connect, go to the "Manage Users" page and add the test counts.
The added accounts cannot be any accounts which have already existed.
That is, you can use those accounts in your phones or tablets.
We have to create a new Apple id for testing.
A suggested way is to combine your original account with test1, test2, and so on.
For example, if your phone Apple id is "ABC", then you can add test account "ABC+test1", "ABC+test2"....
Apple does not offer the function for eliminating purchased record, like store.consumePurchase() for Android we mentioned before.
If your products are non-consumable products, you will need to create many test accounts.
For Apple, if we do store.purchase() for the item which has been purchased, it will tell you that this item has been purchased and you can get it free for one more time.
It will not return error message as Google platform.
After purchasing, the callback function storeTransaction will enter transaction.state == "purchased" as well.
Anyway, we'd better test with new account which has never bought any items before.

Restore
If users have ever bought the managed products, they may install your App in different devices or re-install your App with the same account.
We need to restore the related authorization by following method:
store.restore( )
After calling it, the callback function storeTransaction will enter transaction.state == "restored".
Apple ask that if your App has non-consumable products inside, then you need to provide the restore button.
That is, we should perform restore after user triggering it.
We cannot restore it automatically.
Why?
Because if we perform restore automatically, it will pop up the window for asking password.
Users may feel strange for showing up such menu automatically.
As we mentioned in last paragraph, if we perform purchase for the purchased products, we can restore the purchased items too. It is free.
Why do we need the restore button? 
Can we just ask user to press the purchase button again?
We do need the restore button.
Users may feel that they need to pay again for pressing purchase button.
The message about getting the products again for free is shown up after you press the purchase button.


For Android Play Store Publish or iOS iTunes Connect, you need to wait for a while to take effect after you set up a new IAP product.
It may take few hours sometimes.






沒有留言:

張貼留言