Перейти к основному содержимому

7.0.0

RuStore позволяет интегрировать платежи в мобильное приложение.

подсказка

Если не знаете с чего начать, прочтите инструкцию в сценариях использования.

Пример реализации

Ознакомьтесь с приложением-примером чтобы узнать, как правильно интегрировать SDK платежей.

Подробнее о приёме платежей без установки RuStore читайте здесь.

Условия работы платежей

  • Приложение загружено в Консоль RuStore.
  • Приложение прошло модерацию (публиковать приложение необязательно).
Важно
  • Подпись тестируемой сборки (например, debug) приложения должна совпадать с подписью сборки приложения, которая была загружена в консоль и прошла модерацию ранее (например, release).
  • Пользователь и приложение не должны быть заблокированы в RuStore.
  • Для приложения включена возможность покупок в RuStore Консоли.
предупреждение

Сервис имеет некоторые ограничения на работу за пределами России.

Подготовка к работе

Добавление репозитория

Подключите репозиторий, как показано в примере ниже.

build.gradle
repositories {
maven {
url = uri("https://artifactory-external.vkpartner.ru/artifactory/maven")
}
}

Подключение зависимости

Добавьте следующий код в свой конфигурационный файл для подключения зависимости.

Преимущества использования BOM-файла для конфигурации.

  1. Единое управление версиями:

    • С BOM вы можете управлять версиями всех зависимостей из одного файла. Это особенно полезно, если вы используете несколько библиотек, которые должны быть совместимы друг с другом.
    • Например, если у вас есть несколько библиотек от RuStore, таких как ru.rustore.sdk:billingclient и ru.rustore.sdk:pushclient, вы можете использовать BOM, чтобы гарантировать, что все они будут совместимы друг с другом.
  2. Упрощение обновлений:

    • Обновление зависимостей становится проще, так как вам нужно изменить версию только в одном месте — в BOM-файле. Это снижает риск пропустить обновление какой-либо зависимости и избежать конфликтов версий.
    • Например, если новая версия BOM-файла содержит обновленные версии всех библиотек, вам достаточно обновить только BOM-файл, а не каждую зависимость по отдельности.
  3. Повышение совместимости:

    • Использование BOM помогает избежать конфликтов версий между различными библиотеками. Это особенно важно, когда библиотеки имеют зависимости друг от друга.
    • Например, если две библиотеки зависят от разных версий одной и той же библиотеки, это может вызвать конфликты. BOM помогает избежать таких ситуаций, гарантируя, что все зависимости совместимы.
build.gradle
dependencies {
implementation(platform("ru.rustore.sdk:bom:7.0.0"))
implementation("ru.rustore.sdk:billingclient")
}

Deeplink в RuStore SDK платежей нужна для корректной работы со сторонними приложениями оплаты. Она помогает пользователям быстрее совершать покупки в стороннем приложении и возвращаться в ваше приложение.

Для настройки работы с deeplink в вашем приложении и RuStore SDK, укажите deeplinkScheme внутри вашего AndroidManifest файла и переопределите метод onNewIntent вашего Activity.

AndroidManifest.xml
<activity
android:name=".YourBillingActivity">

<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourappscheme" />
</intent-filter>

</activity>

Вместо yourappscheme из примера выше укажите название своей схемы. Например, ru.package.name.rustore.scheme.

к сведению

Схема, указанная в AndroidManifest файле должна совпадать со схемой, которую вы указываете в методе create RuStore SDK платежей.

Далее в Activity, в которую необходимо вернуться после совершения оплаты (ваша страница магазина), нужно добавить следующий код.

class YourBillingActivity: AppCompatActivity() {

// Previously created with RuStoreBillingClientFactory.create()
private val billingClient: RuStoreBillingClient = YourDependencyInjection.getBillingClient()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
billingClient.onNewIntent(intent)
}
}

override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
billingClient.onNewIntent(intent)
}
}

Для восстановления состояния вашего приложения при возврате с deeplink добавьте в AndroidManifest.xml android:launchMode="singleTask".

AndroidManifest.xml
<activity
android:name=".YourBillingActivity"
android:launchMode="singleTask"
android:exported="true"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">

Инициализация

Перед вызовом методов библиотеки необходимо выполнить её инициализацию.

Создайте RuStoreBillingClient, используя RuStoreBillingClientFactory.create().

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
// Опциональные параметры
themeProvider = null,
debugLogs = false,
externalPaymentLoggerFactory = null,
)
  • context — это ключевой элемент, который предоставляет информацию о текущем состоянии приложения или объекта.
    к сведению

    В Android существует несколько типов контекста:

    • Контекст приложения — это singleton-экземпляр, доступный через getApplicationContext().
    • Контекст Activity — доступен внутри Activity и привязан к её жизненному циклу.
    • Контекст в ContentProvider — доступен через метод getContext() и аналогичен контексту приложения.

    Для создания RuStoreBillingClient предпочтительнее использовать applicationContext.

  • consoleApplicationId — идентификатор приложения из консоли RuStore.

Где в RuStore Консоль отображаются идентификаторы приложений?
  1. Перейдите на вкладку Приложения и выберите нужное приложение.
  2. Скопируйте идентификатор из URL-адреса страницы приложения — это набор цифр между apps/ и /versions. Например, для URL-адреса https://console.rustore.ru/apps/123456/versions ID приложения — 123456.

примечание

ApplicationId, указанный в build.gradle, должен совпадать с applicationId APK-файла, который вы публиковали в RuStore Консоль.

  • deeplinkScheme — схема deeplink, необходимая для возврата в ваше приложение после оплаты через стороннее приложение (например, SberPay или СБП). SDK генерирует свой хост к данной схеме.
примечание
Схема deeplink, передаваемая в yourappscheme, должна совпадать со схемой, указанной в AndroidManifest.xml (подробнее см. Обработка deeplink).
  • themeProvider — интерфейс, который предоставляет тему BillingClientTheme. Возможны 2 реализации темы BillingClientTheme: светлая (Light) и тёмная (Dark). Необязательный интерфейс, по умолчанию применяется светлая тема.

    img img
    примечание

    По умолчанию тема будет светлой (Light), но вы можете настроить и тёмную (Dark).

  • externalPaymentLoggerFactory — интерфейс, позволяющий вести журнал событий.
    подсказка

    Журнал событий полезен для версий, которые ещё не опубликованы на пользователей. С его помощью можно отследить возникающие ошибки.

  • debugLogs — флаг, регулирующий ведение журнала событий. Укажите значение true, если хотите, чтобы события попадали в журнал. В ином случае укажите false.
примечание

Подпись keystore должна совпадать с подписью, которой было подписано приложение, опубликованное в RuStore Консоль. Убедитесь, что используемый buildType (пр. debug) использует такую же подпись, что и опубликованное приложение (пр. release).

Как работают платежи

предупреждение

На схеме ниже показан примерный алгоритм настройки и подключения платежей, на который вы можете ориентироваться. Учитывайте особенности вашего проекта в работе.

Приложение для оплаты RuStore Сервер RuStore_Billing_Client Ваш сервер Ваше приложение Покупатель Приложение для оплаты RuStore Сервер RuStore_Billing_Client Ваш сервер Ваше приложение ПокупательПроверка доступности работы платежей [Необязательно] Серверная валидация платежа [Обязательно] Покупка потребляемого товара [Обязательно] Обработка deeplink при оплате СБП, SberPay и т.д.Покупка продукта Входит в ваше приложение checkPurchasesAvailability РезультатgetProducts Список продуктов вашего приложения Показ списка доступных покупок Приобретение товара purchaseProduct Запрос способа оплаты Способ оплаты Проведение платежа Результат проведения платежа Callback о платеже Серверная валидация (public API) Полная информация о покупке Начисление товара покупателю confirmPurchase Результат потребления Начисление товара покупателюpurchaseProduct Запрос способа оплаты Указал СБП/SberPay/T-Pay Запуск процесса оплаты Сценарий оплаты Оплата покупки Возврат в приложение OnNewIntent Результат оплаты Отображение платёжной шторки с результатом оплаты

Проверка доступности работы с платежами

Для проверки доступности платежей используйте метод checkPurchasesAvailability.

к сведению

Начиная с версии 6.0.0 процедура и результат проверки доступности платежей зависит от наличия RuStore на устройстве пользователя.

Подробнее см. ниже.

Наличие RuStoreПроцедура и результат проверки
Не установлен

Метод вернёт FeatureAvailabilityResult.Available — платежи доступны. Это возможно благодаря запуску приёма платежей без установки RuStore.

Установлен

Выполняется проверка следующих условий.

  • На устройстве пользователя установлена актуальная версия RuStore.
  • Приложение RuStore поддерживает функциональность платежей.
  • Пользователь авторизован в RuStore.
  • Пользователь и приложение не должны быть заблокированы в RuStore.
  • Для приложения включена возможность покупок в RuStore Консоли.
Если все указанные выше условия выполняются, возвращается FeatureAvailabilityResult.Available.

В противном случае возвращается FeatureAvailabilityResult.Unavailable(val cause: RuStoreException), где cause — это ошибка о невыполненном условии.

Все возможные ошибки RuStoreException описаны в разделе Обработка ошибок. Прочие ошибки возвращаются в onFailure. (См. Task API).

предупреждение

Таким образом, положительный ответ вернётся в двух случаях:

  • если RuStore не установлен;
  • если RuStore установлен и установленный экземпляр RuStore на устройстве пользователя соответствует проверяемым условиям.

Сам по себе ответ метода checkPurchasesAvailability не показывает, установлен ли RuStore на устройстве пользователя. Вы можете проверить наличие RuStore на устройстве пользователя с помощью метода isRuStoreInstalled.

Ниже представлен пример использования метода checkPurchasesAvailability.

RuStoreBillingClient.checkPurchasesAvailability()
.addOnSuccessListener { result ->
when (result) {
FeatureAvailabilityResult.Available -> {
// Process purchases available
}

is FeatureAvailabilityResult.Unavailable -> {
// Process purchases unavailable
}
}
}
.addOnFailureListener { throwable ->
// Process unknown error
}

В данной версии SDK метод будет возвращать сообщение о доступности платежа.

Работа с SDK

Проверка наличия RuStore на устройстве

Метод isRuStoreInstalled применяется для проверки, установлен ли RuStore на устройстве пользователя. Эта проверка нужна для реализации корректной логики работы с товарами и покупками. Приведённые ниже методы SDK платежей требуют авторизации пользователя:

Если на устройстве пользователя не установлен RuStore, всякий раз будет отображаться шторка веб-авторизаци, что может негативно сказаться на пользовательском опыте. Таким образом, если проверка показывает, что приложение RuStore устройстве пользователя не установлено, имеет смысл сократить количество запросов, которые требуют авторизации (подробнее см. в разделе Приём платежей без установки RuStore).

public fun isRuStoreInstalled(context: Context): Boolean

Получение списка продуктов

Вы проверили, что платежи доступны и пользователи могут совершать покупки. Теперь можно получить список продуктов. Используйте метод getProducts, чтобы получить информацию о продуктах, добавленных в ваше приложение через RuStore Консоль.
val productsUseCase: ProductsUseCase = billingClient.products
productsUseCase.getProducts(productIds = listOf("id1", "id2"))
.addOnSuccessListener { products: List<Product> ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

productIds — список идентификаторов продуктов. В нём не должно быть более 100 позиций.

Чтобы указать id продуктов, которые нужны для работы метода, выполните следующие действия.

  1. Откройте RuStore Консоль.
  2. Перейдите на вкладку Приложения.
  3. Выберите нужное приложение.
  4. В левом боковом меню выберите раздел Монетизация.
  5. Выберите тип товара: Подписки или Разовые покупки.
  6. Скопируйте идентификаторы нужных товаров. Это и есть id продуктов.

Максимальная длина — 2083 символа в списке.

Метод возвращает

data class Product(
val productId: String,
val productType: ProductType?,
val productStatus: ProductStatus,
val priceLabel: String?,
val price: Int?,
val currency: String?,
val language: String?,
val title: String?,
val description: String?,
val imageUrl: Uri?,
val promoImageUrl: Uri?,
val subscription: ProductSubscription?,
)

Структура продукта

  • productId — идентификатор продукта, который был присвоен продукту в RuStore Консоли (обязательный параметр).
  • productType — тип продукта (потребляемый / непотребляемый / подписка): CONSUMABLE/NON-CONSUMABE/SUBSCRIPTION.
  • productStatus — статус продукта.
  • priceLable — отформатированная цена товара, включая валютный знак на языке language.
  • price — цена в минимальных единицах (в копейках).
  • currency — код валюты ISO 4217.
  • language — язык, указанный с помощью BCP 47 кодирования.
  • title — название продукта на языке language.
  • description — описание на языке language.
  • imageUrl — ссылка на картинку.
  • promoImageUrl — ссылка на промокартинку.
  • subscription — описание подписки, возвращается только для продуктов с типом subscription.

Структура подписки

data class ProductSubscription(
val subscriptionPeriod: SubscriptionPeriod?,
val freeTrialPeriod: SubscriptionPeriod?,
val gracePeriod: SubscriptionPeriod?,
val introductoryPrice: String?,
val introductoryPriceAmount: String?,
val introductoryPricePeriod: SubscriptionPeriod?
)
  • subscriptionPeriod — период подписки.
  • freeTrialPeriod — пробный период подписки.
  • gracePeriod — льготный период подписки.
  • introductoryPrice — отформатированная вступительная цена подписки, включая знак валюты, на языке product:language.
  • introductoryPriceAmount — вступительная цена в минимальных единицах валюты (в копейках).
  • introductoryPricePeriod — расчётный период вступительной цены.

Структура периода подписки

data class SubscriptionPeriod(
val years: Int,
val months: Int,
val days: Int,
)
  • years — количество лет.
  • months — количество месяцев.
  • days — количество дней.

Покупка продукта

Для вызова покупки продукта используйте метод purchaseProduct.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.purchaseProduct(
productId = productId,
orderId = UUID.randomUUID().toString(),
quantity = 1,
developerPayload = null,
).addOnSuccessListener { paymentResult: PaymentResult ->
when (paymentResult) {
// Process PaymentResult
}
}.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • productId: String — идентификатор продукта, который был присвоен продукту в RuStore Консоли (обязательный параметр).
  • orderId: String — уникальный идентификатор оплаты, сформированный приложением (опциональный параметр). Если вы укажете этот параметр в вашей системе, вы получите его в ответе при работе с API. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.
  • quantity: Int — количество продукта (необязательный параметр — если не указывать, будет подставлено значение 1).
  • developerPayload — строка с дополнительной информацией о заказе, которую вы можете установить при инициализации процесса покупки.

Структура результата покупки

public sealed interface PaymentResult {     

public data class Success(
val orderId: String?,
val purchaseId: String,
val productId: String,
val invoiceId: String,
val sandbox: Boolean,
val subscriptionToken: String? = null,
) : PaymentResult

public data class Cancelled(
val purchaseId: String,
val sandbox: Boolean,
) : PaymentResult

public data class Failure(
val purchaseId: String?,
val invoiceId: String?,
val orderId: String?,
val quantity: Int?,
val productId: String?,
val sandbox: Boolean,
val errorCode: Int?,
) : PaymentResult

public object InvalidPaymentState : PaymentResult()
}
к сведению

Параметр sandbox определяет, является ли платёж тестовым. Значения могут быть true или false, где true обозначает тестовый платёж, а false – реальный.

  • Success — результат успешного завершения покупки цифрового товара.
  • Failure — при отправке запроса на оплату или получения статуса оплаты возникла проблема, невозможно установить статус покупки.
  • Cancelled — запрос на покупку отправлен, при этом пользователь закрыл «платёжную шторку» на своём устройстве, и результат оплаты неизвестен.
  • InvalidPaymentState — ошибка работы SDK платежей. Может возникнуть, в случае некорректного обратного deeplink.

Получение сведений о покупке

Для получения информации о покупке, используйте метод getPurchaseInfo.
val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchaseInfo("purchaseId")
.addOnSuccessListener { purchase: Purchase ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

Структура покупки

data class Purchase(
val purchaseId: String?,
val productId: String,
val productType: ProductType?,
val invoiceId: String?,
val language: String?,
val purchaseTime: Date?,
val orderId: String?,
val amountLabel: String?,
val amount: Int?,
val currency: String?,
val quantity: Int?,
val purchaseState: PurchaseState?,
val developerPayload: String?,
val subscriptionToken: String?
)
  • purchaseId — идентификатор покупки.

  • productId — идентификатор продукта, который был присвоен продукту в RuStore Консоли (обязательный параметр).

  • invoiceId — идентификатор счёта.

  • language — язык, указанный с помощью BCP 47 кодирования.

  • purchaseTime — время покупки.

  • orderId — уникальный идентификатор оплаты, сформированный приложением (опциональный параметр). Если вы укажете этот параметр в вашей системе, вы получите его в ответе при работе с API. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.

  • amountLable — отформатированная цена покупки, включая валютный знак.

  • amount — цена в минимальных единицах валюты.

  • currency — код валюты ISO 4217.

  • quantity — количество продукта (необязательный параметр — если не указывать, будет подставлено значение 1).

  • purchaseState — состояние покупки:

    • CREATED — покупка создана;
    • INVOICE_CREATED — создан счёт на оплату, покупка ожидает оплаты;
    • PAID — только покупки потребляемого товара — промежуточный статус, средства на счёте покупателя зарезервированы. Покупка ожидает подтверждения от разработчика;
    • CONFIRMED — платеж за непотребляемый товар успешно совершен;
    • CONSUMED — платеж за потребляемый товар успешно совершен;
    • CANCELLED — покупка отменена — оплата не была произведена или был совершен возврат средств покупателю (для подписок после возврата средств покупка не переходит в CANCELLED);
    • PAUSED — для подписок — подписка перешла в HOLD период;
    • TERMINATED — подписка закрылась.
  • developerPayload — строка с дополнительной информацией о заказе, которую вы можете установить при инициализации процесса покупки.

  • subscriptionToken — токен для валидации покупки на сервере.

Статусная модель (purchaseState)

Статусная модель покупки потребляемых продуктов (CONSUMABLES)

Покупка создана, но счёт на оплату ещё не созданОплаты еще не было. Можно игнорировать такую покупку.У пользователя есть незавершенная покупка. Счёт создан, но не оплачен. Необходимо предупредить пользователя.Покупка отмененаДля продуктов типа consumable. Покупка оплачена, но не потреблена. Апу Арр должен немедленно отправить запрос на приобретение покупки.Для продуктов типа consumable. Потребление покупки было подтверждено.CREATEDINVOICE_CREATEDСANCELLEDСчёт созданСчёт оплаченПотреблениеPAIDCONSUMEDСчёт отменёнВозврат по счётуПокупка отменена / не потреблена в течение 72 часовПокупка отменена

Статусная модель покупки непотребляемых продуктов (NON-CONSUMABLES)

Покупка создана, но счёт на оплату еще не создан. Оплаты еще не было. Можно игнорировать такую покупку.У пользователя есть незавершенная покупка. Счёт создан, но не оплачен. Необходимо предупредить пользователя.Покупка отмененаСтатус завершения покупки для продуктов типа non-consumable или subscription. Означает, что товар уже приобретен, повторная попытка покупки приведет к ошибке.CREATEDINVOICE_CREATEDСANCELLEDСоздание счётаПокупка подтвержденаCONFIRMEDСчёт отменёнВозврат по счётуОтмена покупки

Статусная модель покупки подписок (SUBSCRIPTIONS)

Покупка создана, но счёт на оплату еще не создан. Оплаты еще не было. Можно игнорировать такую покупку.Подписка в HOLD периоде. Повторная попытка покупки приведёт к ошибке.У пользователя есть незавершенная покупка. Счёт создан, но не оплачен. Необходимо предупредить пользователя.Покупка отмененаПодписка активна или в GRACE периоде. Повторная попытка покупки приведёт к ошибке.Срок действия подписки истёк. Доступна повторная �покупка.CREATEDINVOICE_CREATEDСANCELLEDPAUSEDСоздание счётаПокупка подтвержденаПодписка закрытаCONFIRMEDTERMINATEDСчёт отменёнОшибка оплаты очередногоплатежа по подпискеПодписка оплаченаПодписка закрытаОтмена покупки

Получение списка покупок

Метод возвращает только покупки со статусами из таблицы ниже. Подробнее о других возможных состояниях покупки смотрите в разделе Получение сведений о покупке.

Тип/СтатусINVOICE_CREATEDCONFIRMEDPAIDPAUSED
CONSUMABLE++
NON-CONSUMABLE++
SUBSCRIPTION+++
примечание

Метод возвращает незавершённые состояния покупки и покупки потребляемых товаров, требующих обработки. Помимо этого, он показывает подтверждённые покупки для подписок и непотребляемых товаров — тех, которые нельзя купить повторно.

Для получения списка покупок пользователя используйте метод getPurchases.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchases()
.addOnSuccessListener { purchases: List<Purchase> ->
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}

Структура покупки

data class Purchase(
val purchaseId: String?,
val productId: String,
val productType: ProductType?,
val invoiceId: String?,
val language: String?,
val purchaseTime: Date?,
val orderId: String?,
val amountLabel: String?,
val amount: Int?,
val currency: String?,
val quantity: Int?,
val purchaseState: PurchaseState?,
val developerPayload: String?,
val subscriptionToken: String?
)
  • purchaseId — идентификатор покупки.
  • productId — идентификатор продукта, который был присвоен продукту в RuStore Консоли (обязательный параметр).
  • productType — тип продукта (потребляемый / непотребляемый / подписка): CONSUMABLE/NON-CONSUMABE/SUBSCRIPTION.
  • invoiceId — идентификатор счёта.
  • language — язык, указанный с помощью BCP 47 кодирования.
  • purchaseTime — время покупки.
  • orderId — уникальный идентификатор оплаты, сформированный приложением (опциональный параметр). Если вы укажете этот параметр в вашей системе, вы получите его в ответе при работе с API. Если не укажете, он будет сгенерирован автоматически (uuid). Максимальная длина 150 символов.
  • amountLable — отформатированная цена покупки, включая валютный знак.
  • amount — цена в минимальных единицах валюты.
  • currency — код валюты ISO 4217.
  • quantity — количество продукта (необязательный параметр — если не указывать, будет подставлено значение 1).
  • purchaseState — состояние покупки.

Структура ответа сервера на запрос покупок

data class PurchasesResponse(
override val meta: RequestMeta?,
override val code: Int,
override val errorMessage: String?,
override val errorDescription: String?,
override val errors: List<DigitalShopGeneralError>?,
val purchases: List<Purchase>?,
) : ResponseWithCode
  • meta — дополнительная информация о запросе.
  • code — код ответа.
  • errorMessage — сообщение об ошибке для пользователя.
  • errorDescription — расшифровка сообщения об ошибке.
  • errors — список ошибок для запрошенных продуктов.
  • purchases — список запрошенных покупок.

Валидация покупки на сервере

Если вам необходимо произвести валидацию успешной покупки на сервере методами API RuStore, вы можете использовать subscriptionToken в PurchaseResult, возвращаемой purchaseProduct при успешной покупке продукта.

SubscriptionToken состоит из invoiceId покупки и userId, записанных через точку: $invoiceId.$userId.

Также можно получить subscriptionToken в сущности Purchase. Сущность Purchase можно получить используя метод getPurchases.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.purchaseProduct(productId).addOnSuccessListener { paymentResult ->
if (paymentResult is PaymentResult.Success) {
val subscriptionToken = paymentResult.subscriptionToken
yourApi.validate(subscriptionToken)
}
}

Также можно получить subscriptionToken в сущности Purchase. Сущность Purchase можно получить используя метод getPurchases.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.getPurchases().addOnSuccessListener { purchases ->
purchases.forEach { purchase ->
yourApi.validate(purchase.subscriptionToken)
}
}

Потребление (подтверждение) покупки

Продукты, требующие потребления (подтверждения)

Учитывайте тип покупки. Метод потребления (подтверждения) необходим, только если у вас потребляемый товар (CONSUMABLE), который можно купить много раз.

Чтобы такие товары начислились пользователям без ошибок, подтвердите потребление (подтверждение) продукта с помощью метода confirmPurchase. При начислении товара в вашем приложении используйте серверную валидацию платежей. Начисляйте товар только когда платёж (счёт) перейдет в финальный статус CONFIRMED. Начисление продуктов пользователям надо делать в callback addOnSuccessListener метода confirmPurchase.

Обратите внимание!

Статус PAID является промежуточным и означает, что средства пользователя захолдированы на карте и вам нужно подтвердить покупку.

Исключение составляют платежи через СБП или мобильное списание — см. пояснения ниже.

При оплате потребляемых (CONSUMABLE) товаров через СБП или мобильное списание используется одностадийный платёж, при этом модель счёта остаётся двухстадийной. Это значит, что при переходе счёта в статус PAID при оплате через СБП или мобильное списание деньги уже списаны со счёта покупателя, а с разработчика удержана комиссия. В этом случае при отмене покупки в состоянии PAID происходит возврат средств (refund), а не отмена холдирования — reverse. Удержанная комиссия разработчику не возвращается. При этом для завершения покупки всё равно нужно выполнить метод подтверждения (потребления) — см. также таблицу ниже.

Платёжный методТип платежаПлатёж в статусе PAID
  • банковские карты;
  • Сбер ID;
  • SberPay;
  • T-Pay;
  • VK Pay.
Двухстадийный
  • Средства захолдированы на счёте покупателя.
  • Комиссия с разработчика не удержана.
  • Возможна отмена платежа.
  • СБП;
  • мобильное списание.
Одностадийный
  • Средства списаны со счёта покупателя.
  • Удержана комиссия с разработчика.
  • При отмене покупки в состоянии PAID происходит возврат средств (refund), а не отмена холдирования — reverse. Удержанная комиссия разработчику не возвращается.

Вызов метода потребления (подтверждения)

Для потребления (подтверждения) покупки используйте метод confirmPurchase. Запрос на потребление (подтверждение) покупки должен сопровождаться выдачей товара. После вызова подтверждения покупка перейдёт в статус CONSUMED.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.confirmPurchase(purchaseId = "purchaseId", developerPayload = null)
.addOnSuccessListener {
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • purchaseId — идентификатор покупки.
  • developerPayload — строка с дополнительной информацией о заказе, которую вы можете установить при инициализации процесса покупки.

Отмена покупки

Для отмены покупки используйте метод deletePurchase.

val purchasesUseCase: PurchasesUseCase = billingClient.purchases
purchasesUseCase.deletePurchase(purchaseId = "purchaseId")
.addOnSuccessListener {
// Process success
}
.addOnFailureListener { throwable: Throwable ->
// Process error
}
  • purchaseId — идентификатор покупки.
к сведению

Используйте этот метод, если у вас есть логика, завязанная на удалении покупки. Покупка отменяется автоматически через таймаут в 20 минут, либо при повторной покупке от того же клиента.

Обработка ошибок

При вызове метода RuStoreBillingClient.purchases.purchaseProduct(), ошибки обрабатываются автоматически.

Для показа диалога с ошибкой пользователю используйте метод resolveForBilling (см. ниже).

public fun RuStoreException.resolveForBilling(context: Context)

Обработка незавершённых платежей

Обработка незавершённых платежей производится разработчиком.

Чтобы подтвердить покупку типа CONSUMABLE и в статусе PAID вызовите метод потребления (подтверждения) покупки (см. Получение сведений о покупке).

В случае с отменой покупки при использовании методов обработки платежей учитывайте свой внутренний процесс. У некоторых разработчиков он предусматривает проверки перед потреблением (подтверждением) или отменой покупки. В этом случае запросите отдельно статус такой покупки.

подсказка

Например, если пользователь оплатил товар, который вы по каким-то причинам не можете ему поставить, вызовите метод отмены покупки в статусе PAID, чтобы отменить покупку.

В случаях, когда метод получения списка покупок возвращает покупку со статусом INVOICE_CREATED вы можете использовать метод отмены покупки. Например, если не хотите видеть покупку с такими статусами в списке покупок. Делать это самим не обязательно, поскольку RuStore обрабатывает отмену таких покупок на своей стороне.

к сведению

Иногда после оплаты через приложение банка (СБП, SberPay, T-Pay и др.) и при последующем возврате обратно в приложение статус покупки остаётся INVOICE_CREATED, при этом статус платежа — неуспешная покупка. Это связано с временем обработки покупки на стороне банка. Поэтому разработчику необходимо правильно связать логику получения списка покупок с жизненным циклом экрана.

Альтернативное решение — отмена покупки в статусе INVOICE_CREATED только через взаимодействие пользователя с приложением. Например, вы можете вынести эту логику в отдельную кнопку.

Ведение журнала событий

Если необходимо логировать события библиотеки платежей, добавьте в вызов RuStoreBillingClientFactory.create() параметры externalPaymentLoggerFactory и debugLogs. Они не обязательны для инициализации.

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
externalPaymentLoggerFactory = { tag -> PaymentLogger(tag) },
debugLogs = true
)

class PaymentLogger(private val tag: String) : ExternalPaymentLogger {
override fun d(e: Throwable?, message: () -> String) {
Log.d(tag, message.invoke(), e)
}

override fun e(e: Throwable?, message: () -> String) {
Log.e(tag, message.invoke(), e)
}

override fun i(e: Throwable?, message: () -> String) {
Log.i(tag, message.invoke(), e)
}

override fun v(e: Throwable?, message: () -> String) {
Log.v(tag, message.invoke(), e)
}

override fun w(e: Throwable?, message: () -> String) {
Log.w(tag, message.invoke(), e)
}
}

Ниже представлены параметры для включения логирования.

  • externalPaymentLoggerFactory — интерфейс, позволяющий создать логгер, который пробрасывает логи библиотеки в приложение-хост;
  • debugLogs — включить логи (логи будут автоматически отключены для Release-сборок).

Здесь PaymentLogger — это пример реализации логирования событий платежей.

Смена темы интерфейса

SDK поддерживает динамическую смены темы через интерфейс провайдера BillingClientThemeProvider.

val billingClient: RuStoreBillingClient = RuStoreBillingClientFactory.create(
context = app,
consoleApplicationId = "111111",
deeplinkScheme = "yourappscheme",
themeProvider? = BillingClientThemeProviderImpl(),
)

class BillingClientThemeProviderImpl: BillingClientThemeProvider {

override fun provide(): BillingClientTheme {
// Тут должна размещаться логика по проверке установленной темы
val darkTheme = ....
if(darkTheme){
BillingClientTheme.Dark
} else {
BillingClientTheme.Light
}
}
}

Обработка ошибок

Возможные ошибки

  • RuStoreNotInstalledException — на устройстве пользователя не установлен RuStore;
  • RuStoreOutdatedException — версия RuStore, установленная на устройстве пользователя, не поддерживает данный SDK;
  • RuStoreUserUnauthorizedException — пользователь не авторизован в RuStore;
  • RuStoreRequestLimitReached — с момента последнего отображения процесса прошло слишком мало времени;
  • RuStoreReviewExists — этот пользователь уже оценил ваше приложение;
  • RuStoreInvalidReviewInfo — проблемы с ReviewInfo;
  • RuStoreException — базовая ошибка RuStore, от которой наследуются остальные ошибки.
При вызове метода RuStoreBillingClient.purchases.purchaseProduct() ошибки обрабатываются автоматически.

Для показа диалога с ошибкой пользователю используйте метод resolveForBilling.

public fun RuStoreException.resolveForBilling(context: Context)

Коды ошибок

Ниже представлено описание возможных ошибок в поле errorCode.

HTTP-кодКод ошибкиОписание
40040001Параметры запроса неверны — не заполнены обязательные параметры/неверный формат параметров.
40040003Приложение не найдено.
40040004Статус приложения inactive.
40040005Продукт не найден.
40040006Статус продукта inactive.
40040007Недопустимый тип продукта. Поддерживаемые типы: consumable, non-consumable, subscription.
40040008Покупка с таким order_id уже существует.
40040009У текущего клиента найдена покупка этого продукта со статусом invoice_created. Необходимо предложить клиенту оплатить/отменить покупку.
40040010Для типа продукта consumable. У текущего клиента найдена покупка этого продукта со статусом paid. Сначала требуется подтвердить потребление покупки на устройстве, а затем можно отправлять следующий запрос на покупку этого продукта.
40040011Для типа продукта non-consumable. У текущего клиента найдена покупка этого продукта со статусом pre_confirmed/confirmed. Такой продукт уже приобретён. Более одного раза продукт не продаётся.
40040012Для типа продукта subscription. У текущего клиента найдена покупка этого продукта со статусом pre_confirmed/confirmed. Такой продукт уже приобретён. Более одного раза продукт не продаётся.
40040013Для типа продукта subscription. При обращении в сервис подписок за списком продуктов GET/products (serviceId, user_id) данные не были получены.
40040014Обязательный атрибут(-ы) не пришел в запросе.
40040015Не удалось изменить статус при обновлении покупки (переход запрещён).
40040016При покупке подписки непотребляемого продукта указано значение quantity > 1.
40040017Продукт удалён, новые покупки не доступны.
40040018Нельзя потреблять продукт с типом тип продукта.
40140101Невалидный токен.
40140102Время жизни токена истекло.
40340301Доступ к запрашиваемому ресурсу запрещён (неавторизованно).
40340302Для текущего токена текущий вызов не авторизован (метод запрещён).
40340303Идентификатор приложения в запросе и токен не совпадают.
40340305Неверный тип токена.
40440401Не найдено.
40840801Истекло время ожидания уведомления, указанное в запросе.
50050***Внутренняя ошибка платежного сервиса.