Внутриигровые покупки на платформе Android
Godot предлагает собственный плагин для Android GodotGooglePlayBilling, совместимый с Godot 4.2+, который использует Google Play Billing library.
Использование
Начало работы
Убедитесь, что вы включили и успешно настроили Android Gradle Builds. Следуйте инструкциям по установке на странице GodotGooglePlayBilling на Github.
Инициализируйте плагин
Для использования GodotGooglePlayBilling API:
Access the
BillingClient.Подключитесь к его сигналам, чтобы получать результаты биллинга.
Вызовите
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(). Необходимо успешно выполнить запрос сведений о продукте перед вызовом функций purchase(), purchase_subscription() или update_subscription(), иначе будет возвращена ошибка. Функция query_product_details() принимает два параметра: массив строк ID продуктов и тип запрашиваемого продукта. Тип продукта должен быть BillingClient.ProductType.INAPP для обычных покупок в приложении или BillingClient.ProductType.SUBS для подписок. Строки ID в массиве должны соответствовать идентификаторам продуктов, указанным в записи Google Play Console для вашего приложения.
Пример использования 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 для обычных покупок в приложении (app) или 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)
Купить товар (item)
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)
Обработка покупки товара (purchase item)
Сигналы query_purchases_response и on_purchases_updated предоставляют массив покупок в формате Dictionary. Словарь покупок содержит ключи, которые соответствуют значениям класса Purchase в Google Play Billing.
Поля покупки:
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 Library.
Consumables (Расходные материалы)
Если ваш внутриигровой предмет не является одноразовой покупкой, а является расходуемым предметом (например, монетами (coins)), который можно приобрести несколько раз, вы можете использовать предмет, вызвав 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
Подписки
Подписки (Subscriptions) работают практически так же, как и обычные товары в приложении. Используйте BillingClient.ProductType.SUBS в качестве второго аргумента query_product_details() для получения информации о подписке. Передайте BillingClient.ProductType.SUBS в query_purchases() для получения информации о покупке подписки.
Вы можете проверить is_auto_renewing в покупке подписки, возвращенной query_purchases(), чтобы узнать, отменил ли пользователь автоматически продлеваемую подписку.
Вам необходимо подтверждать новые покупки подписки, но не автоматическое продление подписки.
Если вы поддерживаете повышение или понижение уровня подписки, используйте update_subscription(), чтобы использовать поток обновления подписки для изменения активной подписки. Как и purchase(), результаты возвращаются сигналом on_purchases_updated. Параметры update_subscription():
old_purchase_token: Токен покупки текущей активной подписки
replacement_mode: режим замены, применяемый к подписке
product_id: ID продукта новой подписки для переключения
base_plan_id: ID базового плана целевой подписки
offer_id: ID предложения в рамках базового плана (необязательно)
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")