Интеграция системы ЕРИП на сайт
ЕРИП (Единое расчётное и информационное пространство) — государственная платёжная система Беларуси. Позволяет принимать платежи через интернет-банкинг, банкоматы, платёжные терминалы, мобильные приложения банков. Охват — более 99% белорусских банков и платёжных терминалов. Для большинства белорусских сервисов интеграция ЕРИП обязательна: значительная часть пользователей предпочитает платить именно через ЕРИП, особенно через СДБО Сбербанка и Беларусбанка.
Архитектура ЕРИП
Есть два пути подключения:
- Через банк-агент — банк (Беларусбанк, Приорбанк, БСБ Банк и др.) предоставляет шлюз к ЕРИП. Договор с одним банком, API банка.
- Напрямую через ОСМП/ФТОС — требует отдельного договора с оператором ЕРИП, более сложная инфраструктура.
Для большинства проектов выбирается путь через банк-агент. Рассмотрим интеграцию через наиболее распространённый вариант — БСБ Банк (API WSCP) и Беларусбанк (webservice).
Концепция работы
ЕРИП работает в режиме Pull: это не редирект-схема. Покупатель вводит ваш номер услуги в ЕРИП (например, «Интернет-магазины → ВашМагазин → Введите номер заказа»). ЕРИП запрашивает у вашего сервера информацию о заказе (Check-запрос), затем после оплаты отправляет уведомление (Pay-запрос).
Ваш сервер должен реализовать обработчик XML-запросов от ЕРИП.
Реализация обработчика
class EripController extends Controller
{
public function handle(Request $request): Response
{
$xml = simplexml_load_string($request->getContent());
$command = (string)$xml->command['type'];
return match ($command) {
'check' => $this->check($xml),
'pay' => $this->pay($xml),
default => $this->error('Unknown command'),
};
}
private function check(\SimpleXMLElement $xml): Response
{
$accountNo = (string)$xml->customer->account;
$order = Order::where('erip_account', $accountNo)
->where('status', 'pending')
->first();
if (!$order) {
return $this->xmlResponse(200, 'Услуга не найдена');
}
$responseXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result>
<errorCode>0</errorCode>
<errorDescription>Успешно</errorDescription>
</result>
<customer>
<account>{$accountNo}</account>
</customer>
<fields>
<field tag="250" tagName="Сумма к оплате" value="{$order->total}"/>
<field tag="251" tagName="Номер заказа" value="{$order->id}"/>
</fields>
<amount>{$order->total}</amount>
<currency>933</currency>
<info>Заказ #{$order->id} от {$order->created_at->format('d.m.Y')}</info>
</response>
XML;
return response($responseXml, 200)
->header('Content-Type', 'application/xml; charset=utf-8');
}
private function pay(\SimpleXMLElement $xml): Response
{
$accountNo = (string)$xml->customer->account;
$transId = (string)$xml->transaction['id'];
$amount = (float)$xml->transaction->amount;
$paymentDate = (string)$xml->transaction['date'];
$order = Order::where('erip_account', $accountNo)
->where('status', 'pending')
->lockForUpdate()
->first();
if (!$order) {
return $this->xmlResponse(100, 'Счёт не найден');
}
if (abs($amount - $order->total) > 0.01) {
return $this->xmlResponse(200, 'Неверная сумма');
}
// Идемпотентность — не обрабатывать повторно
if (EripTransaction::where('transaction_id', $transId)->exists()) {
return $this->xmlResponse(0, 'Уже обработан');
}
DB::transaction(function () use ($order, $transId, $amount, $paymentDate) {
EripTransaction::create([
'transaction_id' => $transId,
'order_id' => $order->id,
'amount' => $amount,
'paid_at' => $paymentDate,
]);
$order->update(['status' => 'paid']);
});
return $this->xmlResponse(0, 'Принято');
}
private function xmlResponse(int $code, string $message): Response
{
$xml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result>
<errorCode>{$code}</errorCode>
<errorDescription>{$message}</errorDescription>
</result>
</response>
XML;
return response($xml, 200)
->header('Content-Type', 'application/xml; charset=utf-8');
}
}
Номер лицевого счёта ЕРИП
Каждому заказу присваивается уникальный номер лицевого счёта. Обычно это сам ID заказа, но некоторые банки требуют определённого формата (например, 15-значный номер). Этот номер отображается покупателю и вводится в терминале ЕРИП.
Номер счёта нужно сгенерировать заранее и показать покупателю на странице оформления:
// Генерация номера ЕРИП-счёта
$order->erip_account = str_pad($order->id, 12, '0', STR_PAD_LEFT);
QR-код для ЕРИП
Для упрощения оплаты через мобильные приложения можно генерировать QR-код с данными ЕРИП. Формат QR определяется банком-агентом; обычно это глубокая ссылка вида:
erip://pay?service_code=XXXXX&account=000000012345
use Endroid\QrCode\QrCode;
$qrCode = QrCode::create("erip://pay?service_code={$serviceCode}&account={$order->erip_account}")
->setSize(200);
Настройка в дереве услуг ЕРИП
Ваш сервис должен быть зарегистрирован в дереве услуг ЕРИП — это делается через банк-агент. Процедура включает: заполнение заявки, согласование места в дереве (например, «Интернет-магазины → [Регион] → Название магазина»), тестирование. Срок регистрации и тестирования — от 5 до 15 рабочих дней. Изменение позиции в дереве после регистрации — отдельная заявка и ещё 5–10 дней.
Безопасность
Запросы от ЕРИП нужно принимать только с IP-адресов банка-агента. Список IP предоставляет банк. Дополнительно — HTTPS с взаимной аутентификацией (mutual TLS) или HMAC-подпись в зависимости от банка.
// Middleware для проверки IP
public function handle(Request $request, Closure $next): Response
{
$allowedIps = explode(',', env('ERIP_ALLOWED_IPS'));
if (!in_array($request->ip(), $allowedIps)) {
return response('Forbidden', 403);
}
return $next($request);
}







