2015年6月11日 星期四

[Corona SDK] 如何加入程式內購In-App Purchases (IAP)?

現在好像越來越流行In-App Purchases的功能,
也就是程式是免費的,
但是如果你想要無廣告,取得所有關卡,增加某些功能,或者購買某些武器等等,
此時你就需要花錢購買,這就是所謂的In-App Purchases (IAP).

底下會同時考慮AndroidiOS系統,
我們先將需要的共同程式部份貼出,然後再分別討論:
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
首先你必須到Play Store Publish內新增你的APP,
成功後,再進入到那個APP項目內新增應用程式內商品,
IAP種類可以分為管理的(managed)未管理的(unmanaged)定閱的(subscription)
管理的,是指一次的,例如從試用版變成完成版之類的,
如果你的IAP是這種種類的,使用者購買後,Google會將購買紀錄存下來,如果使用者換機或者移除程式再重新安裝程式,可以將已購買部份回復。
未管理的,通常是指可以重複購買的,例如武器或主角的血值等等,
Google不會將購買紀錄存下來,
也就是說,如果使用者換了手機或者移除程式再重新安裝程式,
這部份的相關紀錄,需要開發者另外有方法將其紀錄下來,
定閱的,這部份就先不討論。

完成後,把那個項目id填入程式裡的googleProductList內,

Play Store Publish內同一個APP裡,有一個服務和API項目,
點進去,裡面有一個授權金鑰,
它會是一段字串的格式,將它貼到config.lua內的key欄位:
application = 
{
 content = 
 { 
  width = 640,
  height = 960, 
  scale = "zoomStretch",
 },
 license =
    {
        google =
        {
            key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        },
    },
}
上面的過程,只要你可以申請成功即可,不用經過審查,<br />

再來,我們還需要在build.settings裡新增plugins和權限:
settings =
{
 plugins =
    {
        ["plugin.google.iap.v3"] =
        {
            publisherId = "com.coronalabs",
            supportedPlatforms = { android=true }
        },        
    },
 android =
    {
        usesPermissions =
        {
            "com.android.vending.BILLING",
        },
    },
}

購買Purchase
要購買時,我們只要呼叫store.purchase( currentProductList[1] )即可,
不過,在實測前,我們需要將build好的apk檔上傳,
因為我們還沒測試好,所以上傳到ALPHA或BETA階段都可以,
重點就是要先上傳,不然實測時會得到"這一版的應用程式未提供Google Play結帳功能"之類的錯誤訊息,
之前是不用先上傳apk就可以測試,但現在規則改了,
上面的範例會先判斷store.canMakePurchasesiOS才有作用,
Android平台的話都會是true,
購買成功之後,在callback function storeTransaction裡會進入transaction.state == "purchased"
我們在裡面再將相關的權限打開即可,

測試Test
因為Google規定不能用自己的帳號來測試,所以你必須再弄個新帳號,
如果你的手機和你開發用的帳號是一樣的,
那就必須回到出廠設定,然後再設成新帳號,

我們可以到Play Store Publish 的設定畫面裡新增測試用Gmail帳戶,
也就是說,用這些帳戶購買你的IAP商品時,都不會被扣錢,
如果你沒有先去設定測試用帳戶,一樣可執行測試,
只是要真的花錢就是了,
當然,花了錢還是可以透過下面會提到的退貨機制來退錢,

另外,一開始測試有問題是,我們會不確定是因為程式有問題,
還是因為我們在Play Store Publish上面的設定有問題,
所以Google建議我們在測試IAP時,最好是先做所謂的"Static Responses"測試,
也就是將上面的googleProductList內項目換成
"android.test.purchased"
"android.test.canceled"
"android.test.refunded"
"android.test.item_unavailable"
等等,
這些項目google內定的,
你不用到Play Store Publish內先設定好,
也不用將apk先上傳,
它們可以測試程式內有關於IAP的處理流程,
程式加入IAP功能時,建議最好先做這樣的測試。

退貨Refund
Google允許使用者可以退貨,使用者可能會在電子錢包執行退貨動作,
原則上,我們只要在callback function storeTransaction裡的transaction.state == "refunded"做一些權限的改變即可

清除購買項目Comsuming items
這是Google特有的,
如果你的項目是管理的(managed),你可能測試之後發現有問題,
如果你修改程式之後想要再測一次,該怎麼處理呢?
因為你已經購買過了,我們可以用comsuming的方式將購買紀錄消除掉,
然後再進行購買試試
方法如下:
store.consumePurchase( currentProductList[1], storeTransaction )
執行上面的動作後,callback function storeTransaction會進入transaction.state == "consumed"表示紀錄已消除,然後你可以再執行store.purchase( currentProductList[1] )
如果你沒有執行清除就直接再購買一次,那會出現錯誤"already owned",
callback function storeTransaction裡會進入transaction.state == "failed"

回復
如果使用者曾經買過管理的(managed)的商品,
使用者可能移除程式再安裝,或者同樣的帳號安裝在不同的機器呢?
我們必須將相關的權限回復,
程式可呼叫:
store.restore( )
呼叫之後,會跑到callback function storeTransactiontransaction.state == "purchased"
Google沒有規定何時該執行回復,
你可以程式執行時自動回復,或者弄個按鈕讓使用者啓動回復,
如果使用者沒有買過,那執行回復不會有任何反應,
如上一段所說,已經買過了,
使用者再買一次時會得到錯誤訊息,而不是直接變成回復,
所以程式裡一定要有執行回復的動作


iOS
你必須先到iTunes Connect內新增你的APP,
成功後,再進入到那個APP項目內新增App內購買項目,
Apple提供5種購買項目種類,
一般我們最常用的大概就是消耗性項目(consumable products),和非消耗性項目(non-consumable products)
消耗性項目,和Google的未管理項目一樣,
非消耗性項目,和Google的管理項目一樣,
請參考前面Android部份的說明,

接著,我們把那個項目id填入程式裡的的appleProductList內,
注意, appleProductList內要用table格式,和Android用的格式不同,
上面的過程,只要你可以申請成功即可,不用經過審查,

購買Purchase
要購買時,我們只要呼叫store.purchase( currentProductList[1] )即可,
上面的範例會先判斷store.canMakePurchases是因為在iOS裝置可以將IAP功能鎖住,
主要是為了怕小孩誤按之類的,
購買成功之後,在callback function storeTransaction裡會進入transaction.state == "purchased"
我們在裡面再將相關的權限打開即可,

測試Test
因為App還未上架,在實機上測試IAP功能時,
我們會得到無法連接iTunes Store的錯誤訊息,
要測試,我們必須先iTunes Connect的"使用者和職能"裡面新增沙箱技術測試人員,
新增的帳號不能是現在已經存在的,什麼意思呢?
也就是說,你不能把你手機或平板的帳號設成測試人員帳號,
我們必須新增一個Apple帳號,這點和前面Android的設定測試人員不同,
因為這個新增帳號我們只拿來測試,所以資料可以亂寫,
一個簡單方法就是用你既有的帳號加上test1,test2之類的,
例如,你手機的帳號是"ABC",那就新增帳號"ABC+test1", "ABC+test2"...

你會需要新增很多測試帳號,
因為如果你的IAP商品是非消耗性項目(non-consumable products)
只要買過一次,系統就會紀錄,
Apple沒有像Google有消除購買項目的功能,
當然你再執行購買時,它會告訴你已經買過,可以再免費取得一次,
成功後,在callback function storeTransaction裡也是會進入transaction.state == "purchased"
它並不會像Android平台會進入transaction.state == "failed"
不過,如果你要模擬新使用者的狀態,那還是再新增一個測試帳

回復
如果使用者曾經買過非消耗性項目(non-consumable products)的商品,
使用者可能移除程式再安裝,或者同樣的帳號安裝在不同的機器呢?
我們必須將相關的權限回復,
程式可呼叫:
store.restore( )
呼叫之後,會跑到callback function storeTransactiontransaction.state == "restored"
Apple規定如果你的App有非消耗性項目(non-consumable products)的IAP功能,
那就必須提供回復的按鈕,
等使用者按了之後,再執行回復的動作,
為什麼程式不能自動幫它回復呢?
因為你程式執行回復的動作,會跳出要使用者輸入帳號密碼的畫面,
如果你的程式是自動回復,突然跳出輸入密碼的畫面會讓使用者會覺得莫名其妙,
前面提過,已經購買過了,再購買也不用花錢,也不會像Google會得到錯誤訊息,
那就請使用者再按一次購買就好了啊,為什麼要再弄個回復的按鈕,
因為使用者已經購買過了,按購買的按鈕會讓使用者覺得需要再花一次錢,
不用錢的提示訊息是程式真的執行購買動作後才會跳出,



不管Android的Play Store Publish還是iOS的iTunes Connect
設定新的IAP商品後,都需要一段時間才會生效,
有時可能要等幾個小時,
沒有任何地方告訴你已經可以開始測試,
如果你一直測試有問題,又找不出原因,那就隔天再試,
如果是Android,如前面提的,你可以先做"Static Responses"看看





沒有留言:

張貼留言