ex3mm / bank-payment-qr
Generate bank payment QR codes (GOST R 56042-2014) for PHP 8.5+ and Laravel 12+
Requires
- php: ^8.5
- ext-gd: *
- ext-mbstring: *
- chillerlan/php-qrcode: ^6.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- illuminate/contracts: ^12.0
- illuminate/support: ^12.0
- orchestra/testbench: ^10.0
- pestphp/pest: ^3.8
- pestphp/pest-plugin-type-coverage: ^3.0
- phpstan/phpstan: ^2.1
- rector/rector: ^2.0
Suggests
- illuminate/support: Required for Laravel integration (^12.0)
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-10 21:36:53 UTC
README
ex3mm/bank-payment-qr создаёт QR-коды для оплаты по банковским реквизитам в формате ГОСТ Р 56042-2014. После сканирования такого кода банковское приложение может заполнить получателя, счёт, БИК, сумму и назначение платежа.
Пакет работает локально: он не отправляет реквизиты во внешние сервисы и не обращается к банкам. Это не СБП, не эквайринг и не Универсальный платёжный код.
Требования
- PHP 8.5 или новее;
- расширения
mbstringиGD; - Laravel 12 — только если нужна Laravel-интеграция.
Установка
composer require ex3mm/bank-payment-qr
Использование без Laravel
После установки Laravel, service provider и отдельный конфигурационный файл не нужны. Подключите Composer autoloader и создайте генератор через BankPaymentQrFactory.
Полный пример payment-qr.php:
<?php declare(strict_types=1); use Ex3mm\BankPaymentQr\BankPaymentQrFactory; use Ex3mm\BankPaymentQr\Payment\BankPaymentDetails; require __DIR__.'/vendor/autoload.php'; $payment = new BankPaymentDetails( recipientName: 'ООО "Ромашка"', recipientBankAccountNumber: '40702810123456789012', recipientBankName: 'ПАО "Банк"', recipientBankIdentificationCode: '044525225', recipientCorrespondentAccountNumber: '30101810400000000225', ); $payment = $payment ->withRecipientTaxpayerIdentificationNumber('7701234567') ->withRecipientTaxRegistrationReasonCode('770101001') ->withPaymentAmountKopecks(150000) ->withPaymentPurpose('Оплата по договору №123'); $qrCode = BankPaymentQrFactory::create()->generate($payment); file_put_contents(__DIR__.'/payment.svg', $qrCode->toSvg()); file_put_contents(__DIR__.'/payment.png', $qrCode->toPng());
Запустите файл из консоли:
php payment-qr.php
Рядом со скриптом появятся payment.svg и payment.png. Если Composer autoloader уже подключается входной точкой вашего приложения, повторный require не нужен.
Сумма передаётся целым числом копеек: 150000 означает 1 500 рублей. Методов с float намеренно нет.
BankPaymentDetails — неизменяемый объект. Каждый метод with...() возвращает новый экземпляр, поэтому результат нужно присвоить переменной, как в примере выше.
Результат генерации
Метод generate() возвращает RenderedQrCode:
$qrCode->toSvg(); // SVG-разметка $qrCode->toPng(); // бинарные данные PNG $qrCode->toDataUri(); // data:image/svg+xml;base64,... $qrCode->paymentPayload(); // строка, закодированная в QR
generate() сразу создаёт SVG и PNG, поэтому расширение GD требуется даже тогда, когда приложение использует только toSvg().
Пример вывода PNG без framework:
header('Content-Type: image/png'); echo $qrCode->toPng();
Laravel
Laravel обнаруживает service provider и facade автоматически.
Предпочтительный вариант — внедрить генератор через контейнер:
use Ex3mm\BankPaymentQr\BankPaymentQrGenerator; use Ex3mm\BankPaymentQr\Payment\BankPaymentDetails; use Illuminate\Http\Response; final class InvoiceQrController { public function __invoke(BankPaymentQrGenerator $generator): Response { $payment = new BankPaymentDetails( recipientName: 'ООО "Ромашка"', recipientBankAccountNumber: '40702810123456789012', recipientBankName: 'ПАО "Банк"', recipientBankIdentificationCode: '044525225', recipientCorrespondentAccountNumber: '30101810400000000225', ); $qrCode = $generator->generate( $payment ->withPaymentAmountKopecks(150000) ->withPaymentPurpose('Оплата счёта №123'), ); return response($qrCode->toSvg(), 200, [ 'Content-Type' => 'image/svg+xml; charset=UTF-8', ]); } }
Если удобнее facade:
use Ex3mm\BankPaymentQr\Laravel\Facades\BankPaymentQr; $qrCode = BankPaymentQr::generate($payment);
Конфигурация
Публикация конфига необязательна. Она нужна, только если требуется изменить renderer или параметры изображения:
php artisan vendor:publish --tag=bank-payment-qr-config
Доступные переменные окружения:
BANK_PAYMENT_QR_ECC=M BANK_PAYMENT_QR_SIZE=400 BANK_PAYMENT_QR_MARGIN=4 # BANK_PAYMENT_QR_RENDERER="App\\QrCode\\BankPaymentQrRenderer"
BANK_PAYMENT_QR_ECC— уровень коррекции ошибок:L,M,QилиH;BANK_PAYMENT_QR_SIZE— максимальная целевая сторона PNG в пикселях;BANK_PAYMENT_QR_MARGIN— свободная зона вокруг QR в модулях, минимум4.BANK_PAYMENT_QR_RENDERER— класс собственной реализацииQrCodeRendererInterface.
Фактическая сторона PNG может быть немного меньше заданной: размер округляется вниз до целого масштаба QR-модуля. Если изображение слишком мало для получившейся матрицы, генерация завершится исключением вместо создания нечитаемого QR. SVG остаётся масштабируемым.
Настройки отдельного QR-кода
Настройки из конфига можно переопределить для одного вызова:
use Ex3mm\BankPaymentQr\QrCode\QrCodeErrorCorrectionLevel; use Ex3mm\BankPaymentQr\QrCode\QrCodeRenderOptions; $qrCode = $generator->generate( $payment, new QrCodeRenderOptions( errorCorrectionLevel: QrCodeErrorCorrectionLevel::Quartile, sizeInPixels: 600, margin: 4, ), );
Поддерживаемые реквизиты
Пять полей обязательны и всегда идут в порядке, установленном ГОСТ:
| Поле | Аргумент PHP | Что передавать | Формат | Пример |
|---|---|---|---|---|
Name |
recipientName |
полное имя или наименование получателя из банковских реквизитов | непустой UTF-8 текст, до 160 символов | ООО "Ромашка" |
PersonalAcc |
recipientBankAccountNumber |
расчётный счёт получателя | ровно 20 цифр | 40702810123456789012 |
BankName |
recipientBankName |
наименование банка получателя | непустой UTF-8 текст, до 45 символов | ПАО "Банк" |
BIC |
recipientBankIdentificationCode |
БИК банка получателя | ровно 9 цифр | 044525225 |
CorrespAcc |
recipientCorrespondentAccountNumber |
корреспондентский счёт банка; если его нет — строку 0 |
от 1 до 20 цифр | 30101810400000000225 |
Дополнительные поля не попадают в QR, пока не вызван соответствующий метод:
| Поле | Метод | Что передавать | Формат | Пример |
|---|---|---|---|---|
PayeeINN |
withRecipientTaxpayerIdentificationNumber() |
ИНН получателя | 10 цифр для организации или 12 для физлица/ИП | 7701234567 |
KPP |
withRecipientTaxRegistrationReasonCode() |
КПП получателя из его реквизитов | от 1 до 9 цифр | 770101001 |
Sum |
withPaymentAmountKopecks() |
сумму платежа в копейках, не в рублях | неотрицательное целое, до 18 цифр | 150000 — это 1 500 ₽ |
Purpose |
withPaymentPurpose() |
назначение, которое должно появиться в платеже | UTF-8 текст, до 210 символов | Оплата счёта №123 |
UIN |
withPaymentUniqueAccrualIdentifier() |
УИН из квитанции или требования на оплату | от 1 до 25 цифр | 0 — если правила платежа требуют нулевой УИН |
CBC |
withBudgetClassificationCode() |
КБК бюджетного платежа | от 1 до 20 цифр | 18210101011011000110 |
OKTMO |
withMunicipalTerritoryCode() |
ОКТМО получателя бюджетного платежа | 8 или 11 цифр | 45348000 |
PaytReason |
withTaxPaymentReason() |
двухсимвольный код основания налогового платежа | текст до 2 символов | ТП |
TaxPeriod |
withTaxPeriod() |
налоговый период из платёжных реквизитов | текст до 10 символов | КВ.01.2026 |
DocNo |
withTaxDocumentNumber() |
номер документа-основания | текст до 15 символов | 0 |
DocDate |
withTaxDocumentDate() |
дату документа-основания или допустимое для платежа значение | текст до 10 символов | 10.09.2026 или 0 |
TaxPaytKind |
withTaxPaymentKind() |
код типа налогового платежа | текст до 2 символов | 0 |
Пример бюджетных реквизитов:
$payment = $payment ->withPaymentUniqueAccrualIdentifier('0') ->withBudgetClassificationCode('18210101011011000110') ->withMunicipalTerritoryCode('45348000') ->withTaxPaymentReason('ТП') ->withTaxPeriod('КВ.01.2026') ->withTaxDocumentNumber('0') ->withTaxDocumentDate('0') ->withTaxPaymentKind('0');
Пакет проверяет длину и базовый формат этих значений, но не определяет, какие бюджетные поля нужны для конкретного платежа. КБК, ОКТМО, УИН, основание, период и реквизиты документа нужно переносить из квитанции, требования или других официальных реквизитов платежа. Если значения нет и оно не требуется, соответствующий метод вызывать не нужно.
Как устроен payload
При стандартном разделителе строка начинается так:
ST00012|Name=ООО "Ромашка"|PersonalAcc=40702810123456789012|...
Здесь ST — идентификатор формата, 0001 — версия, 2 — UTF-8, а последний символ служебного блока — разделитель.
ГОСТ не предусматривает экранирование значений. Если в одном из них встречается |, пакет выбирает другой свободный графический разделитель и записывает его в служебный блок:
ST00012#Name=ООО "Ромашка"#...#Purpose=Товар | доставка
Символ = внутри значения разрешён: имя поля отделяется по первому =. Управляющие символы, включая табуляцию и переносы строк, отклоняются.
Готовая строка кодируется одним QR byte-сегментом без ECI. Блок обязательных реквизитов не превышает 300 символов. Общий размер дополнительных данных ограничивается вместимостью QR с выбранным уровнем коррекции ошибок, а не отдельным лимитом пакета.
Соответствие ГОСТ Р 56042-2014
| Требование | Реализация в пакете |
|---|---|
| Служебный блок длиной 8 байт | ST + версия 0001 + признак кодировки 2 + выбранный разделитель |
| Кодировка UTF-8 | Итоговая строка проверяется и передаётся как UTF-8 |
| Пять обязательных реквизитов | Name, PersonalAcc, BankName, BIC, CorrespAcc обязательны и сериализуются в нормативном порядке |
| Обязательный блок не более 300 символов | Ограничение проверяется при сериализации |
Формат Псевдоним=Значение |
Каждое поле сериализуется отдельной парой; первый = отделяет имя от значения |
| Разделитель содержится в значении | Выбирается другой графический разделитель и записывается в служебный блок |
| Дополнительные реквизиты | Добавляются только явно и сериализуются в детерминированном порядке |
| Двоичные данные QR | Payload помещается в один byte-сегмент без ECI |
| Свободная зона QR | Renderer требует не менее четырёх модулей |
| Вместимость символа | При превышении возможностей QR renderer выбрасывает QrCodeRenderingException |
Пакет проверяет структуру payload, кодировку, длины и базовый формат поддерживаемых реквизитов. Он не проверяет существование банковских реквизитов, актуальные правила конкретного бюджетного платежа, качество печати и поддержку формата конкретным банковским приложением.
Ошибки
Ошибки реквизитов наследуются от InvalidBankPaymentDetails:
use Ex3mm\BankPaymentQr\Exceptions\InvalidBankPaymentDetails; use Ex3mm\BankPaymentQr\Exceptions\QrCodeRenderingException; try { $qrCode = $generator->generate($payment); } catch (InvalidBankPaymentDetails $exception) { // Некорректные реквизиты, сумма или кодировка. } catch (QrCodeRenderingException $exception) { // QR невозможно отрисовать с выбранными параметрами. }
Некорректный конфиг или QrCodeRenderOptions приводит к InvalidArgumentException.
Собственный renderer
Renderer должен реализовать QrCodeRendererInterface. В Laravel укажите его класс в опубликованном конфиге — экземпляр будет создан через контейнер:
'renderer' => App\QrCode\BankPaymentQrRenderer::class,
Без Laravel класс можно передать фабрике. В этом случае у renderer должен быть конструктор без обязательных аргументов:
$generator = BankPaymentQrFactory::create([ 'qr_code' => [ 'renderer' => App\QrCode\BankPaymentQrRenderer::class, 'error_correction_level' => 'M', 'size_in_pixels' => 400, 'margin' => 4, ], ]);
Что пакет не проверяет
- существует ли счёт и принадлежит ли он получателю;
- соответствует ли расчётный счёт указанному БИК;
- примет ли конкретный банк конкретный набор дополнительных реквизитов;
- качество печати и физический размер QR на документе;
- факт, статус или безопасность проведения платежа.
Поддержка ГОСТ со стороны банковских приложений может отличаться. Перед выпуском платёжного документа отсканируйте итоговый QR теми приложениями и с того носителя, которыми будут пользоваться плательщики.
Частые вопросы
Что указать, если у банка нет корреспондентского счёта?
Передайте строку 0 в recipientCorrespondentAccountNumber. Поле остаётся обязательным и не должно быть пустым.
Почему банковское приложение не распознало QR?
Сначала прочитайте QR обычным декодером и сравните payload с реквизитами документа. Затем проверьте код в целевых банковских приложениях. На распознавание также влияют физический размер, контраст, качество печати и слишком большой объём дополнительных данных.
Можно ли передать сумму в рублях?
Нет. Публичный API принимает только целое количество копеек. Для 1 500 рублей передайте 150000.
Можно ли использовать пакет для СБП?
Нет. QR по банковским реквизитам и QR СБП — разные платёжные форматы.
Разработка
composer test
composer phpstan
composer cs:check
В Laravel Sail-полигоне этого репозитория доступны отдельные цели:
make test-bank-payment-qr make phpstan-bank-payment-qr make cs-bank-payment-qr
Документация и стандарты
- ГОСТ Р 56042-2014
- ГОСТ Р ИСО/МЭК 18004-2015
- СТО БР БФБО-1.9-2024
Лицензия
MIT. См. LICENSE.