Setting up QR code payment in 1C-Bitrix

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

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