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 предлагает собственный плагин для Android GodotGooglePlayBilling, совместимый с Godot 4.2+, который использует Google Play Billing library.

Использование

Начало работы

Убедитесь, что вы включили и успешно настроили Android Gradle Builds. Следуйте инструкциям по установке на странице 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(). Необходимо успешно выполнить запрос сведений о продукте перед вызовом функций 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():

  1. old_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")