Android 應用程式內購

Godot 提供官方的 GodotGooglePlayBilling Android 外掛程式,支援 Godot 4,並使用 Google Play Billing 函式庫

用法

入門

請確保你已啟用並正確設定 Android Gradle 建置。接著請依照 GodotGooglePlayBillingGitHub 頁面 上的編譯說明操作。

然後,將 ./godot-google-play-billing/build/outputs/aar/GodotGooglePlayBilling.***.release.aar./GodotGooglePlayBilling.gdap 兩個檔案放入專案的 res://android/plugins 資料夾中。

此外掛程式現在應會顯示於 Android 匯出設定中,並可在此啟用。

初始化外掛程式

要使用 GodotGooglePlayBilling API:

  1. 取得 GodotGooglePlayBilling 單例的參考

  2. 連接外掛的訊號處理器

  3. 呼叫 startConnection

初始化範例:

var payment

func _ready():
    if Engine.has_singleton("GodotGooglePlayBilling"):
        payment = Engine.get_singleton("GodotGooglePlayBilling")

        # These are all signals supported by the API
        # You can drop some of these based on your needs
        payment.billing_resume.connect(_on_billing_resume) # No params
        payment.connected.connect(_on_connected) # No params
        payment.disconnected.connect(_on_disconnected) # No params
        payment.connect_error.connect(_on_connect_error) # Response ID (int), Debug message (string)
        payment.price_change_acknowledged.connect(_on_price_acknowledged) # Response ID (int)
        payment.purchases_updated.connect(_on_purchases_updated) # Purchases (Dictionary[])
        payment.purchase_error.connect(_on_purchase_error) # Response ID (int), Debug message (string)
        payment.sku_details_query_completed.connect(_on_product_details_query_completed) # Products (Dictionary[])
        payment.sku_details_query_error.connect(_on_product_details_query_error) # Response ID (int), Debug message (string), Queried SKUs (string[])
        payment.purchase_acknowledged.connect(_on_purchase_acknowledged) # Purchase token (string)
        payment.purchase_acknowledgement_error.connect(_on_purchase_acknowledgement_error) # Response ID (int), Debug message (string), Purchase token (string)
        payment.purchase_consumed.connect(_on_purchase_consumed) # Purchase token (string)
        payment.purchase_consumption_error.connect(_on_purchase_consumption_error) # Response ID (int), Debug message (string), Purchase token (string)
        payment.query_purchases_response.connect(_on_query_purchases_response) # Purchases (Dictionary[])

        payment.startConnection()
    else:
        print("Android IAP support is not enabled. Make sure you have enabled 'Gradle Build' and the GodotGooglePlayBilling plugin in your Android export settings! IAP will not work.")

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

getConnectionState() 的回傳值:

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

查詢可購買項目

API 連線後,請使用 querySkuDetails() 查詢 SKU。在呼叫 purchase()queryPurchases() 前,必須先成功查詢 SKU,否則會回傳錯誤。querySkuDetails() 有兩個參數:SKU 名稱字串陣列,以及 SKU 類型字串。SKU 類型字串若為一般應用內購買請用 "inapp",若為訂閱請用 "subs"。陣列中的名稱必須與 Google Play Console 你 App 設定的 SKU 產品 ID 一致。

querySkuDetails() 的使用範例:

func _on_connected():
  payment.querySkuDetails(["my_iap_item"], "inapp") # "subs" for subscriptions

func _on_product_details_query_completed(product_details):
  for available_product in product_details:
    print(available_product)

func _on_product_details_query_error(response_id, error_message, products_queried):
    print("on_product_details_query_error id:", response_id, " message: ",
            error_message, " products: ", products_queried)

查詢用戶購買紀錄

要取得用戶的購買紀錄,請呼叫 queryPurchases(),並傳入要查詢的 SKU 類型字串。一般應用程式內購買請用 "inapp",訂閱請用 "subs"。查詢結果會透過 query_purchases_response 訊號傳回。該訊號僅有一個參數:一個 Dictionary,內含狀態碼與購買項目陣列或錯誤訊息。僅包含有效訂閱與尚未消耗的一次性購買。

queryPurchases() 的使用範例:

func _query_purchases():
    payment.queryPurchases("inapp") # Or "subs" for subscriptions

func _on_query_purchases_response(query_result):
    if query_result.status == OK:
        for purchase in query_result.purchases:
            _process_purchase(purchase)
    else:
        print("queryPurchases failed, response code: ",
                query_result.response_code,
                " debug message: ", query_result.debug_message)

在成功取得 SKU 詳細資訊後,建議於應用程式啟動時查詢購買紀錄。由於使用者可能會在 App 以外的地方進行購買或處理待完成交易,因此應於從背景回到前景時再次查詢購買。你可以利用 billing_resume 訊號來處理此情境。

billing_resume 的使用範例:

func _on_billing_resume():
    if payment.getConnectionState() == ConnectionState.CONNECTED:
        _query_purchases()

關於處理 queryPurchases() 回傳的購買項目,請參閱下方 處理購買項目 章節

購買項目

若要啟動購買流程,請呼叫 purchase() 並傳入要購買 SKU 的產品 ID。提醒: 必須 先查詢該 SKU 詳細資訊後,才能傳給 purchase()

purchase() 的使用範例:

payment.purchase("my_iap_item")

購買流程完成時,成功會發送 purchases_updated 訊號,失敗則會發送 purchase_error 訊號。

func _on_purchases_updated(purchases):
    for purchase in purchases:
        _process_purchase(purchase)

func _on_purchase_error(response_id, error_message):
    print("purchase_error id:", response_id, " message: ", error_message)

處理購買項目

query_purchases_responsepurchases_updated 訊號會提供一個包含多筆購買紀錄的 Dictionary 陣列。該購買字典的鍵與 Google Play Billing Purchase 類別的欄位對應。

購買欄位:

dictionary.put("order_id", purchase.getOrderId());
dictionary.put("package_name", purchase.getPackageName());
dictionary.put("purchase_state", purchase.getPurchaseState());
dictionary.put("purchase_time", purchase.getPurchaseTime());
dictionary.put("purchase_token", purchase.getPurchaseToken());
dictionary.put("quantity", purchase.getQuantity());
dictionary.put("signature", purchase.getSignature());
// PBL V4 replaced getSku with getSkus to support multi-sku purchases,
// use the first entry for "sku" and generate an array for "skus"
ArrayList<String> skus = purchase.getSkus();
dictionary.put("sku", skus.get(0)); # Not available in plugin
String[] skusArray = skus.toArray(new String[0]);
dictionary.put("products", productsArray);
dictionary.put("is_acknowledged", purchase.isAcknowledged());
dictionary.put("is_auto_renewing", purchase.isAutoRenewing());

檢查購買狀態

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

PurchaseState 可能值:

# Matches Purchase.PurchaseState in the Play Billing Library
enum PurchaseState {
    UNSPECIFIED,
    PURCHASED,
    PENDING,
}

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

消耗品

若你的應用程式內項目為可重複購買的消耗品(例如金幣),可呼叫 consumePurchase() 並傳入購買字典中的 purchase_token 進行消耗。呼叫 consumePurchase() 會自動確認該筆購買。被消耗後,該項目不會再出現在後續的 queryPurchases() 查詢結果中,使用者就可以再次購買。

consumePurchase() 的使用範例:

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

func _on_purchase_consumed(purchase_token):
    _handle_purchase_token(purchase_token, true)

func _on_purchase_consumption_error(response_id, error_message, purchase_token):
    print("_on_purchase_consumption_error id:", response_id,
            " message: ", error_message)
    _handle_purchase_token(purchase_token, false)

# Find the sku 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

確認購買

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

acknowledgePurchase() 的使用範例:

func _process_purchase(purchase):
    if "my_one_time_iap_item" in purchase.products and \
            purchase.purchase_state == 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
        payment.acknowledgePurchase(purchase.purchase_token)

func _on_purchase_acknowledged(purchase_token):
    _handle_purchase_token(purchase_token, true)

func _on_purchase_acknowledgement_error(response_id, error_message, purchase_token):
    print("_on_purchase_acknowledgement_error id: ", response_id,
            " message: ", error_message)
    _handle_purchase_token(purchase_token, false)

# Find the sku 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

訂閱

訂閱與一般應用程式內項目操作方式相似。若要查詢訂閱 SKU 詳細資料,請於 querySkuDetails() 的第二個參數傳入 "subs"。欲查詢訂閱購買紀錄,則於 queryPurchases() 傳入 "subs"

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

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

如果你的 App 支援不同訂閱等級的升級或降級,請使用 updateSubscription() 來執行訂閱更新流程。其回傳結果與 purchase() 相同,皆透過 purchases_updatedpurchase_error 訊號傳回。updateSubscription() 共需三個參數:

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

  2. 要更換為的新訂閱 SKU 產品 ID

  3. 訂閱的比例分攤(proration)模式。

比例分攤模式定義如下:

enum SubscriptionProrationMode {
    # Replacement takes effect immediately, and the remaining time
    # will be prorated and credited to the user.
    IMMEDIATE_WITH_TIME_PRORATION = 1,
    # Replacement takes effect immediately, and the billing cycle remains the same.
    # The price for the remaining period will be charged.
    # This option is only available for subscription upgrade.
    IMMEDIATE_AND_CHARGE_PRORATED_PRICE,
    # Replacement takes effect immediately, and the new price will be charged on
    # next recurrence time. The billing cycle stays the same.
    IMMEDIATE_WITHOUT_PRORATION,
    # Replacement takes effect when the old plan expires, and the new price
    # will be charged at the same time.
    DEFERRED,
    # 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.
    IMMEDIATE_AND_CHARGE_FULL_PRICE,
}

預設行為為 IMMEDIATE_WITH_TIME_PRORATION

updateSubscription() 的使用範例:

payment.updateSubscription(_active_subscription_purchase.purchase_token, \
                    "new_sub_sku", SubscriptionProrationMode.IMMEDIATE_WITH_TIME_PRORATION)

confirmPriceChange() 函式可用於啟動訂閱價格調整的確認流程。請傳入需調整價格的訂閱 SKU 產品 ID。結果會透過 price_change_acknowledged 訊號傳回。

confirmPriceChange() 的使用範例:

enum BillingResponse {SUCCESS = 0, CANCELLED = 1}

func confirm_price_change(product_id):
    payment.confirmPriceChange(product_id)

func _on_price_acknowledged(response_id):
    if response_id == BillingResponse.SUCCESS:
        print("price_change_accepted")
    elif response_id == BillingResponse.CANCELED:
        print("price_change_canceled")