Search by

ex3mm / bank-payment-qr

Ex3mm

Generate bank payment QR codes (GOST R 56042-2014) for PHP 8.5+ and Laravel 12+

Package info

github.com/ex3mm/bank-payment-qr

pkg:composer/ex3mm/bank-payment-qr

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.0 2026-09-10 20:59 UTC

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

Документация и стандарты

Лицензия

MIT. См. LICENSE.