Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

NBUQRCode (Kotlin Multiplatform)

CI Maven Central License: MIT Kotlin Platforms

Платіжні 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-код НБУ, згенерований бібліотекою

Цей 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-код із зображенням знаку гривні відразу із платіжного застосунку.

    — bank.gov.ua/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;end

androidDeepLink — 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")!!) // колір вашого бренду

Колір отримують знак ₴ і центри трьох кутових квадратів. Надто світлий колір рендер відхилить, щоб код лишився читабельним.

Категорія (поле 10)

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) — англійською, для логів і розробників. Для користувача покажіть власний текст за типом помилки.

Приклад: екран оплати в Compose

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.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages