將 Google Play 帳款服務程式庫整合至應用程式

本文說明如何將 Google Play Billing Library 整合至應用程式,以便開始販售產品。

購買流程

以下是一次性消費或訂閱項目的一般購買流程。

  1. 向使用者說明可以購買的內容。
  2. 啟動購買流程,以便使用者接受購買。
  3. 在伺服器上驗證購買交易。
  4. 將內容提供給使用者。
  5. 確認內容的交付程序。如果是消費性產品,請先消耗購買項目,讓使用者可以再次購買該商品。

訂閱會自動續約,直到取消為止。訂閱可呈現下列狀態:

  • 有效:使用者記錄良好,有權存取訂閱項目。
  • 已取消:使用者已取消訂閱,但在到期前仍擁有存取權。
  • 寬限期:使用者遇到付款問題,但仍擁有存取權,同時 Google 正在重試付款方式以扣除款項。
  • 保留中:使用者因遇到付款問題而不再擁有存取權,同時 Google 正在重試以該付款方式扣除款項。
  • 已暫停:使用者已暫停存取權,取消暫停後才可繼續存取。
  • 已到期:使用者已取消訂閱並失去存取權。訂閱到期後,系統會將使用者視為「已流失」。

初始化與 Google Play 的連線

如要整合 Google Play 的帳單系統,首先請將 Google Play 帳款服務程式庫新增至應用程式並啟動連線。

新增 Google Play 帳款服務程式庫依附元件

將 Google Play 帳款服務程式庫依附元件新增至應用程式的 build.gradle 檔案,如下所示:

Groovy

dependencies {
    def billing_version = "9.1.0"

    implementation "com.android.billingclient:billing:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "9.1.0"

    implementation("com.android.billingclient:billing:$billing_version")
}

如果您使用 Kotlin,Google Play 帳款服務程式庫 KTX 模組會包含 Kotlin 擴充功能和協同程式支援機制,可讓您在使用 Google Play 帳款服務程式庫時編寫慣用的 Kotlin 程式碼。如要在專案中加入這些擴充功能,請將下列依附元件新增至應用程式的 build.gradle 檔案,如下所示:

Groovy

dependencies {
    def billing_version = "9.1.0"

    implementation "com.android.billingclient:billing-ktx:$billing_version"
}

Kotlin

dependencies {
    val billing_version = "9.1.0"

    implementation("com.android.billingclient:billing-ktx:$billing_version")
}

初始化 BillingClient

加入 Google Play 帳款服務程式庫的依附元件後,您需要初始化 BillingClient 例項。BillingClient 是 Google Play 帳款服務程式庫與應用程式其餘部分之間通訊的主要介面。BillingClient 提供同步和非同步的便利方法,適用於許多常見的結帳作業。請注意下列事項:

如要建立 BillingClient,請使用 newBuilder。 您可以將任何內容傳遞至 newBuilder(),BillingClient 就可用來取得應用程式內容。因此,您不必擔心記憶體流失的問題。如要接收購買交易的最新動態,您也必須呼叫 setListener,並傳遞指向 PurchasesUpdatedListener 的參照。這個事件監聽器會接收應用程式內所有購買交易的最新動態。

Kotlin

private val purchasesUpdatedListener =
    PurchasesUpdatedListener { billingResult, purchases ->
        // To be implemented in a later section.
    }

private var billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    // Configure other settings.
    .build()

Java

private PurchasesUpdatedListener purchasesUpdatedListener = new PurchasesUpdatedListener() {
  @Override
  public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
      // To be implemented in a later section.
  }
};

private BillingClient billingClient = BillingClient.newBuilder(context)
  .setListener(purchasesUpdatedListener)
  // Configure other settings.
  .build();

連線至 Google Play

建立 BillingClient 後,您需要與 Google Play 建立連線。

如要連線至 Google Play,請呼叫 startConnection。 連線程序為非同步,在用戶端完成設定並準備好提出進一步要求後,您必須實作 BillingClientStateListener 才能收到回呼。

如果您尚未啟用自動重新連線服務,還必須實作重試邏輯,處理與 Google Play 連線中斷的問題。如要實作重試邏輯,請覆寫 onBillingServiceDisconnected() 回呼方法,並確認 BillingClient 會呼叫 startConnection() 方法重新連線 Google Play,然後再提出其他請求。如果您已啟用自動重新連線服務,則可將此方法實作為無作業。

以下範例說明如何啟動連線,並測試是否可供使用:

Kotlin

billingClient.startConnection(object : BillingClientStateListener {
    override fun onBillingSetupFinished(billingResult: BillingResult) {
        if (billingResult.responseCode == BillingResponseCode.OK) {
            // The BillingClient is ready. You can query purchases here.
        }
    }
    override fun onBillingServiceDisconnected() {
        // If automatic service reconnection is enabled, this can be left empty (no-op)
        // because the library handles retries. You can still use this for non-retry
        // tasks like logging or updating the UI to reflect a disconnected state.
        // Otherwise, try to restart the connection on the next request to
        // Google Play by calling the startConnection() method.
    }
})

Java

billingClient.startConnection(
        new BillingClientStateListener() {
            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                    // The BillingClient is ready. You can query purchases here.
                    // It's a good practice to query products after the connection is established.
                    queryProductDetails();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
                // This is automatically handled by the library when you call a method that requires a connection.
            }
        });

自動重新建立連線

Play 帳款服務程式庫 8.0.0 版導入 BillingClient.Builder 中的 enableAutoServiceReconnection() 方法後,如果服務中斷連線時發出 API 呼叫,程式庫現在可以自動重新建立服務連線。由於重新連線是在 API 呼叫前於內部處理,因此 SERVICE_DISCONNECTED 回應可能會減少。

如何啟用自動重新連線

建構 BillingClient 例項時,請使用 BillingClient.Builder 中的 enableAutoServiceReconnection() 方法啟用自動重新連線功能。

Kotlin

val billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build())
    .enableAutoServiceReconnection() // Add this line to enable reconnection
    .build()

Java

BillingClient billingClient = BillingClient.newBuilder(context)
    .setListener(listener)
    .enablePendingPurchases()
    .enableAutoServiceReconnection() // Add this line to enable reconnection
    .build();

顯示可供購買的產品

與 Google Play 建立連線後,就可以開始查詢可用的產品,並向使用者顯示。

向使用者顯示產品之前,查詢產品詳細資料是非常重要的步驟,因為這會傳回本地化的產品資訊。如果是訂閱項目,請確認產品顯示方式遵守所有 Play 政策。

如要查詢買斷制產品的詳細資料,請呼叫 queryProductDetailsAsync 方法。這個方法會根據單次產品設定,傳回多項優惠。詳情請參閱「單次產品的多種購買選項和優惠」。

如要處理非同步作業的結果,您必須一併指定實作 ProductDetailsResponseListener 介面的事件監聽器。接著,您可以覆寫 onProductDetailsResponse,在查詢完成時通知事件監聽器,如以下範例所示:

Kotlin

val queryProductDetailsParams =
    QueryProductDetailsParams.newBuilder()
        .setProductList(
            listOf(
                Product.newBuilder()
                    .setProductId("product_id_example")
                    .setProductType(ProductType.SUBS)
                    .build()
            )
        )
        .build()

billingClient.queryProductDetailsAsync(queryProductDetailsParams) {
        billingResult,
        queryProductDetailsResult ->
    if (billingResult.responseCode == BillingResponseCode.OK) {
        for (productDetails in queryProductDetailsResult.productDetailsList) {
            // Process successfully retrieved product details here.
        }

        for (unfetchedProduct in queryProductDetailsResult.unfetchedProductList) {
            // Handle any unfetched products as appropriate.
        }
    }
}

Java

QueryProductDetailsParams queryProductDetailsParams =
QueryProductDetailsParams.newBuilder()
    .setProductList(
        ImmutableList.of(
            Product.newBuilder()
                .setProductId("product_id_example")
                .setProductType(ProductType.SUBS)
                .build()))
    .build();

billingClient.queryProductDetailsAsync(
queryProductDetailsParams,
new ProductDetailsResponseListener() {
    public void onProductDetailsResponse(BillingResult billingResult,
            QueryProductDetailsResult queryProductDetailsResult) {
        if (billingResult.getResponseCode() == BillingResponseCode.OK) {
           for (ProductDetails productDetails : queryProductDetailsResult.getProductDetailsList()) {
             // Process successfully retrieved product details here.
           }

           for (UnfetchedProduct unfetchedProduct : queryProductDetailsResult.getUnfetchedProductList()) {
             // Handle any unfetched products as appropriate.
           }
        }
    }
}
)

查詢產品詳細資料時,請傳遞 QueryProductDetailsParams 例項,此例項可指定在 Google Play 管理中心內建立的產品 ID 字串清單以及 ProductType。ProductType 可以是一次性產品的 ProductType.INAPP,也可以是訂閱項目的 ProductType.SUBS。

使用 Kotlin 擴充功能進行查詢

如果您使用 Kotlin 擴充功能,可以呼叫 queryProductDetails() 擴充功能函式,查詢單次產品的詳細資料。

queryProductDetails() 會利用 Kotlin 協同程式,因此您不需定義單獨的事件監聽器。相對的,函式會先暫停直到查詢完成為止,之後您可以處理結果:

suspend fun processPurchases() {
    val productList = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("product_id_example")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )
    val params = QueryProductDetailsParams.newBuilder()
    params.setProductList(productList)

    // leverage queryProductDetails Kotlin extension function
    val productDetailsResult = withContext(Dispatchers.IO) {
        billingClient.queryProductDetails(params.build())
    }

    // Process the result.
}

在極少數情況下,某些裝置無法支援 ProductDetails 和 queryProductDetailsAsync(),這通常是因為 Google Play 服務版本過舊。為妥善支援此情境,請參閱 Play Billing Library 7 遷移指南,瞭解如何使用回溯相容性功能。

處理結果

Google Play Billing Library 會將查詢結果儲存在 QueryProductDetailsResult 物件中。QueryProductDetailsResult 包含 ProductDetails 物件的 List。接著,您可以對清單中的各個 ProductDetails 物件呼叫各種方法,查看成功擷取的單次產品相關資訊,例如價格或說明。如要查看可用的產品詳細資料,請參閱 ProductDetails 類別中的方法清單。

QueryProductDetailsResult 也包含 List 的 UnfetchedProduct 物件。接著,您可以查詢每個 UnfetchedProduct,取得與擷取失敗原因相應的狀態碼。如要查看可用的未擷取產品資訊,請參閱 UnfetchedProduct 類別中的方法清單。

在提供待售項目之前,請先檢查使用者是否尚未擁有該項目。如果使用者的商品庫中仍有可消耗項目,必須先消耗該項目才能再次購買。

提供訂閱項目前,請先確認使用者尚未訂閱, 此外,也請注意下列事項:

  • 如果是訂閱項目,queryProductDetailsAsync() 方法會傳回訂閱產品詳細資料,每個訂閱項目最多 50 項使用者適用優惠。如果使用者嘗試購買不符資格的優惠方案 (例如應用程式顯示過舊的優惠清單),Play 會將不符資格的情況告知使用者,讓他們可以選擇改為購買基本方案。

  • 如果是單次產品,queryProductDetailsAsync() 方法只會傳回使用者符合資格的優惠。如果使用者嘗試購買不符資格的優惠方案 (例如使用者已達購買數量上限),Play 會將不符資格的情況告知使用者,讓他們可以選擇改為購買其購買選項優惠方案。

啟動購買流程

如要透過應用程式啟動購買要求,請從應用程式的主要執行緒呼叫 launchBillingFlow() 方法。這個方法會參照 BillingFlowParams 物件,其中包含藉由呼叫 queryProductDetailsAsync 取得的相關 ProductDetails 物件。 如要建立 BillingFlowParams 物件,請使用 BillingFlowParams.Builder 類別。

Kotlin

// An activity reference from which the billing flow will be launched.
val activity : Activity = ...

val productDetailsParamsList = listOf(
    BillingFlowParams.ProductDetailsParams.newBuilder()
        // retrieve a value for productDetails by calling queryProductDetailsAsync()
        .setProductDetails(productDetails)
        // Get the offer token:
        // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
        // for a list of offers that are available to the user.
        // b. For subscriptions, call ProductDetails.getSubscriptionOfferDetails()
        // for a list of offers that are available to the user.
        .setOfferToken(selectedOfferToken)
        .build()
)

val billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build()

// Launch the billing flow
val billingResult = billingClient.launchBillingFlow(activity, billingFlowParams)

Java

// An activity reference from which the billing flow will be launched.
Activity activity = ...;

ImmutableList<BillingFlowParams.ProductDetailsParams> productDetailsParamsList =
    ImmutableList.of(
        BillingFlowParams.ProductDetailsParams.newBuilder()
             // retrieve a value for "productDetails" by calling queryProductDetailsAsync()
            .setProductDetails(productDetails)
            // Get the offer token:
            // a. For one-time products, call ProductDetails.getOneTimePurchaseOfferDetailsList()
            // for a list of offers that are available to the user.
            // b. For subscriptions, call ProductDetails.getSubscriptionOfferDetails()
            // for a list of offers that are available to the user.
            .setOfferToken(selectedOfferToken)
            .build()
    );

BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder()
    .setProductDetailsParamsList(productDetailsParamsList)
    .build();

// Launch the billing flow
BillingResult billingResult = billingClient.launchBillingFlow(activity, billingFlowParams);

launchBillingFlow() 方法會傳回 BillingClient.BillingResponseCode 中列出的其中一個回應代碼。請務必查看這項結果,確認啟動時購買流程沒有發生錯誤。BillingResponseCode 為 OK 表示啟動成功。

成功呼叫 launchBillingFlow() 時,系統會顯示 Google Play 購買畫面。圖 1 顯示某個訂閱項目的購買畫面:

Google Play 購買畫面顯示可供購買的訂閱項目
圖 1. Google Play 購買畫面顯示可供購買的訂閱項目。

Google Play 會呼叫 onPurchasesUpdated(),將購物作業的結果提供給實作 PurchasesUpdatedListener 介面的事件監聽器。該事件監聽器是在初始化用戶端期間使用 setListener() 方法指定。

您必須實作 onPurchasesUpdated() 來處理可能的回應代碼。以下範例說明如何覆寫 onPurchasesUpdated():

Kotlin

override fun onPurchasesUpdated(billingResult: BillingResult, purchases: List<Purchase>?) {
    if (billingResult.responseCode == BillingResponseCode.OK && purchases != null) {
        for (purchase in purchases) {
            // Process the purchase as described in the next section.
        }
    } else if (billingResult.responseCode == BillingResponseCode.USER_CANCELED) {
        // Handle an error caused by a user canceling the purchase flow.
    } else {
        // Handle any other error codes.
    }
}

Java

@Override
void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
  if (billingResult.getResponseCode() == BillingResponseCode.OK
      && purchases != null) {
      for (Purchase purchase : purchases) {
          // Process the purchase as described in the next section.
      }
  } else if (billingResult.getResponseCode() == BillingResponseCode.USER_CANCELED) {
      // Handle an error caused by a user canceling the purchase flow.
  } else {
      // Handle any other error codes.
  }
}

購買成功後,系統會產生與圖 2 類似的 Google Play 購買成功畫面。

Google Play 的購買成功畫面
圖 2. Google Play 的購買成功畫面。

購買成功後還會產生購買憑證,這是一個不重複 ID,代表已購買單次產品的使用者和產品 ID。應用程式可在使用者的裝置上儲存購買憑證。不過,強烈建議您將該憑證傳遞至安全的後端伺服器,方便驗證購買交易及防範詐欺行為。如要進一步瞭解這項程序,請參閱「偵測及處理購買交易」。

使用者也會收到隨附交易收據的電子郵件,收據內含該筆交易的訂單 ID 或專屬 ID。系統會在使用者每次購買一次性產品和初始訂閱項目,以及後續定期自動續訂時,發送內含專屬訂單 ID 的電子郵件。您可以在 Google Play 管理中心使用這組訂單 ID 管理退款。

顯示個人化的價格

如果應用程式可以發布給歐盟使用者,請在呼叫 launchBillingFlow 時使用 setIsOfferPersonalized() 方法,向使用者揭露應用程式是以自動化決策功能提供個人化商品價格。

Google Play 購買畫面顯示已針對使用者自訂價格。
圖 3. Google Play 購買畫面顯示已針對使用者自訂價格。

您必須查閱《消費者權利指令》(2011/83/EU) 6 (1) (ea) CRD 條款,確定您為使用者提供的價格是否經過個人化處理。

setIsOfferPersonalized() 接受布林輸入。如果為 true,Play UI 會包含揭露事項。如果為 false,UI 會省略揭露事項。預設值為 false。

詳情請參閱消費者說明中心。

附加使用者 ID

啟動購買流程時,應用程式可以使用 obfuscatedAccountId 或 obfuscatedProfileId,附加使用者進行購買交易時的任何使用者 ID。舉例來說,識別碼可以是使用者在您系統中的登入資訊經過模糊處理的版本。設定這些參數有助於 Google 偵測詐欺。此外,如「授予使用者權利」一文所述,這項功能可協助您確保系統將購買交易歸因給正確的使用者。

偵測及處理購買交易

本節所述的購買交易偵測和處理程序適用於所有類型的購買交易,包括應用程式外的購買交易,例如促銷兌換。

應用程式可透過下列任一方式偵測新的購買交易和已完成的購物交易未完成:

  1. 如果應用程式呼叫 launchBillingFlow (如上一節所述),或應用程式在應用程式外進行購買交易或完成待處理的購買交易時,正在執行並與帳款服務程式庫建立連線,就會呼叫 onPurchasesUpdated。例如,家庭成員在其他裝置上核准待處理的購買交易。
  2. 應用程式呼叫 queryPurchasesAsync 查詢使用者的購買交易時。

針對第 1 點,只要應用程式正在執行,且與 Google Play Billing Library 保持連線,系統就會自動為新交易或已完成的交易呼叫 onPurchasesUpdated。如果應用程式未執行,或應用程式沒有有效的 Google Play 帳款服務程式庫連線,系統就不會叫用 onPurchasesUpdated。請注意,建議應用程式在前景運作時盡量保持連線狀態,以便及時取得購買交易更新。

針對第 2 項,您必須呼叫 BillingClient.queryPurchasesAsync(),確保應用程式能處理所有購買交易。建議您在應用程式成功與 Google Play Billing Library 建立連線時執行這項操作 (建議在應用程式啟動或進入前景時執行,詳情請參閱「初始化 BillingClient」)。方法是在收到 onServiceConnected 的成功結果時,呼叫 queryPurchasesAsync。遵循這項建議,對於處理下列事件和情況至關重要:

  • 購物期間的網路問題:使用者成功完成購買交易且收到 Google 的確認通知,但使用者的裝置和您的應用程式在透過 PurchasesUpdatedListener 收到購買通知前失去網路連線。
  • 多部裝置:使用者在某部裝置上購買商品,且希望在其他裝置上也能查看該商品。
  • 處理應用程式外的購買交易:某些購買交易可在應用程式外完成,例如促銷兌換。
  • 處理購物交易狀態轉換:使用者可能會在應用程式未執行時完成「購物交易未完成」購買交易的付款,並希望在開啟應用程式時收到交易完成確認。
  • 暫停訂閱:訂閱項目可能會在訂閱生命週期中暫停。只有在 QueryPurchasesParams.Builder 上設定 includeSuspendedSubscriptions 參數時,BillingClient.queryPurchasesAsync() 才會傳回暫停的訂閱項目。PurchasesUpdatedListener 不會傳回已暫停的訂閱項目。

應用程式偵測到新的或已完成的購買交易後,應採取下列行動:

  • 驗證購買交易。
  • 將內容授予完成購買交易的使用者。
  • 通知使用者。
  • 通知 Google 您的應用程式已處理完成的購買交易。

以下各節將詳細說明這些步驟,並在最後一節總結所有步驟。

驗證購買交易

應用程式應一律先驗證購買交易的合法性,再向使用者提供福利。如要瞭解如何驗證購買交易,請參閱「在授權前驗證購買交易」一文。應用程式應先驗證購買交易,再繼續處理交易並授予使用者權限,這部分會在下一節中說明。如果購買交易未通過驗證、資格或濫用行為檢查,請參閱「處理遭拒或未完成的購買交易」。

將授權授予使用者

應用程式驗證購買交易後,可以繼續向使用者授予權利並通知使用者。授權前,請確認應用程式會檢查購買狀態是否為 PURCHASED。如果購買交易處於「待處理」狀態,應用程式應通知使用者仍須完成操作,才能完成交易並取得授權。只有在購買狀態從「PENDING」轉為「PURCHASED」時,才可授權。詳情請參閱「處理待處理交易」。

如果您已按照「附加使用者 ID」一節所述,將使用者 ID 附加至購買交易,則可擷取並使用這些 ID,在系統中歸因至正確的使用者。如果應用程式可能遺失購買交易的背景資訊,無法判斷交易是為哪位使用者而進行,這項技巧就很有用。為防止濫用行為,Google Play 建議您先在安全後端驗證這些 ID,再授予授權。詳情請參閱「在授權前驗證購買交易」。請注意,在應用程式外進行的交易不會設定這些 ID。在這種情況下,應用程式可以將授權授予登入的使用者,或提示使用者選取偏好的帳戶。

如果是預購,在推出時間之前,購買交易會處於 PENDING 狀態。預購交易會在推出時完成,狀態會變更為「PURCHASED」,您不需採取任何額外動作。

通知使用者

授權使用者後,應用程式應顯示通知,確認購買交易成功。由於有通知,使用者不會對交易是否成功感到困惑,否則可能會停止使用應用程式、聯絡使用者支援團隊,或在社群媒體上抱怨。請注意,應用程式可能會在應用程式生命週期的任何時間偵測到購買交易更新。舉例來說,如果家長在其他裝置上核准待處理的購買交易,應用程式可能會想延遲通知使用者,直到適當的時間。以下是適合延遲的情況:

  • 在遊戲需要大量操作的部分或過場畫面中,顯示訊息可能會分散使用者的注意力。在這種情況下,您必須在操作結束後通知使用者。
  • 遊戲的初始教學課程和使用者設定期間。舉例來說,使用者可能在安裝應用程式前,已在應用程式外完成購買交易。建議您在新使用者開啟遊戲後或初次進行使用者設定期間,立即發出獎勵通知。如果應用程式要求使用者先建立帳戶或登入,才能授予權利,建議您向使用者說明如何完成步驟來兌換購買內容。如果應用程式未處理購買交易,系統會在 3 天後退款,因此請務必確認應用程式能處理這類交易。

通知使用者購買交易時,Google Play 建議採用下列機制:

  • 顯示應用程式內對話方塊。
  • 將訊息傳送至應用程式內通訊訊息方塊,並清楚說明應用程式內通訊訊息方塊中有新訊息。
  • 使用作業系統通知訊息。

通知應告知使用者獲得的福利。例如「你已購買 100 枚金幣!」。此外,如果購買交易是 Play Pass 等計畫的福利,應用程式會向使用者說明。例如「收到的產品!您已使用 Play Pass 兌換 100 顆寶石。 繼續。」。每個計畫可能都有建議向使用者顯示的文字,以說明福利。

通知 Google 購買交易已處理完畢

應用程式授權給使用者,並通知他們交易成功後,必須通知 Google 購買交易已成功處理。方法是確認購買交易,且必須在三天內完成,否則系統會自動退款並撤銷授權。以下各節將說明確認不同類型購買交易的程序。

消耗性產品

如果應用程式設有安全的後端,建議您針對消耗性產品,使用 Purchases.products:consume 確實消耗購買項目。您可以根據呼叫 Purchases.products:get 的結果檢查 consumptionState,確定購買項目尚未消耗。如果應用程式只有用戶端而沒有後端,請使用 Google Play 帳款服務程式庫的 consumeAsync()。這兩種方法都能執行確認要求,且會指出應用程式已授權給使用者。應用程式也可以使用這些方法,提供與輸入購買憑證相應的一次性產品,讓使用者再次購買。使用 consumeAsync() 時,您也必須傳遞可實作 ConsumeResponseListener 介面的物件,這個物件會處理消耗作業的結果。您可以覆寫 onConsumeResponse() 方法,Google Play Billing Library 會在作業完成後呼叫此方法。

以下範例示範如何使用相關聯的購買憑證,透過 Google Play 帳款服務程式庫消耗產品:

Kotlin

val consumeParams =
    ConsumeParams.newBuilder()
        .setPurchaseToken(purchase.getPurchaseToken())
        .build()
val consumeResult = withContext(Dispatchers.IO) {
    client.consumePurchase(consumeParams)
}

Java

ConsumeParams consumeParams =
        ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build();

ConsumeResponseListener listener =
        (billingResult, purchaseToken) -> {
            if (billingResult.getResponseCode() == BillingResponseCode.OK) {
                // Handle the success of the consume operation.
            }
        };

billingClient.consumeAsync(consumeParams, listener);

非消耗性產品

如果應用程式設有安全的後端,建議您使用 Purchases.products:acknowledge 確認非消耗性產品的購買交易。您可以根據呼叫 Purchases.products:get 的結果檢查 acknowledgementState,確定購買交易先前未經確認。

如果應用程式只有用戶端,請在應用程式中使用 Google Play 帳款服務程式庫的 BillingClient.acknowledgePurchase()。在確認購買交易之前,應用程式應檢查該筆交易是否已使用 Google Play 帳款服務程式庫的 isAcknowledged() 方法完成確認。

以下範例說明如何使用 Google Play 帳款服務程式庫確認購買交易:

Kotlin

val client: BillingClient = billingClient
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
    .setPurchaseToken(purchase.purchaseToken)
val ackPurchaseResult = withContext(Dispatchers.IO) {
    client.acknowledgePurchase(acknowledgePurchaseParams.build())
}

Java

if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) {
    if (!purchase.isAcknowledged()) {
        AcknowledgePurchaseParams acknowledgePurchaseParams =
                AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.getPurchaseToken())
                        .build();
        billingClient.acknowledgePurchase(acknowledgePurchaseParams, (billingResult) -> {
            // Acknowledgment handled.
        });
    }
}

訂閱項目

處理訂閱項目的方式與非消耗性產品類似。如果可能,請使用 Google Play Developer API 提供的 Purchases.subscriptions.acknowledge,從安全的後端確認購買交易。驗證購買交易先前未經確認,方法是透過 Purchases.subscriptions:get 檢查購買資源中的 acknowledgementState。否則,您也可以在檢查 isAcknowledged() 後,使用 Google Play Billing Library 的 BillingClient.acknowledgePurchase() 確認訂閱。所有初始訂閱項目的購買交易都需經過確認,訂閱項目的續訂則無需確認。如要進一步瞭解需要確認訂閱項目的時機,請參閱「販售訂閱項目」主題。

處理遭拒或未完成的交易

如果購買交易未通過後端驗證、使用者資格檢查、詐欺和濫用行為檢查,或系統無法履行授權,請勿授予授權,也不要確認或消耗購買交易。

雖然未確認的購買交易會在確認期限到期後自動退款並撤銷,以保障消費者權益,但請勿依賴這個到期期限來拒絕購買交易。如果確認視窗逾時,訂單會維持在未確認狀態,Google Play 也無法區分開發人員是刻意拒絕,還是後端處理逾時。

請改用 Google Play Developer API,從後端立即撤銷並退還遭拒的購買交易:

  • 訂閱項目:在 revocationContext 中使用 "fullRefund": {} 呼叫 purchases.subscriptionsv2.revoke。系統會立即終止訂閱、撤銷存取權,並啟動全額退款程序。
  • 一次性產品:呼叫 orders.refund 並將 revoke 參數設為 true,即可立即退還訂單款項並撤銷授權。呼叫這個方法時,您必須傳遞購買交易的訂單 ID,該 ID 可透過 Purchase.getOrderId() 或 purchases.productsv2.getproductpurchasev2 擷取。請勿對遭拒的購買交易呼叫 consumeAsync()、acknowledgePurchase()、Purchases.products:consume 或 Purchases.products:acknowledge。
  • 即時開發人員通知和交易作廢行為:開發人員發起的撤銷和退款 (purchases.subscriptionsv2.revoke 和 orders.refund,且 revoke 設為 true) 會觸發即時開發人員通知,並記錄在 Voided Purchases API 中 (purchases.voidedpurchases.list,且 voidedSource 回應欄位設為 1,適用於開發人員):
    • 訂閱:同時傳送 SUBSCRIPTION_REVOKED 通知和 VoidedPurchaseNotification。
    • 單次產品:傳送 VoidedPurchaseNotification (如果應用程式在 Play 管理中心設為接收所有單次產品通知,則會傳送 ONE_TIME_PRODUCT_CANCELED 通知)。
    • 呼叫 orders.refund 時,如果未將 revoke 設為 true,系統會退款給訂單,但不會撤銷授權,也不會觸發撤銷或交易作廢通知。詳情請參閱「管理訂閱項目和單次購買」。

回顧

以下是這些步驟的摘要。

  1. 將購買交易傳送至安全後端,在授權前驗證購買交易。

  2. 根據購買項目更新授權儲存空間。如果購買交易處於 PENDING 狀態,請確認授權標示為待處理,且使用者尚未獲得福利。建議您在安全的後端執行這個步驟,並在步驟 1 中與對後端的 API 呼叫結合。

  3. 使用適當的訊息通知使用者 (如有需要,請按照先前討論的方式延遲通知)。

  4. 按照「處理購買交易」一節所述的步驟,通知 Google 購買交易已處理完畢。

如要確認應用程式是否正確執行這些步驟,請參閱測試指南。

處理未完成的交易

Google Play 支援「未完成的交易」,這類交易程序從使用者發起購買交易開始,到系統處理購買交易付款方式的這段期間,需要完成一或多個額外步驟。除非 Google 通知您已透過使用者的付款方式成功扣款,否則應用程式不應授權給這類購買交易。

舉例來說,使用者可以選擇實體商店,稍後再以現金付款,藉此發起交易。使用者會透過通知和電子郵件接收代碼。當使用者來到實體商店時,就可以向收銀員兌換代碼,再用現金付款。接著,Google 會通知您和使用者已收到款項,您的應用程式就可以向使用者授權。

在初始化 BillingClient 時呼叫 enablePendingPurchases(),為應用程式啟用待處理交易。應用程式必須啟用並支援單次產品的待處理交易。新增支援前,請務必瞭解待處理交易的購買生命週期。

無論應用程式是透過 PurchasesUpdatedListener 還是呼叫 queryPurchasesAsync 而收到新的購買交易,都須使用 getPurchaseState() 方法來判斷購買狀態是 PURCHASED 或 PENDING。只有在狀態是 PURCHASED 時才可授權。

如果使用者完成購買時,您的應用程式正在運作,且您已建立有效的 Play Billing Library 連線,系統會再次呼叫 PurchasesUpdatedListener,而 PurchaseState 現已變為 PURCHASED。這時,應用程式可按照偵測及處理購買交易的標準方法處理購買交易。應用程式也應在應用程式的 onResume() 方法中呼叫 queryPurchasesAsync(),處理在應用程式未運作期間轉換為 PURCHASED 狀態的購買交易。

當購買交易從 PENDING 變為 PURCHASED 時,real_time_developer_notifications 用戶端會收到 ONE_TIME_PRODUCT_PURCHASED 或 SUBSCRIPTION_PURCHASED 通知。如果購買交易遭到取消,您會收到 ONE_TIME_PRODUCT_CANCELED 或 SUBSCRIPTION_PENDING_PURCHASE_CANCELED 通知。如果客戶在指定時間範圍內未完成付款,就可能發生這種情況。 請注意,您隨時可以使用 Google Play Developer API 檢查購買交易的目前狀態。

處理多件購買交易

在 Google Play 帳款服務程式庫 4.0 以上版本中,Google Play 允許客戶在購物車中指定購買數量,在一筆交易中購買多件相同的買斷制產品。應用程式必須有能力處理多件購買交易,並按照指定的購買數量授權。

為了滿足多件購買交易的需求,應用程式的佈建邏輯需要檢查項目數量。您可以透過下列任一 API 存取 quantity 欄位:

加入多件購買交易的處理邏輯後,您需要前往 Google Play 管理中心的「一次性商品管理」頁面,為相應的產品啟用多件購買交易功能。

查詢使用者的結帳設定

getBillingConfigAsync() 會提供使用者要用於 Google Play 的國家/地區。

建立 BillingClient 後,即可查詢使用者的結帳設定。下列程式碼片段說明如何呼叫 getBillingConfigAsync()。實作 BillingConfigResponseListener 來處理回應。凡是從應用程式啟動的結帳設定查詢,都會由這個事件監聽器接收更新內容。

如果傳回的 BillingResult 沒有任何錯誤,您可以檢查 BillingConfig 物件的 countryCode 欄位,取得使用者的 Play 國家/地區。

Kotlin

// Use the default GetBillingConfigParams.
val getBillingConfigParams = GetBillingConfigParams.newBuilder().build()
billingClient.getBillingConfigAsync(
    getBillingConfigParams,
    object : BillingConfigResponseListener {
        override fun onBillingConfigResponse(
            billingResult: BillingResult,
            billingConfig: BillingConfig?
        ) {
            if (billingResult.responseCode == BillingResponseCode.OK &&
                billingConfig != null
            ) {
                val countryCode = billingConfig.countryCode
                // ...
            } else {
                // Handle errors
            }
        }
    }
)

Java

GetBillingConfigParams getBillingConfigParams = GetBillingConfigParams.newBuilder().build();
billingClient.getBillingConfigAsync(
        getBillingConfigParams,
        (billingResult, billingConfig) -> {
            if (billingResult.getResponseCode() == BillingResponseCode.OK && billingConfig != null) {
                String countryCode = billingConfig.getCountryCode();
            } else {
                // TODO: Handle errors
            }
        });