Подключение QR‑код от Яндекс Пэй Android SDK

Шаг 1. Добавьте зависимости

// build.gradle.kts
dependencies {
    implementation("com.yandex.pay:quickpay:LATEST_VERSION")
}
// build.gradle (Groovy DSL)
dependencies {
    implementation 'com.yandex.pay:quickpay:LATEST_VERSION'
}

Актуальная версия: com.yandex.pay:quickpay

ProGuard / R8: дополнительная настройка не нужна. SDK поставляется с consumer-rules.pro — правила применяются автоматически при сборке приложения.

Multidex: явно не требуется (minSdk = 24). SDK включает несколько крупных зависимостей, поэтому если суммарное число методов в вашем приложении приближается к 64 000, включите multidex:

android {
    defaultConfig {
        multiDexEnabled = true
    }
}
dependencies {
    implementation("androidx.multidex:multidex:2.0.1")
}

Манифест: дополнительные объявления не нужны. SDK добавляет необходимые пермиссии и <queries> через Manifest Merger автоматически:

  • android.permission.INTERNET
  • android.permission.ACCESS_NETWORK_STATE
  • <queries> для пакетов com.yandex.bank и com.yandex.bank.dev (для взаимодействия с приложением Яндекс Банк на Android 11+)

Шаг 2. Инициализируйте SDK

Инициализация выполняется один раз в Application.onCreate().

import com.yandex.pay.quickpay.api.YandexQuickPay
import com.yandex.pay.quickpay.api.QuickPayConfig
import com.yandex.pay.quickpay.api.QuickPayEnvironment
import com.yandex.pay.quickpay.api.QuickPayLocale
import com.yandex.pay.quickpay.api.QuickPayThemeColorScheme
import com.yandex.pay.quickpay.api.QuickPaymentStateListener
import com.yandex.pay.quickpay.api.QuickPayResult
import com.yandex.pay.quickpay.api.IsPaymentEnabled

class MyApp : Application() {

    val quickPayListener = object : QuickPaymentStateListener {
        override fun onPaymentEnabledStateChanged(isEnabled: IsPaymentEnabled) {}
        override fun onSessionExpired() {}
        override fun onPaymentResult(result: QuickPayResult) {}
    }

    override fun onCreate() {
        super.onCreate()

        YandexQuickPay.locale = QuickPayLocale.SYSTEM
        YandexQuickPay.theme = QuickPayThemeColorScheme.SYSTEM

        val config = QuickPayConfig(
            merchantId = "YOUR_MERCHANT_ID",
            environment = QuickPayEnvironment.SANDBOX, // SANDBOX для разработки, PRODUCTION для релиза
        )

        YandexQuickPay.initialize(
            config = config,
            context = this,
            quickPaymentStateListener = quickPayListener,
        )
    }
}

Важно

SDK хранит листенер как WeakReference. Держите сильную ссылку в Application, Activity или ViewModel, иначе объект будет собран GC и колбэки перестанут приходить.

Важно

Перед релизом установите QuickPayEnvironment.PRODUCTION в конфиге. В sandbox-окружении реальные платежи не проводятся.

initialize() можно вызывать повторно — например, если нужно сменить merchantId или обновить листенер. При повторном вызове старый листенер заменяется.

Шаг 3. Инициализируйте UI-компоненты

initUi() необходимо вызвать в Activity, которая будет работать с быстрой оплатой. Метод не показывает никакого UI сразу — он регистрирует лончеры для OAuth-авторизации и биометрии, а также сохраняет ссылки для отображения bottom sheets.

lifecycleScope.launch {
    YandexQuickPay.initUi(
        activity = this@QrActivity,
        fragmentManager = supportFragmentManager
    )
    // После initUi можно вызывать getPaymentSessionId() и enableQuickPayment()
}
Когда initUi() обязателен
  • При вызове getPaymentSessionId() (требуется биометрическая аутентификация)
  • При вызове enableQuickPayment() (OAuth-авторизация через Яндекс ID)
  • При показе bottom sheets в процессе оплаты
Когда можно обойтись без initUi()

Если вы только отображаете виджет YandexPaymentMethodsWidget в read-only режиме без авторизации пользователя.

Шаг 4. Добавьте виджет методов оплаты

YandexPaymentMethodsWidget показывает доступные пользователю платёжные методы и элементы управления быстрой оплатой. Виджет обновляется автоматически.

Через XML:

<com.yandex.pay.quickpay.api.YandexPaymentMethodsWidget
    android:id="@+id/paymentMethodsWidget"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

Программно:

val widget = YandexPaymentMethodsWidget(context)
widgetContainer.addView(widget)

Публичного API у виджета нет — он управляет своим состоянием самостоятельно через SDK.

Шаг 5. Управляйте состоянием быстрой оплаты

Рекомендуем вынести логику в ViewModel для корректного lifecycle.

sealed interface QrScreenState {
    object Loading : QrScreenState
    data class QrReady(val sessionId: String) : QrScreenState
    object PaymentDisabled : QrScreenState
    data class Error(val message: String) : QrScreenState
}

class QrViewModel : ViewModel() {

    private val _state = MutableStateFlow<QrScreenState>(QrScreenState.Loading)
    val state: StateFlow<QrScreenState> = _state.asStateFlow()

    val quickPayListener = object : QuickPaymentStateListener {
        override fun onPaymentEnabledStateChanged(isEnabled: IsPaymentEnabled) {
            if (isEnabled.value) refreshSession() else _state.value = QrScreenState.PaymentDisabled
        }
        override fun onSessionExpired() { refreshSession() }
        override fun onPaymentResult(result: QuickPayResult) { refreshSession() }
    }

    fun init() {
        viewModelScope.launch {
            val isEnabled = runCatching { YandexQuickPay.isQuickPaymentEnabled() }
                .getOrDefault(IsPaymentEnabled(false))
            if (isEnabled.value) refreshSession() else _state.value = QrScreenState.PaymentDisabled
        }
    }

    fun refreshSession() {
        viewModelScope.launch {
            _state.value = QrScreenState.Loading
            runCatching { YandexQuickPay.getPaymentSessionId() }
                .onSuccess { _state.value = QrScreenState.QrReady(it) }
                .onFailure { _state.value = QrScreenState.Error(it.message ?: "Неизвестная ошибка") }
        }
    }

    fun enableQuickPayment() {
        viewModelScope.launch { YandexQuickPay.enableQuickPayment().onFailure { /* ... */ } }
    }

    fun disableQuickPayment() {
        viewModelScope.launch { YandexQuickPay.disableQuickPayment().onFailure { /* ... */ } }
    }
}

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

class QrActivity : AppCompatActivity() {
    private val viewModel: QrViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_qr)

        // Передаём листенер из ViewModel в SDK
        YandexQuickPay.initialize(
            config = QuickPayConfig(merchantId = "YOUR_MERCHANT_ID", environment = QuickPayEnvironment.SANDBOX),
            context = application,
            quickPaymentStateListener = viewModel.quickPayListener,
        )

        lifecycleScope.launch {
            YandexQuickPay.initUi(this@QrActivity, supportFragmentManager)
            viewModel.init()
        }

        lifecycleScope.launch {
            viewModel.state.collect { state ->
                when (state) {
                    is QrScreenState.Loading -> showLoading()
                    is QrScreenState.QrReady -> showQr(state.sessionId)
                    is QrScreenState.PaymentDisabled -> showEnableButton()
                    is QrScreenState.Error -> showError(state.message)
                }
            }
        }
    }
}

Включение и отключение

  • Включение: viewModel.enableQuickPayment() — может показать экран авторизации Яндекс ID или биометрический запрос. После успеха — onPaymentEnabledStateChanged(isEnabled = true).
  • Отключение: viewModel.disableQuickPayment().
  • Выход из аккаунта: YandexQuickPay.logout() — очищает все данные, потребуется повторная авторизация.

Шаг 6. Работайте с платёжной сессией и QR-кодом

getPaymentSessionId() создаёт новую сессию или возвращает существующую, если срок её действия ещё не истёк. Срок действия определяется сервером.

viewModelScope.launch {
    runCatching { YandexQuickPay.getPaymentSessionId() }
        .onSuccess { sessionId ->
            val bitmap = generateQrBitmap(sessionId)
            qrImageView.setImageBitmap(bitmap)
        }
        .onFailure { error ->
            Log.e("QuickPay", "Ошибка получения сессии: ${error.message}", error)
        }
}

Примечание

При вызове getPaymentSessionId() может появиться системный запрос биометрической аутентификации, если с момента последней разблокировки прошло достаточно времени. Убедитесь, что initUi() был вызван заранее.

Когда обновлять QR:

Событие Действие
Первое отображение экрана (быстрая оплата включена) Вызвать getPaymentSessionId()
onSessionExpired() Вызвать getPaymentSessionId()
onPaymentResult() (любой результат) Вызвать getPaymentSessionId() для следующей транзакции
onPaymentEnabledStateChanged(isEnabled = true) Вызвать getPaymentSessionId()

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

getPaymentSessionId() может бросить следующие исключения:

Исключение Причина Решение
RuntimeException("YandexQuickPay must be initialized...") initialize() не был вызван Вызовите initialize() в Application.onCreate()
IllegalStateException("Activity not available") initUi() не был вызван Вызовите initUi() перед первым запросом сессии
IllegalStateException("User is not authorized...") Пользователь не авторизован Вызовите enableQuickPayment() и дождитесь onPaymentEnabledStateChanged
RuntimeException("Failed to generate session: ...") Ошибка сети или сервера Повторите запрос, проверьте соединение

Аналогичные исключения бросают isQuickPaymentEnabled(), enableQuickPayment() и disableQuickPayment().

Рекомендация: оборачивайте все suspend-вызовы SDK в runCatching { } и обрабатывайте ошибки в UI.

Безопасность

  • merchantId — публичный идентификатор магазина, не является секретом. Можно хранить в коде или конфигурации.
  • sessionId — идентификатор платёжной сессии, привязан к пользователю и устройству. Не логируйте sessionId в production и не передавайте третьим сторонам.
  • SDK не хранит данные банковских карт на устройстве.

Тестирование

Sandbox-окружение: используйте QuickPayEnvironment.SANDBOX для разработки. В sandbox все платежи симулируются — реальных списаний не происходит.

Сценарии для проверки:

Сценарий Как воспроизвести Ожидаемое поведение
Первая авторизация Вызов enableQuickPayment() для нового пользователя Показ экрана авторизации Яндекс ID
Успешный платёж Сканирование QR на тестовой кассе onPaymentResult(QuickPayResult.Success)
Истечение сессии Ожидание истечения expires_at onSessionExpired(), затем новый QR после getPaymentSessionId()
Отключение быстрой оплаты Вызов disableQuickPayment() onPaymentEnabledStateChanged(isEnabled = false)
Выход из аккаунта Вызов logout() Очистка данных, требуется повторная авторизация
Предыдущая