Thursday, April 12, 2012

串接 Google Play In-app-billing 易犯的錯誤

撰寫時間︰2012/04/13 11:41
更新時間︰2012/11/05 11:04
文章更新次數︰11

一、前言
之前提到,
臺灣目前雖然無法購買付費型APP,
但卻可以使用In-app-billing機制來獲利。

Google Play的In-app-billing機制很完善,
因此在機制底下的規矩也很多。
新手在串接時,
可能因此發生了一堆奇奇怪怪的錯誤。
這邊我把遇到的問題跟大家分享,
這些都是我很寶貴的出錯經驗。

二、文章開始
我在串接Google Play in-app-billing時發生過的問題及錯誤如下︰

1.關於應用程式產品內ID值的問題

(1)應用程式產品內ID沒有照規則走
雖然Google Play In-app-billing Document已經很明確的教導我們ID值的命名規則是︰
產品IDs是以唯一性的方式跨越應用程式命名空間。產品ID必須啟始字元必須為小寫或數字,而且組成的字串也都只能有小寫(a-z)、數字(0-9)、下 底線(_)和小數點(.)。以"android.test"開頭的產品ID命名被保留,任何以android.test開頭的命名方式皆不可用。附帶一 提,當您在建立產品ID後,是無法修改的,而且您也無法重覆使用同一組產品ID。

有時候為了搶時間,這個ID值沒有照規範走就定義在程式碼中並上傳成草稿APK至Google Play Publisher。
直到要在Publisher後臺添加 應用程式產品內ID值 時,才發現自己沒有遵照規範。
因此程式碼內的 應用程式產品內ID值 又要再改一次,再重新上傳一個新的草稿APK。
更糟的是︰
如果你的APP有做package name控管,那麼你的package name一定會超出控管。
因為同樣的package name的APP,即使你從後臺刪除又重新上傳,Google Play都會把它們當成是同一隻APP。這使得你package name需要重新命名才能完成上傳,因而package name已經不是你原本希望的名稱了。

(2)設定Publisher後臺 應用程式產品內ID值 的[受管理]與[不受管理]分類時請小心!

由於 應用程式產品內ID值 分成受管理和不受管理類,
這個值如果沒有設定好就儲存或發佈,
後來發現設定錯了,
即使刪除,
都不能再在同一個APP內設定同一個產品ID了。
這很麻煩,
因為程式還要為了這個不小心的錯誤,
重新改程式碼中對應的ID值、重新上傳草稿APK、重新測試…

2.點擊購買流程,iap視窗彈出「這個版本的應用程式還無法用付款功能。」
這算是一個新手錯誤。

如果查看一下LOG,收到的LOG應該是RESULT_DEVELOPER_ERROR
官方文件對這個LOG的定義如下︰
此回應指出您的APP試圖發送iap請求,但是APP的AnddroidManifest.xml裡卻沒有宣告 com.android.vending.BILLING權限。也可能是因為應用程式沒有正確的被簽署,或者您發送了一個非正確格式的請求,像是忘了傳 Bundle的key值或者是使用了一個無法被識別的請求類型。

官方文件曾經說,
要測試iap內容,
有幾個條件需要實作︰
(1)手機的primary account要設成test account
如果不是,請重設為原廠設定,這是官方建議我們的。
(2)test account要設定在Google Play Publisher的編輯個人資料的測試帳戶底下。
因為是測試iap,因此我們上傳的草稿apk不用被發佈,只要儲存即可。
但是 應用程式內產品ID 一定要被發佈。
但是這樣要怎麼找的到這些沒有被發佈的應用程式內ID呢?
答案就是要將手機的account設到這邊當成test account。
(3)androidManifest.xml裡要宣告
<uses-permission android:name="com.android.vending.BILLING" />
而且重點是,不僅要宣告這行,還要把這行放在<manifest>和<application>中間
(4)請確認裝置上的版編、版號、keystore與上傳的草稿APK的版編、版號、keystore一致
因為我們不能用debug.keystore上傳app,
因此,在做內部測試時,
請確認線上的草稿app的keystore是和裝置上要測試的版本的keystore是一致的。

官方文件Testing In-app Billing單元有提到︰
上傳您的草稿APP至發佈網站。您無需發佈您的APP才能執行端點測試和真實產品ID的消費。您僅需以草稿的方式將您的APP上傳。然而, 您必須將您的APP簽署上那把你平時釋出APP時,專用的金鑰。而且,您上傳的APP版號必須和你裝置上要執行測試的APP的版號一致。如果想知道關於如 何上傳APP至Android市集,請見 Uploading applications

我曾經將草稿APK上傳了,
但是就是一直回應我這個版本的應用程式還無法用付款功能
我將這行AndroidManifest.xml裡的宣告改變位置,
然後將package name和 應用程式內產品ID 全部重新定義過,
也都無法解決,
最後才發現我裝置上的APK的版編版號沒有和線上的『已發佈』APK一致
這些細節如果沒仔細去看,
真的會很折磨人。

註︰
2012/11/05
有時候為了不要讓服務直接上線(還在內測階段),會上一隻假的APK檔上架Google Play來測試裝置上DEBUG模式的真實APK。如果上傳的假APK裡Android Manifest屬性和手上實測的DEBUG模式真實APK裡的Android Manifest屬性差太多,可能也會造成即使線上和手上裝置版本號一致,但仍show出此版本無法購買的問題。此時建議直接再上傳一個新版號做測試即可。

3.payload很好用,但是使用它是有條件的。
我們知道在request Purchase時,
可以附一個payload給Google Play,
屆時如果交易成功,
傳回來的Json裡面會傳回這個payload。
因此這個payload變得很好用,
因為我們可以拿這個值來做虛擬幣加值的依據之類的。
但,
這個payload只會在正式金流交易下回傳過來,
如果你請求購買的 應用程式內產品IDandroid.test.purchased之類的,
很抱歉,
傳回來的Json是不會附帶這個payload的。

4.不要將你的購買成功後的程式動作放在Response_OK後面。
    @Override
        public void onRequestPurchaseResponse(RequestPurchase request,
                ResponseCode responseCode) {
            if (Consts.DEBUG) {
                Log.d(TAG, "331 "+request.mProductId + ": " + responseCode);
            }
            //付費成功不是在這裡處理。這裡只是一般購買請求Google Play是否答應的接收處
            if (responseCode == ResponseCode.RESULT_OK) {
                if (Consts.DEBUG) {
                    Log.d(TAG, "335 purchase was successfully sent to server");
                }

//             MainActivity.addMoney();//不是在這裡加值

//              logProductActivity(request.mProductId, "sending purchase request");
            } else if (responseCode == ResponseCode.RESULT_USER_CANCELED) {
                if (Consts.DEBUG) {
                    Log.d(TAG, "340 user canceled purchase");
                  
                }
//                logProductActivity(request.mProductId, "dismissed purchase dialog");
            } else {
                if (Consts.DEBUG) {
                    Log.d(TAG, "346 purchase failed");
                }
//                logProductActivity(request.mProductId, "request purchase returned " + responseCode);
            }
        }
 
在官方的iap教學文檔中,
我們實作了PurchaseObserver。
這個PurchaseObserver被呼叫的時間點,
是在Google Play對iap購買交易有完成的成功回應時,
在ResponseHandler.java呼叫purchaseResponse(), 找我們實作的PurchaseObserver底下去執行程式相關的動作。
但是,
ResponseCode.RESULT_OK只是代表我們可以執行iap購買,
不代表交易成功了。
還記得in-app-billing交易流程圖嗎?
ResponseCode.RESULT_OK只是這個交易flow的第2條而已。
真正表示你交易成功會發生在第7點︰PURCHASE_STATE_CHANGED。

因此交易成功要在onPurchaseStateChange()函式裡實作,
程式如下︰
 @Override
        public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
                int quantity, long purchaseTime, String developerPayload) {
            if (Consts.DEBUG) {
                Log.d(TAG, "286 onPurchaseStateChange() itemId: " + itemId + " " + purchaseState);
            }

//            if (developerPayload == null) {
//                logProductActivity(itemId, purchaseState.toString());
//            } else {
//                logProductActivity(itemId, purchaseState + "\n\t" + developerPayload);
//            }
            
            //購買完成會呼叫這裡
            if (purchaseState == PurchaseState.PURCHASED) {
             Log.w(TAG, "297 onPurchaseStateChange: PURCHASED");
//             Toast.makeText(IapPage.this, R.string.purchased_success, Toast.LENGTH_SHORT).show();       

             MainActivity.addMoney(purchaseTime, developerPayload);      
//              mOwnedItems.add(itemId);
            }
 

5.老王不能自己買瓜
剛開始串接IAP的新手有時候會遇到「找不到項目」的錯誤,
看LOG回覆的錯誤訊息,並且參照Reference通常都能找出錯誤原因,
我曾遇過的錯誤是因為RESULT_ERROR,查了一下In-app Billing Reference後,發現問題是因為我自己是販售者,我仍然又用販售者的身份去購買in-app Billing的商品,自己跟自己買東西,這件事在Google Wallet是不被允許的。 
因此,如果你現在用Test Account購買商品,記得手機登入的Primary Account不能跟販售者的account相同。 


6.別急
許多人在設定和實作的過程中,都急著想看到可以購買的結果。
但是,如果你是第1次為這隻APP發佈in-app-billing商品時,
Google是需要花時間去處理的(我的實測是2個小時候才找的到該商品,因為中午吃了一個便當,今天吃7-11的義大利麵)。
所以,你發佈出去不代表馬上就能找到應用程式內商品,
等一下他們吧!

註︰最近Google好像不提供不發佈APK、僅發佈iab商品測試消費了,
一定要將apk發佈,才能找的到iab商品(見下圖)。2012/08/14



三、結論
就是這些了。
這些經驗花了我半年 + 卡住數次得來。
它們都是我在串接Google Play iap時常遇到的錯誤。

如果之後還有機會遇到任何的錯誤,
我會把經驗分享上來。
(當然會希望不要再有了!!)

只希望之後大家在串接時,
不要再犯我犯過的錯了。

四、附註
1.官方的Sample Code主程式中unregister observer時的時間點錯了
(範例文件將unregister放在onStop()去執行),
因為我們不知道系統什麼時候會讓Activity進入onStop()狀態,
造成原本應該監聽iap後續動作的observer被系統終止了。
這會造成交易完成後,我們實作的observer有時候會無法順利被呼叫。
最好是移到onDestroy()再去執行ResponseHandler.unregister(myPurchaseObserver);

19 comments:

samlu said...

不同應用還是可以用相同的 product ID.
http://support.google.com/googleplay/android-developer/bin/answer.py?hl=en&answer=1072599

"Product IDs are unique across all the items for the same application and cannot be reused in the future."

小鰻 said...

感謝Sam大!

阿檬 said...

我利用最新版本的google play 的in app 範例 , 我只是分析用戶是不是購買完全版本 , google 郤把所有購買方式合併在一起 , 我搞了Dungeons.java一個星期 , 結果都不知如何抽起public boolean isEnabled 中 , 分析是不是購買完全版本 , 求謝解答

阿檬 said...

另外我已經可以在checkout 看到我的購買 , 但 google 的範例總是第一次彈出 找不到項目 , 按下確定後會轉回我正確的購買項目 , 很古怪

小鰻 said...

Dear阿檬,抱歉今天才看到留言,
我不太懂你完全版本這件事的整個business logic,可能再麻煩你寄信來跟我說明。
另外,第1次彈找不到項目,第2次顯示正確購買這件事我也遇過,你現在登入手機的帳戶是不是跟販售者帳戶相同?

阿法貝塔 said...

Dear 小鰻
我想請問一下 目前的Google Play的In-app-billing 是否可以正常使用

我自己也有看到某家公司使用Google Wallet 放在app裡面來當In-app-billing 使用

所以是否還是被鎖起來的狀況 謝謝回答

Sparks said...

感謝寫出這麼詳細的筆記。。這裡面的問題我基本上都遇到過了。按照你撰寫的方法應該很快能夠解決了。!感謝,感謝。

小鰻 said...

Dear 阿法貝塔
應該是還能用哦!

owen said...

小鰻你好!
我想請問一下,
我測試iap v3的範例,不過會跳出錯誤為:
"這一版的應用程是為提供google Play 結帳功能..."

你所說的注意事項我都有確認
請問一下還有要注意的嗎??

Nevin Chen said...

owen.
1. You should installed the signed version APK on your device. Debug mode is not allowed.
2. You'll need to wait a couple of hours before the APP is ready in Google play

Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Nevin Chen said...
This comment has been removed by the author.
Little Little said...

太感謝了, 省了半天debug

jing-yang min said...

你好,我这边用测试账号账号测试支付,点击购买后弹出 Error:retrieving information from server [DF-DIC-02].求解答是哪里出问题了。谢谢