Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Android 应用内购买

Godot 提供了一个官方的 GodotGooglePlayBilling 插件,兼容 Godot 4.2 及以上版本,该插件使用 Google Play Billing library

用法

新手入门

确保你已启用并成功设置 Android Gradle 构建。按照 GodotGooglePlayBilling github 页面上的安装说明进行操作。

初始化插件

要使用 GodotGooglePlayBilling API:

  1. 访问 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 中为你的应用定义的产品 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)

购买项目

要发起某个商品的购买流程:对于应用内商品,使用 purchase(),传入产品 ID 字符串。对于订阅,使用 purchase_subscription(),传入产品 ID 和基础订阅方案 ID。你还可以选择性地提供优惠 ID。

对于 purchase()purchase_subscription() 方法,你可选择传入布尔值参数以指示是否启用`个性化定价 <https://developer.android.com/google/play/billing/integrate#personalized-price>`_

提醒:你 必须 在将商品传递给 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 格式表示的购买数组。购买的 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 结算库文档中的处理待处理交易

消耗品

如果你的应用内商品不是一次性购买的商品,而是可消耗商品(例如,金币),可以通过调用 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 会撤销购买。如果你调用了 comsume_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

订阅

订阅的工作方式与普通应用内商品类似。使用 BillingClient.ProductType.SUBS 作为第二个参数传递给 query_product_details() 以获取订阅详情。将 BillingClient.ProductType.SUBS 传递给 query_purchases() 以获取订阅购买详情。

你可以检查从 query_purchases() 返回的订阅购买中的 is_auto_renewing,以查看用户是否取消了自动续订的订阅。

你需要确认新的订阅购买,但不需要确认自动订阅续订。

如果你支持在不同订阅级别之间进行升级或降级,应该使用 update_subscription() 来通过订阅更新流程更改活跃的订阅。与 purchase() 一样,结果通过 on_purchases_updated 信号返回。以下是 update_subscription() 的参数:

  1. old_purchase_token:当前活跃订阅的购买令牌(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")