Android 應用程式內購

Godot 提供官方的 GodotGooglePlayBilling Android 外掛,相容於 Godot 4.2+,並使用 Google Play Billing 函式庫

用法

入門

請確保你已啟用並成功設定 Android Gradle Builds。接著依照 GodotGooglePlayBillingGitHub 頁面 上的安裝說明操作。

初始化外掛程式

要使用 GodotGooglePlayBilling API:

  1. Access the BillingClient.

  2. 連接其訊號以接收交易結果。

  3. 呼叫 start_connection

初始化範例:

var billing_client: BillingClient
func _ready():
    billing_client = BillingClient.new()
    billing_client.connected.connect(_on_connected) # No params
    billing_client.disconnected.connect(_on_disconnected) # No params
    billing_client.connect_error.connect(_on_connect_error) # response_code: int, debug_message: String
    billing_client.query_product_details_response.connect(_on_query_product_details_response) # response: Dictionary
    billing_client.query_purchases_response.connect(_on_query_purchases_response) # response: Dictionary
    billing_client.on_purchase_updated.connect(_on_purchase_updated) # response: Dictionary
    billing_client.consume_purchase_response.connect(_on_consume_purchase_response) # response: Dictionary
    billing_client.acknowledge_purchase_response.connect(_on_acknowledge_purchase_response) # response: Dictionary

    billing_client.start_connection()

API 必須在連線狀態下才能使用。當連線成功時會送出 connected 訊號。你也可以使用 is_ready() 判斷外掛是否已就緒。get_connection_state() 函式會回傳外掛目前的連線狀態。

get_connection_state() 的回傳值:

# Matches BillingClient.ConnectionState in the Play Billing Library.
# Access in your script as: BillingClient.ConnectionState.CONNECTED
enum ConnectionState {
    DISCONNECTED, # This client was not yet connected to billing service or was already closed.
    CONNECTING, # This client is currently in process of connecting to billing service.
    CONNECTED, # This client is currently connected to billing service.
    CLOSED, # This client was already closed and shouldn't be used again.
}

查詢可購買項目

API 連線後, 請使用 query_product_details() 查詢產品 ID。在呼叫 purchase()purchase_subscription()update_subscription() 前, 必須先成功查詢產品詳細資料, 否則會回傳錯誤。 query_product_details() 需要兩個參數: 產品 ID 字串陣列, 以及要查詢的產品類型。一般應用內購買請用 BillingClient.ProductType.INAPP , 訂閱則用 BillingClient.ProductType.SUBS. 陣列中的 ID 字串必須與你在 Google Play Console 為 app 設定的產品 ID 一致。

query_product_details() 的使用範例:

func _on_connected():
  billing_client.query_product_details(["my_iap_item"], BillingClient.ProductType.INAPP) # BillingClient.ProductType.SUBS for subscriptions.

func _on_query_product_details_response(query_result: Dictionary):
    if query_result.response_code == BillingClient.BillingResponseCode.OK:
        print("Product details query success")
        for available_product in query_result.product_details:
            print(available_product)
    else:
        print("Product details query failed")
        print("response_code: ", query_result.response_code, "debug_message: ", query_result.debug_message)

查詢用戶購買紀錄

要取得使用者的購買紀錄,請呼叫 query_purchases() 並傳入要查詢的產品類型。一般應用內購買請用 BillingClient.ProductType.INAPP,訂閱則用 BillingClient.ProductType.SUBS。查詢結果會透過 query_purchases_response 訊號傳回。該訊號有一個參數:一個 Dictionary,包含回應代碼以及購買項目陣列或除錯訊息。購買陣列只包含有效訂閱與尚未消耗的一次性購買。

query_purchases() 的使用範例:

func _query_purchases():
    billing_client.query_purchases(BillingClient.ProductType.INAPP) # Or BillingClient.ProductType.SUBS for subscriptions.

func _on_query_purchases_response(query_result: Dictionary):
    if query_result.response_code == BillingClient.BillingResponseCode.OK:
        print("Purchase query success")
        for purchase in query_result.purchases:
            _process_purchase(purchase)
    else:
        print("Purchase query failed")
        print("response_code: ", query_result.response_code, "debug_message: ", query_result.debug_message)

購買項目

To launch the billing flow for an item: Use purchase() for in-app products, passing the product ID string. Use purchase_subscription() for subscriptions, passing the product ID and base plan ID. You may also optionally provide an offer ID.

對於 purchase()purchase_subscription() , 你可以選擇性地傳入布林值, 以指示是否使用 個人化優惠

提醒: 在把某項目傳給 purchase() 前, 你 必須 先查詢該項目的產品詳細資料。此方法會回傳一個字典, 表示結帳流程是否成功啟動。其中包含回應代碼以及購買陣列或除錯訊息。

purchase() 的使用範例:

var result = billing_client.purchase("my_iap_item")
if result.response_code == BillingClient.BillingResponseCode.OK:
    print("Billing flow launch success")
else:
    print("Billing flow launch failed")
    print("response_code: ", result.response_code, "debug_message: ", result.debug_message)

購買結果會透過 on_purchases_updated 訊號傳送。

func _on_purchases_updated(result: Dictionary):
    if result.response_code == BillingClient.BillingResponseCode.OK:
        print("Purchase update received")
        for purchase in result.purchases:
            _process_purchase(purchase)
    else:
        print("Purchase update error")
        print("response_code: ", result.response_code, "debug_message: ", result.debug_message)

處理購買項目

query_purchases_responseon_purchases_updated 訊號會提供一個 Dictionary 格式的購買紀錄陣列。每筆購買的字典鍵值會對應到 Google Play Billing 的 Purchase 類別欄位。

購買欄位:

order_id: String
purchase_token: String
package_name: String
purchase_state: int
purchase_time: int (milliseconds since the epoch (Jan 1, 1970))
original_json: String
is_acknowledged: bool
is_auto_renewing: bool
quantity: int
signature: String
product_ids: PackedStringArray

檢查購買狀態

請檢查購買紀錄中的 purchase_state 欄位,以判斷該筆購買是否已完成或仍在處理中。

PurchaseState 可能值:

# Matches Purchase.PurchaseState in the Play Billing Library
# Access in your script as: BillingClient.PurchaseState.PURCHASED
enum PurchaseState {
    UNSPECIFIED,
    PURCHASED,
    PENDING,
}

若購買處於 PENDING 狀態,請勿給予道具或進行任何後續處理,直到其進入 PURCHASED 狀態。如果你有商店介面,建議顯示提示告知用戶需至 Google Play 商店完成待處理訂單。詳情請參考 Google Play Billing 文件中的 處理待處理交易

消耗品

若你的應用內項目不是一次性購買,而是可重複購買的消耗品(例如金幣),可呼叫 consume_purchase() 並傳入購買字典中的 purchase_token 來消耗該項目。呼叫 consume_purchase() 會自動確認該筆購買。消耗後,使用者即可再次購買;除非再次購買,否則它不會出現在後續的 query_purchases() 查詢結果中。

consume_purchase() 的使用範例:

func _process_purchase(purchase):
    if "my_consumable_iap_item" in purchase.product_ids and purchase.purchase_state == BillingClient.PurchaseState.PURCHASED:
        # Add code to store payment so we can reconcile the purchase token
        # in the completion callback against the original purchase
        billing_client.consume_purchase(purchase.purchase_token)

func _on_consume_purchase_response(result: Dictionary):
    if result.response_code == BillingClient.BillingResponseCode.OK:
        print("Consume purchase success")
        _handle_purchase_token(result.token, true)
    else:
        print("Consume purchase failed")
        print("response_code: ", result.response_code, "debug_message: ", result.debug_message, "purchase_token: ", result.token)

# Find the product associated with the purchase token and award the
# product if successful
func _handle_purchase_token(purchase_token, purchase_successful):
    # check/award logic, remove purchase from tracking list

確認購買

若你的應用內項目為一次性購買,必須呼叫 acknowledge_purchase() 並傳入購買字典內的 purchase_token 來確認該筆交易。若三天內未確認,使用者會自動獲得退款,且 Google Play 會撤銷該筆購買。若你已呼叫 consume_purchase(),則會自動完成確認,不需再呼叫 acknowledge_purchase()

acknowledge_purchase() 的使用範例:

func _process_purchase(purchase):
    if "my_one_time_iap_item" in purchase.product_ids and \
            purchase.purchase_state == BillingClient.PurchaseState.PURCHASED and \
            not purchase.is_acknowledged:
        # Add code to store payment so we can reconcile the purchase token
        # in the completion callback against the original purchase
        billing_client.acknowledge_purchase(purchase.purchase_token)

func _on_acknowledge_purchase_response(result: Dictionary):
    if result.response_code == BillingClient.BillingResponseCode.OK:
        print("Acknowledge purchase success")
        _handle_purchase_token(result.token, true)
    else:
        print("Acknowledge purchase failed")
        print("response_code: ", result.response_code, "debug_message: ", result.debug_message, "purchase_token: ", result.token)

# Find the product associated with the purchase token and award the
# product if successful
func _handle_purchase_token(purchase_token, purchase_successful):
    # check/award logic, remove purchase from tracking list

訂閱

訂閱大致與一般應用內項目相同。要取得訂閱詳細資料,請在 query_product_details() 的第二個參數傳入 BillingClient.ProductType.SUBS。要取得訂閱購買紀錄,請在 query_purchases() 傳入 BillingClient.ProductType.SUBS

你可以檢查 query_purchases() 回傳之訂閱購買紀錄中的 is_auto_renewing,以判斷使用者是否已取消自動續訂。

新訂閱購買需進行確認,但自動續訂不需再次確認。

如果你的 App 支援在不同訂閱等級間升級或降級,應使用 update_subscription() 來進行訂閱更新流程以變更現有的有效訂閱。與 purchase() 相同,結果會透過 on_purchases_updated 訊號回傳。以下是 update_subscription() 的參數:

  1. old_purchase_token:目前有效訂閱的購買憑證(purchase token)

  2. replacement_mode:要套用於該訂閱的替換模式

  3. product_id:要切換到的新訂閱之產品 ID

  4. base_plan_id:目標訂閱的基礎方案 ID

  5. offer_id:基礎方案下的優惠 ID(選用)

  6. is_offer_personalized:是否啟用個人化定價(選用)

替換模式的值定義如下:

# Access in your script as: BillingClient.ReplacementMode.WITH_TIME_PRORATION
enum ReplacementMode {
    # Unknown...
    UNKNOWN_REPLACEMENT_MODE = 0,

    # The new plan takes effect immediately, and the remaining time will be prorated and credited to the user.
    # Note: This is the default behavior.
    WITH_TIME_PRORATION = 1,

    # The new plan takes effect immediately, and the billing cycle remains the same.
    CHARGE_PRORATED_PRICE = 2,

    # The new plan takes effect immediately, and the new price will be charged on next recurrence time.
    WITHOUT_PRORATION = 3,

    # Replacement takes effect immediately, and the user is charged full price of new plan and
    # is given a full billing cycle of subscription, plus remaining prorated time from the old plan.
    CHARGE_FULL_PRICE = 5,

    # The new purchase takes effect immediately, the new plan will take effect when the old item expires.
    DEFERRED = 6,
}

預設行為為 WITH_TIME_PRORATION

update_subscription() 的使用範例:

billing_client.update_subscription(_active_subscription_purchase.purchase_token, \
                    BillingClient.ReplacementMode.WITH_TIME_PRORATION, "new_sub_product_id", "base_plan_id")