ERIP System Integration
ERIP (Unified Settlement and Information Space) is Belarus state payment system. Allows payment acceptance via internet-banking, ATMs, payment terminals, bank mobile apps. Coverage over 99% Belarusian banks and payment terminals. For most Belarusian services ERIP integration mandatory: significant user share prefers paying via ERIP, especially via Sberbank and Belarusbank SBDO.
ERIP Architecture
Two connection paths:
- Via bank-agent — bank (Belarusbank, Priorbank, BSB Bank, etc.) provides ERIP gateway. Contract with one bank, bank API.
- Directly via OSMP/FTOS — requires separate contract with ERIP operator, more complex infrastructure.
For most projects bank-agent path chosen. Consider integration via most common variant — BSB Bank (WSCP API) and Belarusbank (webservice).
How It Works
ERIP works in Pull mode: not redirect scheme. Customer enters your service number in ERIP (e.g., "Internet-shops → Your Store → Enter order number"). ERIP queries your server for order info (Check-request), then after payment sends notification (Pay-request).
Your server must implement XML-request handler from ERIP.
Handler Implementation
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, 'Service not found');
}
$responseXml = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<response>
<result>
<errorCode>0</errorCode>
<errorDescription>Success</errorDescription>
</result>
<customer>
<account>{$accountNo}</account>
</customer>
<fields>
<field tag="250" tagName="Amount to Pay" value="{$order->total}"/>
<field tag="251" tagName="Order Number" value="{$order->id}"/>
</fields>
<amount>{$order->total}</amount>
<currency>933</currency>
<info>Order #{$order->id} from {$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, 'Account not found');
}
if (abs($amount - $order->total) > 0.01) {
return $this->xmlResponse(200, 'Invalid amount');
}
// Idempotency — don't process repeatedly
if (EripTransaction::where('transaction_id', $transId)->exists()) {
return $this->xmlResponse(0, 'Already processed');
}
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, 'Accepted');
}
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');
}
}
ERIP Account Number
Each order assigned unique account number. Usually order ID itself, but some banks require specific format (e.g., 15-digit number). This number shown to customer and entered in ERIP terminal.
Generate account number early and show customer on checkout page:
// Generate ERIP account number
$order->erip_account = str_pad($order->id, 12, '0', STR_PAD_LEFT);
QR Code for ERIP
For mobile app payment simplification, QR code with ERIP data can be generated. Format determined by bank-agent; usually deep link like:
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);
ERIP Service Tree Setup
Your service must be registered in ERIP service tree — done via bank-agent. Procedure includes: application completion, tree position agreement (e.g., "Internet-shops → [Region] → Store name"), testing. Registration and testing period 5–15 business days. Tree position change after registration separate request and 5–10 more days.
Security
Requests from ERIP accept only from bank-agent IP addresses. List provided by bank. Additionally — HTTPS with mutual authentication (mutual TLS) or HMAC-signature depending on bank.
// Middleware to check 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);
}







