QR Code Payment Setup for 1C-Bitrix
Payment by QR code — the buyer scans the code on screen or a printed document and confirms the payment in their bank's mobile app or via the Faster Payments System (FPS). Popular in B2B (invoice payments), at pickup points, and as an alternative method for customers without a card. Technically, QR payment on a Bitrix site is implemented via the FPS (Central Bank of Russia) or through banks' proprietary QR services.
Two Types of QR Codes
Static QR — a single code for one store/account; the amount is entered manually by the buyer at the time of payment. Simple to implement but inconvenient: you need to manually track who paid and how much.
Dynamic QR — each payment generates a unique QR with a specific amount and order ID. Payment is automatically matched to the order. This is the correct option for e-commerce.
Dynamic QR via FPS
The FPS of the Central Bank of Russia supports dynamic QR codes through participating banks. In most cases, this is implemented through an acquiring service:
// Creating a QR via YooKassa (sberqr or sbp method)
$payment = $yookassaClient->createPayment([
'amount' => ['value' => $orderAmount, 'currency' => 'RUB'],
'payment_method_data' => ['type' => 'sberbank'], // or 'sbp'
'capture' => true,
'confirmation' => ['type' => 'qr'], // confirmation type — QR
'description' => 'Order #' . $orderId,
], uniqid('', true));
// Get the QR image
$qrCodeUrl = $payment->getConfirmation()->getConfirmationData();
// $qrCodeUrl — URL of the QR code image or a payload string for generation
// Or via Tinkoff
$qrData = $tinkoffClient->getQr(['PaymentId' => $tinkoffPaymentId]);
$qrString = $qrData['Data']; // string for QR generation
Generating a QR Image on the Server
If the acquiring service returns a payload string (rather than a ready-made image):
// Install: composer require endroid/qr-code
use Endroid\QrCode\Builder\Builder;
use Endroid\QrCode\Encoding\Encoding;
use Endroid\QrCode\Writer\PngWriter;
$result = Builder::create()
->writer(new PngWriter())
->data($qrString)
->encoding(new Encoding('UTF-8'))
->size(300)
->margin(10)
->build();
// Return as data URI or save to file
$qrDataUri = $result->getDataUri();
Displaying the QR on the Order Page
In the template of the sale.order.payment.result component or on a custom payment page:
<div class="qr-payment-block">
<p>Scan the QR code in your bank's app to pay</p>
<img src="<?= $arResult['QR_IMAGE_URL'] ?>"
alt="Payment QR code"
width="250" height="250">
<p>Amount due: <strong><?= $arResult['AMOUNT'] ?> RUB</strong></p>
<p>Order code: <?= $arResult['ORDER_NUMBER'] ?></p>
<!-- Automatic status check every 5 seconds -->
<div id="qr-status">Waiting for payment...</div>
</div>
<script>
const checkInterval = setInterval(async () => {
const res = await fetch('/bitrix/tools/check_payment_status.php?order=<?= $orderId ?>');
const data = await res.json();
if (data.paid) {
clearInterval(checkInterval);
window.location.href = '/checkout/success/?order=<?= $orderId ?>';
}
}, 5000);
// Stop checking after 15 minutes
setTimeout(() => clearInterval(checkInterval), 15 * 60 * 1000);
</script>
Payment Status Polling
// /bitrix/tools/check_payment_status.php
$orderId = (int)($_GET['order'] ?? 0);
$order = \Bitrix\Sale\Order::load($orderId);
header('Content-Type: application/json');
if (!$order) {
echo json_encode(['paid' => false, 'error' => 'Order not found']);
exit;
}
// Check via the acquiring API (not only through Bitrix)
$externalId = getExternalPaymentId($orderId);
$status = $yookassaClient->getPaymentInfo($externalId)->getStatus();
if ($status === 'succeeded' && !$order->isPaid()) {
// Synchronise status
$payment = $order->getPaymentCollection()->current();
$payment->setPaid('Y');
$payment->save();
}
echo json_encode(['paid' => $order->isPaid()]);
Timeline
| Task | Duration |
|---|---|
| Dynamic QR generation via acquiring service | 1 day |
| Status polling + JS auto-refresh of the page | 0.5 day |
| Testing scenarios (success, timeout, cancellation) | 0.5 day |







