1. 簡介
在本程式碼研究室中,您將著重於建立一次性商品、整合應用程式與 Play 帳款服務程式庫 (PBL),以及分析購買交易中斷的原因。
觀眾
這個程式碼研究室適用於使用 Play 帳款服務程式庫 (PBL) 的 Android 應用程式開發人員,或想使用 PBL 透過一次性產品營利的開發人員。
課程內容...
- 如何在 Google Play 管理中心建立一次性產品。
- 如何將應用程式與 PBL 整合。
- 瞭解如何在 PBL 中處理消耗性和非消耗性一次性產品購買交易。
- 如何分析購物流程中途放棄情形。
事前準備
- 使用開發人員帳戶存取 Google Play 管理中心。如果沒有開發人員帳戶,請建立帳戶。
- 本程式碼研究室的範例應用程式,您可以從 GitHub 下載。
- Android Studio。
2. 建構範例應用程式
這個範例應用程式是功能齊全的 Android 應用程式,提供完整原始碼,可展示下列各方面:
- 將應用程式與 PBL 整合
- 擷取一次性產品
- 啟動一次性產品的購買流程
- 購買情境會導致下列帳單回應:
BILLING_UNAVAILABLE
USER_CANCELLED
OK
ITEM_ALREADY_OWNED
以下示範影片顯示範例應用程式部署及執行後的樣貌和行為。
必備條件
建構及部署範例應用程式前,請先完成下列步驟:
- 建立 Google Play 管理中心開發人員帳戶。如果已有開發人員帳戶,請略過這個步驟。
- 在 Play 管理中心建立新應用程式。建立應用程式時,您可以為範例應用程式指定任何應用程式名稱。
- 安裝 Android Studio。
建構
這個建構步驟的目標是產生範例應用程式的已簽署 Android App Bundle 檔案。
如要產生 Android 應用程式套件,請按照下列步驟操作:
- 從 GitHub 下載範例應用程式。
- 建構範例應用程式。建構前,請先變更範例應用程式的套件名稱,然後再建構。如果 Play 管理中心中還有其他應用程式的套件,請確保您為範例應用程式提供的套件名稱是專屬的。
注意:建構範例應用程式只會建立 APK 檔案,可用於本機測試。不過,由於您尚未在 Play 管理中心設定產品,因此執行應用程式不會擷取產品和價格,您將在本程式碼研究室中進一步瞭解這項操作。 - 產生已簽署的 Android App Bundle。
接下來,請將 Android 應用程式套件上傳至 Google Play 管理中心。
3. 在 Play 管理中心建立一次性產品
如要在 Google Play 管理中心建立一次性產品,您必須在 Play 管理中心擁有應用程式。在 Play 管理中心建立應用程式,然後上傳先前建立的已簽署應用程式套件。
建立應用程式
如要建立應用程式,請按照下列步驟操作:
- 使用開發人員帳戶登入 Google Play 管理中心。
- 按一下「建立應用程式」,開啟「建立應用程式」頁面。
- 輸入應用程式名稱、選取預設語言,以及其他應用程式相關詳細資料。
- 按一下「建立應用程式」,即可在 Google Play 管理中心建立應用程式。
現在可以上傳範例應用程式的已簽署應用程式套件。
上傳已簽署的應用程式套件
- 將已簽署的應用程式套件上傳至 Google Play 管理中心的內部測試群組。上傳後,您才能在 Play 管理中心設定營利相關功能。
- 依序點選「測試及發布」>「測試」>「內部測試版本」>「建立新版本」。
- 輸入發布版本名稱,然後上傳已簽署的應用程式套件檔案。
- 依序點選「下一步」和「儲存並發布」。
現在你可以建立一次性產品。
建立一次性產品
如何建立一次性產品:
- 在 Google Play 管理中心的左側導覽選單中,依序前往「透過 Google Play 營利」 >「產品」 >「一次性產品」。
- 按一下「建立一次性產品」。
- 輸入下列產品詳細資料:
- 產品 ID:輸入專屬產品 ID。輸入
one_time_product_01
。 - (選用) 標記:新增相關標記。
- 名稱:輸入產品名稱。例如
Product name
。 - 說明:輸入產品說明。例如
Product description
。 - (選用) 新增圖示圖片:上傳代表產品的圖示。
- 產品 ID:輸入專屬產品 ID。輸入
- 點選 [下一步]。
- 新增購買選項,並設定區域供應情形。一次性產品至少須有一個購買選項,用於定義授權方式、價格和區域供應情形。在本程式碼研究室中,我們將為產品新增標準的「購買」選項。在「購買選項」部分,輸入下列詳細資料:
- 購買選項 ID:輸入購買選項 ID。例如
buy
。 - 交易類型:選取「購買」。
- (選用) 標記:新增這個購買選項專屬的標記。
- (選用) 按一下「進階選項」,設定進階選項。在本程式碼研究室中,您可以略過進階選項設定。
- 購買選項 ID:輸入購買選項 ID。例如
- 在「適用情形與定價」部分,依序點選「設定價格」 >「大量編輯價格」。
- 選取「國家 / 地區」選項。這會選取所有區域。
- 按一下「繼續」。系統會開啟對話方塊,供你輸入價格。輸入 10 美元,然後按一下「套用」。
- 依序按一下「儲存」和「啟用」。系統會建立並啟用購買選項。
以本程式碼研究室為例,請建立 3 項一次性產品,並使用下列產品 ID:
- consumable_product_01
- consumable_product_02
- consumable_product_03
範例應用程式已設定為使用這些產品 ID。您可以提供不同的產品 ID,但必須修改範例應用程式,才能使用您提供的產品 ID。
在 Google Play 管理中心開啟範例應用程式,然後依序前往「透過 Google Play 營利」 >「產品」 >「一次性產品」。然後按一下「建立一次性產品」,並重複步驟 3 至 9。
一次性產品建立影片
以下範例影片顯示先前所述的一次性產品建立步驟。
4. 與 PBL 整合
接下來,我們將說明如何整合應用程式與 Play 帳款服務程式庫 (PBL)。本節說明整合的高階步驟,並提供每個步驟的程式碼片段。您可以參考這些程式碼片段,實作實際的整合功能。
如要整合應用程式與 PBL,請按照下列步驟操作:
- 將 Play 帳款服務程式庫依附元件新增至範例應用程式。
dependencies { val billing_version = "8.0.0" implementation("com.android.billingclient:billing-ktx:$billing_version") }
- 初始化 BillingClient。BillingClient 是位於應用程式的用戶端 SDK,可與 Play 帳款服務程式庫通訊。下列程式碼片段說明如何初始化帳單用戶端。
protected BillingClient createBillingClient() { return BillingClient.newBuilder(activity) .setListener(purchasesUpdatedListener) .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) .enableAutoServiceReconnection() .build(); }
- 連線至 Google Play。下列程式碼片段說明如何連線至 Google Play。
public void startBillingConnection(ImmutableList<Product> productList) { Log.i(TAG, "Product list sent: " + productList); Log.i(TAG, "Starting connection"); billingClient.startConnection( new BillingClientStateListener() { @Override public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingResponseCode.OK) { // Query product details to get the product details list. queryProductDetails(productList); } else { // BillingClient.enableAutoServiceReconnection() will retry the connection on // transient errors automatically. // We don't need to retry on terminal errors (e.g., BILLING_UNAVAILABLE, // DEVELOPER_ERROR). Log.e(TAG, "Billing connection failed: " + billingResult.getDebugMessage()); Log.e(TAG, "Billing response code: " + billingResult.getResponseCode()); } } @Override public void onBillingServiceDisconnected() { Log.e(TAG, "Billing Service connection lost."); } }); }
- 擷取一次性產品詳細資料。將應用程式與 PBL 整合後,您必須將一次性產品詳細資料擷取到應用程式中。下列程式碼片段說明如何在應用程式中擷取一次性產品詳細資料。
擷取private void queryProductDetails(ImmutableList<Product> productList) { Log.i(TAG, "Querying products for: " + productList); QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(productList).build(); billingClient.queryProductDetailsAsync( queryProductDetailsParams, new ProductDetailsResponseListener() { @Override public void onProductDetailsResponse( BillingResult billingResult, QueryProductDetailsResult productDetailsResponse) { // check billingResult Log.i(TAG, "Billing result after querying: " + billingResult.getResponseCode()); // process returned productDetailsList Log.i( TAG, "Print unfetched products: " + productDetailsResponse.getUnfetchedProductList()); setupProductDetailsMap(productDetailsResponse.getProductDetailsList()); billingServiceClientListener.onProductDetailsFetched(productDetailsMap); } }); }
ProductDetails
,系統會傳回類似以下的回應:{ "productId": "consumable_product_01", "type": "inapp", "title": "Shadow Coat (Yolo's Realm | Play Samples)", "name": "Shadow Coat", "description": "A sleek, obsidian coat for stealth and ambushes", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 1990000, "priceCurrencyCode": "USD", "formattedPrice": "$1.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }, { "productId": "consumable_product_02", "type": "inapp", "title": "Emperor Den (Yolo's Realm | Play Samples)", "name": "Emperor Den", "description": "A fair lair glowing with molten rock and embers", "skuDetailsToken": "<---skuDetailsToken--->", "oneTimePurchaseOfferDetails": {}, "oneTimePurchaseOfferDetailsList": [ { "priceAmountMicros": 2990000, "priceCurrencyCode": "USD", "formattedPrice": "$2.99", "offerIdToken": "<--offerIdToken-->", "purchaseOptionId": "buy", "offerTags": [] } ] }
- 啟動結帳流程。
public void launchBillingFlow(String productId) { ProductDetails productDetails = productDetailsMap.get(productId); if (productDetails == null) { Log.e( TAG, "Cannot launch billing flow: ProductDetails not found for productId: " + productId); billingServiceClientListener.onBillingResponse( BillingResponseCode.ITEM_UNAVAILABLE, BillingResult.newBuilder().setResponseCode(BillingResponseCode.ITEM_UNAVAILABLE).build()); return; } ImmutableList<ProductDetailsParams> productDetailsParamsList = ImmutableList.of( ProductDetailsParams.newBuilder().setProductDetails(productDetails).build()); BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() .setProductDetailsParamsList(productDetailsParamsList) .build(); billingClient.launchBillingFlow(activity, billingFlowParams); }
- 偵測及處理購買交易。在這個步驟中,您需要:
- 驗證購買交易
- 授予使用者授權
- 通知使用者
- 向 Google 通知購買程序
private void handlePurchase(Purchase purchase) { // Step 1: Send the purchase to your secure backend to verify the purchase following // https://developer.android.com/google/play/billing/security#verify // Step 2: Update your entitlement storage with the purchase. If purchase is // in PENDING state then ensure the entitlement is marked as pending and the // user does not receive benefits yet. It is recommended that this step is // done on your secure backend and can combine in the API call to your // backend in step 1. // Step 3: Notify the user using appropriate messaging. if (purchase.getPurchaseState() == PurchaseState.PURCHASED) { for (String product : purchase.getProducts()) { Log.d(TAG, product + " purchased successfully! "); } } // Step 4: Notify Google the purchase was processed. // For one-time products, acknowledge the purchase. // This sample app (client-only) uses billingClient.acknowledgePurchase(). // For consumable one-time products, consume the purchase // This sample app (client-only) uses billingClient.consumeAsync() // If you have a secure backend, you must acknowledge purchases on your server using the // server-side API. // See https://developer.android.com/google/play/billing/security#acknowledge if (purchase.getPurchaseState() == PurchaseState.PURCHASED && !purchase.isAcknowledged()) { if (shouldConsume(purchase)) { ConsumeParams consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); billingClient.consumeAsync(consumeParams, consumeResponseListener); } else { AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder() .setPurchaseToken(purchase.getPurchaseToken()) .build(); billingClient.acknowledgePurchase( acknowledgePurchaseParams, acknowledgePurchaseResponseListener); } } }
5. 分析購買流程中途放棄的狀況
到目前為止,程式碼研究室的 Play Billing 回應著重於有限的情境,例如 USER_CANCELLED、BILLING_UNAVAILABLE、OK 和 ITEM_ALREADY_OWNED 回應。不過,Play 結帳服務可能會傳回 13 種不同的回應碼,這些回應碼是由各種實際因素觸發。
本節將詳細說明 USER_CANCELLED
和 BILLING_UNAVAILABLE
錯誤回應的原因,並建議您可以採取的修正措施。
USER_CANCELED 回應錯誤代碼
這個回應代碼表示使用者已放棄購買流程 UI,但未完成購買。
可能原因 | 您可以採取哪些行動? |
|
|
BILLING_UNAVAILABLE 回應錯誤代碼
這個回應代碼表示由於使用者的付款服務供應商或所選付款方式發生問題,因此無法完成交易。例如使用者的信用卡已過期,或使用者位於不支援的國家/地區。這項代碼並不表示 Play 結帳系統本身發生錯誤。
可能原因 | 您可以採取哪些行動? |
|
|
回應錯誤代碼的重試策略
針對 Play 帳款服務程式庫 (PBL) 的可復原錯誤,有效的重試策略會因情境而異,例如使用者在工作階段中的互動 (如購買期間) 與背景作業 (如在應用程式恢復時查詢購買交易)。請務必實作這些策略,因為某些 BillingResponseCode
值代表可透過重試解決的暫時性問題,其他值則代表永久性問題,不需要重試。
如果使用者處於工作階段中發生錯誤,建議採用簡易的重試策略,並設定最大重試次數,盡量避免干擾使用者體驗。反之,對於背景作業 (例如確認新購買交易) 這類不需要立即執行的作業,建議採用指數輪詢。
如要進一步瞭解特定回應代碼和對應的建議重試策略,請參閱「處理 BillingResult 回應代碼」。
6. 後續步驟
- 瞭解如何充分運用 Play 結帳系統整合功能。
- 請務必在使用者開始購買這些產品後,遵循驗證及處理購買交易的最佳做法,確保後端安全無虞。
參考文件
7. 恭喜!
恭喜!您已順利透過 Google Play 管理中心建立新的一次性產品、測試帳單結算回應代碼,並分析購買交易中斷情形。
問卷調查
我們非常重視您對本程式碼研究室的意見。請考慮花幾分鐘填寫問卷調查。