Внутриигровые покупки на платформе Android

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

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

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

Убедитесь, что вы включили и успешно настроили Android Gradle Builds. Следуйте инструкциям по установке на странице GodotGooglePlayBilling на Github.

Инициализируйте плагин

Для использования GodotGooglePlayBilling API:

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