Платіжні QR-коди НБУ для оплати за IBAN (формат 003, Постанова №97). Платник сканує код у застосунку свого банку — і отримувач, IBAN, ЄДРПОУ, сума та призначення платежу заповнюються самі. А кнопки «Оплатити через …» відкривають той самий платіж одразу в застосунку конкретного банку.
Вимоги: Kotlin 2.3+; Android 5.0+ (API 21), JVM 11+ або iOS (arm64, симулятор arm64).
dependencies {
implementation("io.github.skules777:nbu-qr-code:1.0.0")
}У KMP-проєкті — у commonMain.dependencies { … }.
Для Swift-проєктів є nbu-qr-code-swift.
Цей QR-код згенеровано ось цим кодом:
val payment = NBUQRPayment(
recipient = "Петренко Роман Петрович",
iban = "UA906543210000000260323012024",
amount = BigDecimal("63"),
recipientCode = "40121425",
purpose = "За каву",
)
val payload = NBUQRPayload(payment)
val bitmap = NBUQRImageRenderer(accentColor = NBUQRColor.NBU_GREEN).bitmap(payload)
val monobank = payload.deepLinkIntent(NBUQRBank.MONOBANK)Дані — з офіційного прикладу Постанови (P2P-переказ). bitmap(payload) — для
Android; на JVM — bufferedImage(payload), на iOS — uiImage(payload), на будь-якій
платформі — pngBytes(payload) (PNG).
Сума — NBUQRDecimal: BigDecimal на JVM і Android, NSDecimalNumber на iOS.
У спільному коді — "63.50".toNBUQRDecimal().
У QR-коді — посилання https://qr.bank.gov.ua/…. Що відбувається далі,
залежить від того, чим його відкрити:
-
Сканер у застосунку банку — реквізити заповнюються автоматично. Саме так радить НБУ:
ВАЖЛИВО! Для швидкої оплати наступного разу скануйте QR-код із зображенням знаку гривні відразу із платіжного застосунку.
-
Камера iPhone відкриває застосунок банку, який обробляв такий QR-код останнім.
-
Перехід за посиланням на iPhone відкриває один зі встановлених банківських застосунків — дати платнику вибір iOS не дозволяє.
Тому, щоб платник сам обрав банк, покажіть поруч із QR-кодом кнопки «Оплатити через …» — див. Банки.
QR-код з кодом старту застосунку конкретного банку (напр. monobank) бібліотека свідомо не генерує: його опрацює лише застосунок цього банку, сканер будь-якого іншого його не прочитає. А знак ₴ на коді обіцяє платнику, що підійде будь-який банк, — тож такий QR лише вводить в оману.
QR-код сканують застосунки банків з офіційного переліку НБУ.
Кнопки «Оплатити через …» (deep link) відкривають платіж одразу в застосунку конкретного банку:
- Протестовано: Приват24, monobank (iOS).
- Не протестовано: Приват24 і monobank на Android, Privat24 for business, Абанк, ПУМБ, Райффайзен Банк, UKRSIBBANK, Укргазбанк, ОТП БАНК, Креді Агріколь Банк, РАДАБАНК, VST BANK, KredoBank, Sense Bank, izibank, Банк Кредит Дніпро, Глобус Банк.
Якщо на платформі прямого способу відкрити застосунок банку немає, посилання —
null.
// Android
val buttons = NBUQRBank.ALL.mapNotNull { bank ->
payload.deepLinkIntent(bank)
?.takeIf { it.resolveActivity(packageManager) != null } // лише встановлені
?.let { bank.name to it }
}
// iOS (KMP) і бекенд
payload.iosDeepLink(NBUQRBank.MONOBANK) // https://mbnk.app/qr/…
payload.androidDeepLink(NBUQRBank.MONOBANK) // intent://qr.bank.gov.ua/…;package=com.ftband.mono;endandroidDeepLink — Android Intent URI, який Chrome відкриває прямо з вебсторінки.
- Android 11+: щоб
resolveActivityбачив застосунки банків, оголосіть їхні пакети в<queries>маніфесту — готовий список є в маніфесті демо-застосунку. - iOS: частина банків відкривається власною схемою (
abank24://,vostok://…), а не https-адресою. Щоб приховати кнопки невстановлених застосунків черезcanOpenURL, додайте схеми банків уLSApplicationQueriesSchemesсвого Info.plist.
Банк, якого немає в списку, додається одним рядком:
val bank = NBUQRBank(
name = "Example Bank",
iosAppStartUrl = "https://bank.example/qr/",
androidPackage = "com.example.bank",
)
payload.iosDeepLink(bank) // https://bank.example/qr/<payload>NBUQRImageRenderer() // чорний
NBUQRImageRenderer(accentColor = NBUQRColor.NBU_GREEN) // зелений НБУ, як на bank.gov.ua
NBUQRImageRenderer(accentColor = NBUQRColor.fromHex("#1A237E")!!) // колір вашого брендуКолір отримують знак ₴ і центри трьох кутових квадратів. Надто світлий колір рендер відхилить, щоб код лишився читабельним.
category = "MP2P/MP2B" // код категорії / цілі за ISO 20022
category = "" // за замовчуванням: у QR-код потрапляє OTHR/OTHR («інше / інше»)Порожнім поле 10 бібліотека не лишає: постанова позначає його обовʼязковим, а Приват24 код із порожнім полем відхиляє. Деталі — у docs/NBU-COMPLIANCE.md.
Які поля платник може змінити у своєму застосунку перед оплатою:
editableFields = null // за замовчуванням: рішення за банком
editableFields = emptySet() // змінити нічого не можна
editableFields = setOf(NBUQREditableField.AMOUNT) // лише суму
editableFields = setOf(NBUQREditableField.AMOUNT, NBUQREditableField.PURPOSE) // суму й призначенняval issues = payment.validate() // List<NBUQRValidationIssue>
// [NBUQRValidationIssue(field = IBAN, problem = InvalidChecksum)]Кожна проблема прив'язана до поля, тож її легко підсвітити у формі. Порожній
список гарантує, що NBUQRPayload(payment) не кине помилку.
NBUQRException.InvalidPayment(issues)— той самий список, що йvalidate().NBUQRException.QRDataTooLong— дані не вміщуються в QR-код (deep link при цьому працює).NBUQRImageException.DataTooShort/DataTooLong— даних замало чи забагато для QR-коду за вимогами НБУ; помилка каже, на скільки байтів.
Тексти помилок (message) — англійською, для логів і розробників. Для
користувача покажіть власний текст за типом помилки.
QR-код і кнопки «Оплатити через …»
@Composable
fun PaymentQR(orderId: String, amount: BigDecimal) {
val context = LocalContext.current
val payment = remember(orderId, amount) {
NBUQRPayment(
function = NBUQRPaymentFunction.XCT,
recipient = "ФОП Коваленко Ірина Олександрівна",
iban = "UA906543210000000260323012024",
amount = amount,
recipientCode = "40121425",
reference = orderId,
purpose = "Оплата замовлення №$orderId",
// Reference — єдиний ключ, що звʼязує переказ із замовленням,
// тож усе інше блокуємо від редагування платником.
editableFields = emptySet(),
)
}
// Рендер — CPU-робота, тож не в головному потоці.
val state by produceState<Result<Pair<NBUQRPayload, ImageBitmap>>?>(null, payment) {
value = withContext(Dispatchers.Default) {
runCatching {
val payload = NBUQRPayload(payment)
payload to NBUQRImageRenderer().bitmap(payload).asImageBitmap()
}
}
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
state?.onSuccess { (payload, image) ->
Image(
bitmap = image,
contentDescription = "Платіжний QR-код",
filterQuality = FilterQuality.None, // модулі лишаються чіткими
modifier = Modifier.size(260.dp),
)
for (bank in listOf(NBUQRBank.PRIVAT_BANK, NBUQRBank.MONOBANK)) {
val intent = payload.deepLinkIntent(bank) ?: continue
Button(onClick = { context.startActivity(intent) }) {
Text("Оплатити через ${bank.name}")
}
}
}?.onFailure {
Text("Не вдалося згенерувати QR: ${it.message}", color = MaterialTheme.colorScheme.error)
} ?: CircularProgressIndicator()
}
}sample/ — застосунок на Compose Multiplatform для Android та iOS: форма
платежу з валідацією, QR-код і кнопки «Оплатити через …» для встановлених
банківських застосунків.
На JVM бібліотека працює без Android і без дисплея (headless):
val png: ByteArray = NBUQRImageRenderer().pngBytes(NBUQRPayload(payment))З Java — так само: new NBUQRPayload(payment), NBUQRImages.bufferedImage(renderer, payload).
Як бібліотека виконує вимоги Постанови №97 і що показала перевірка в банківських застосунках — у docs/NBU-COMPLIANCE.md.
MIT. QR-кодувальник — на основі QR Code generator Project Nayuki (MIT), див. NOTICE.
