Android 应用内购买
Godot 提供与 Godot 4 兼容的第一方 GodotGooglePlayBilling
Android 插件,该插件使用 Google Play 结算系统库。
用法
入门
确保你已启用并成功设置 Android Gradle 构建。按照 GodotGooglePlayBilling
github 页面上的编译说明进行操作。
然后将文件 ./godot-google-play-billing/build/outputs/aar/GodotGooglePlayBilling.***.release.aar 和 ./GodotGooglePlayBilling.gdap 放入项目中的 res://android/plugins 文件夹中。
现在 Android 导出设置中就会显示该插件,你可以在那里启用该插件。
初始化插件
要使用 GodotGooglePlayBilling
API:
获取对
GodotGooglePlayBilling
单例的引用为该插件的信号连接处理函数
调用
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 控制台条目中定义的 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)
查询用户购买记录
To retrieve a user's purchases, call the queryPurchases()
function passing
a string with the type of SKU to query. The SKU type string should be
"inapp"
for normal in-app purchases or "subs"
for subscriptions.
The query_purchases_response
signal is sent with the result.
The signal has a single parameter: a Dictionary with
a status code and either an array of purchases or an error message.
Only active subscriptions and non-consumed one-time purchases are
included in the purchase array.
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 详细信息后,你应在启动期间查询购买记录。由于用户可能会从应用外部进行购买或解决待处理交易,因此你应在从后台恢复时重新检查购买记录。为此,你可以使用 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_response
和 purchases_updated
信号以 Dictionary 格式提供购买数组。购买字典中包含映射到 Google Play 支付系统 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 结算库文档中的处理待处理交易。
消耗品
如果你的应用内物品不是一次性购买,而是可以多次购买的消耗品(如硬币),你可以通过调用 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 会撤销购买。如果你调用 comsumePurchase()
,它会自动确认购买,且无需调用 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
订阅
订阅的工作原理和普通的应用内项目没有太大区别. 只要使用 "subs"
作为 querySkuDetails()
的第二个参数, 就可以得到订阅的详细信息. 在 queryPurchases()
的结果中检查 is_auto_renewing
来查看用户是否取消了自动更新的订阅。
你可以在 queryPurchases()
返回的订阅购买中检查 is_auto_renewing
,以查看用户是否取消了自动续订订阅。
你需要确认新的订阅购买,但不需要确认自动订阅续订。
如果你支持在不同订阅级别之间升级或降级,则应使用 updateSubscription()
来使用订阅更新流程,以更改有效订阅。与 purchase()
一样,结果由 purchases_updated
和 purchase_error
信号返回。updateSubscription()
有三个参数:
当前有效订阅的购买令牌
要更改到的订阅 SKU 的产品 ID 字符串
适用于该订阅的比例分摊模式。
比例分摊的值定义为:
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")