покупки через програму Android

Godot пропонує власний плагін GodotGooglePlayBilling для Android, сумісний із Godot 4, який використовує бібліотеку Google Play Billing.

Використання

Перші кроки

Переконайтеся, що ви ввімкнули й успішно налаштували Android Gradle Builds. Дотримуйтесь інструкцій щодо компіляції на сторінці Github GodotGooglePlayBilling <https://github.com/godotengine/godot-google-play-billing>`__.

Потім додайте файли ./godot-google-play-billing/build/outputs/aar/GodotGooglePlayBilling.***.release.aar і ./GodotGooglePlayBilling.gdap у свій проект у res://android папка /plugins.

Тепер плагін має з’явитися в налаштуваннях експорту Android, де його можна ввімкнути.

Ініціалізуйте плагін

Щоб використовувати API GodotGooglePlayBilling:

  1. Отримайте посилання на синглтон GodotGooglePlayBilling

  2. Підключіть обробники для сигналів плагіна

  3. Викличте 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 надішліть запит до SKU за допомогою querySkuDetails(). Ви повинні успішно виконати запит SKU перед викликом функцій purchase() або queryPurchases(), інакше вони повернуть помилку. querySkuDetails() приймає два параметри: масив рядків імен SKU та рядок, що вказує тип SKU, який запитується. Рядок типу SKU має бути "inapp" для звичайних покупок у програмі або "subs" для підписок. Рядки імен у масиві мають збігатися з ідентифікаторами продуктів SKU, визначеними в записі Google Play Console для вашої програми.

Приклад використання 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)

Запит на покупки користувачів і

Щоб отримати покупки користувача, викличте функцію queryPurchases(), передаючи рядок із типом SKU для запиту. Рядок типу SKU має бути "inapp" для звичайних покупок у програмі або "subs" для підписок. З результатом надсилається сигнал query_purchases_response. Сигнал має єдиний параметр: Dictionary з кодом статусу та або масивом покупок, або повідомленням про помилку. Лише активні підписки та невикористані одноразові покупки включені в масив покупок.

Приклад використання 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, який ви бажаєте придбати. Нагадування: ви повинні запитати деталі 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 Billing 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,
}

Якщо покупка перебуває в стані ОЧІКУВАННЯ, вам не слід присуджувати вміст покупки або виконувати будь-яку подальшу обробку покупки, доки вона не досягне стану ПРИДБАНО. Якщо у вас є інтерфейс магазину, ви можете відобразити інформацію про незавершені покупки, які потрібно завершити в магазині Google Play. Додаткову інформацію про незавершені покупки див. у розділі «Обробка незавершених транзакцій <https://developer.android.com/google/play/billing/integrate#pending>`_ у документації бібліотеки платежів 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(), щоб отримати деталі підписки. Передайте "subs" в queryPurchases(), щоб отримати деталі покупки підписки.

Ви можете перевірити is_auto_renewing у покупці підписки, повернутій queryPurchases(), щоб побачити, чи користувач скасував підписку з автоматичним поновленням.

Вам потрібно підтверджувати придбання нових підписок, але не автоматичне поновлення підписки.

Якщо ви підтримуєте оновлення або пониження між різними рівнями підписки, вам слід використовувати updateSubscription(), щоб використовувати потік оновлення підписки для зміни активної підписки. Як і purchase(), результати повертаються сигналами purchases_updated і purchase_error. Є три параметри для updateSubscription():

  1. Маркер покупки поточної активної підписки

  2. Рядок ідентифікатора продукту SKU підписки, на який потрібно змінити

  3. Режим пропорції для підписки.

Значення пропорції визначаються як:

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 підписки, що залежить від зміни ціни. Результат буде надіслано сигналом 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")