Назад к блогу
08.04.2026 16:17:09

Интеграция Binance Pay v3 в Битрикс: создание платежа, webhook и каноническое пополнение баланса

Интеграция Binance Pay с сайтом на 1С-Битрикс может использоваться не только для оплаты обычных заказов, но и для пополнения внутреннего баланса пользователя. Такой сценарий часто встречается в личных кабинетах, сервисах, маркетплейсах, форумах с платными функциями, B2B-порталах и проектах, где пользователь заранее вносит средства, а потом расходует их внутри сайта.

На первый взгляд задача выглядит просто: пользователь ввел сумму, оплатил через Binance Pay, а сайт увеличил его баланс. Но в реальном продакшене финтех-логика не прощает халатных решений. Чтобы защитить проект от «состояния гонки» (Race Condition), фейковых оплат и утечки данных, необходима профессиональная интеграция платежных систем на базе канонической архитектуры ядра самого Битрикса.

Такие задачи относятся к сложной кастомной разработке на PHP и Битрикс. Если вам нужно подключить криптовалютные платежи, глубоко доработать личный кабинет или связать внешние шлюзы с внутренним счетом магазина, это можно реализовать в рамках услуги доработки Bitrix и PHP-задачи.

Как работает безопасная схема пополнения баланса

Весь процесс интеграции Binance Pay v3 строится на строгом асинхронном взаимодействии сервер-сервер:

  1. Пользователь вводит сумму пополнения в личном кабинете.
  2. Сервер создает платежный ордер в Binance Pay.
  3. Binance возвращает данные для оплаты: безопасную ссылку, QR-код или deeplink.
  4. Пользователь совершает транзакцию через приложение или платежную страницу Binance.
  5. Binance отправляет подписанный асимметричным ключом webhook на сервер сайта.
  6. Сервер валидирует подпись, проверяет статус платежа и находит внутреннюю техническую запись.
  7. Если платеж успешен и ранее не обрабатывался, баланс пользователя атомарно увеличивается на уровне СУБД.
  8. Фронтенд через защищенный AJAX-endpoint узнает об успехе, обновляет интерфейс и закрывает окно оплаты.

Важно: баланс пользователя категорически нельзя обновлять на фронтенде или сразу после открытия QR-кода. Зачисление средств происходит только по факту успешной обработки вебхука.

Архитектура «здорового человека»: почему кастомные поля — это костыль

Распространенная ошибка Junior-разработчиков — создание пользовательского поля типа UF_BALANCE в таблице пользователей и его обновление через предварительный расчет в PHP ($newBalance = $current + $amount) с последующим вызовом $user->Update().

В финансовых операциях это недопустимо. Если в одну секунду придет два вебхука или пользователь параллельно спишет деньги на покупку товара, один процесс затрет данные другого (Race Condition).

В 1С-Битрикс «из коробки» в модуле sale уже заложен профессиональный функционал внутреннего счета пользователя. Он использует две связанные таблицы:

  • b_sale_user_account — хранит текущие остатки в разрезе разных валют.
  • b_sale_user_transact — неизменяемый лог (аудит-трейл), куда автоматически пишется вся история: кто, когда, сколько зачислил или за какой заказ списал.

Штатный метод CSaleUserAccount::UpdateAccount() выполняет транзакции непосредственно на уровне базы данных, полностью защищая систему от рассинхронизации баланса.

Роль Highload-блока в платежной логике

При каноническом подходе Highload-блок не должен хранить баланс пользователя. Его задача — фиксировать техническое состояние самого процесса оплаты, выступая связующим звеном между вебхуком Binance и внутренним счетом Битрикса.

Основные поля Highload-блока:

  • UF_ORDER_ID — внутренний номер платежа (merchantTradeNo);
  • UF_USER_ID — ID пользователя Битрикс;
  • UF_AMOUNT — сумма пополнения;
  • UF_CURRENCY — валюта платежа (например, USDT);
  • UF_PAID — флаг успешного завершения операции (boolean, защита от дублирования вебхуков);
  • UF_STATUS — технический статус ответа платежной системы;
  • UF_CREATED_AT / UF_PAID_AT — временные метки шагов транзакции.

Создание платежа через Binance Pay v3

Для генерации QR-кода сервер шлет запрос на endpoint:

POST /binancepay/openapi/v3/order

Payload для пополнения счета обязательно должен содержать правильный тип товара (goodsType: "02" — Virtual Goods):

{
    "merchantTradeNo": "BPAY10023X7",
    "orderAmount": "50.00",
    "currency": "USDT",
    "env": {
        "terminalType": "WEB"
    },
    "description": "Пополнение внутреннего счета",
    "goodsDetails": [
        {
            "goodsType": "02",
            "goodsCategory": "D000",
            "referenceGoodsId": "balance_topup",
            "goodsName": "User Balance Top-up"
        }
    ],
    "returnUrl": "https://agstudio.md/personal/wallet/success",
    "cancelUrl": "https://agstudio.md/personal/wallet/cancel"
}

Поле merchantTradeNo должно содержать строго уникальную буквенно-цифровую строку длиной до 32 символов без спецзнаков. Сразу после отправки payload и генерации подписи мы логируем транзакцию в Highload-блок через D7:

<?php
use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;

Loader::includeModule("highloadblock");

$hlblock = HighloadBlockTable::getById(HL_BLOCK_ID_BINANCE)->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityDataClass = $entity->getDataClass();

$entityDataClass::add([
    'UF_ORDER_ID' => $merchantTradeNo,
    'UF_USER_ID'  => $userId,
    'UF_AMOUNT'   => $amount,
    'UF_CURRENCY' => $currency,
    'UF_PAID'     => false,
    'UF_STATUS'   => 'INITIAL'
]);
?>

Подпись запроса Binance Pay

Запрос подписывается с помощью HMAC-SHA512. Строка подписи формируется из миллисекундного timestamp, случайной строки (nonce) и точного JSON-тела запроса, разделенных переносом строки:

$signaturePayload = $timestamp . "\n" . $nonce . "\n" . $jsonPayload . "\n";
$signature = strtoupper(hash_hmac('SHA512', $signaturePayload, $secretKey));

Безопасная обработка Webhook (Золотой стандарт)

Для изоляции платежной логики скрипт-обработчик размещается по пути: /local/binance/webhook.php. Ниже представлен полностью исправный код, реализующий принцип идемпотентности и каноническое зачисление средств:

<?php
define("STOP_STATISTICS", true);
define("NO_KEEP_STATISTIC", true);
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");

use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;

if (!Loader::includeModule("sale") || !Loader::includeModule("highloadblock")) {
    die("Required modules missing");
}

$rawData = file_get_contents("php://input");

// [ВАЖНО]: Здесь должна быть валидация асимметричной подписи из заголовка BinancePay-Signature!
$isSignatureValid = true; 

if (!$isSignatureValid) {
    CHttp::SetStatus("403 Forbidden");
    die("Signature verification failed");
}

$payload = json_decode($rawData, true);
$innerData = json_decode($payload['data'], true);

$merchantTradeNo = $innerData['merchantTradeNo'];
$amount = (float)$innerData['orderAmount'];
$currency = $innerData['currency'];

if ($payload['bizStatus'] !== 'PAY_SUCCESS') {
    die("Transaction not successful");
}

// Поиск записи в Highload-блоке
$hlblock = HighloadBlockTable::getById(HL_BLOCK_ID_BINANCE)->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityDataClass = $entity->getDataClass();

$dbOrder = $entityDataClass::getList([
    'filter' => ['UF_ORDER_ID' => $merchantTradeNo]
]);

if (!$order = $dbOrder->fetch()) {
    CHttp::SetStatus("404 Not Found");
    die("Order not found");
}

// ПРИНЦИП ИДЕМПОТЕНТНОСТИ: защита от дублирующих вебхуков шлюза
if ($order['UF_PAID'] == true) {
    echo json_encode(["returnCode" => "SUCCESS", "returnMsg" => "Already processed"]);
    exit;
}

// Каноническое, потокобезопасное пополнение счета в Битрикс
$userId = (int)$order['UF_USER_ID'];
$transactionNotes = "Пополнение счета через Binance Pay. Ордер: " . $merchantTradeNo;

$isSuccess = CSaleUserAccount::UpdateAccount(
    $userId,
    $amount,
    $currency,
    "BINANCE_PAY",
    0,
    $transactionNotes
);

if ($isSuccess) {
    // Закрываем транзакцию в HL-блоке
    $entityDataClass::update($order['ID'], [
        'UF_PAID'    => true,
        'UF_STATUS'  => 'SUCCESS',
        'UF_WEBHOOK' => $rawData,
        'UF_PAID_AT' => new \Bitrix\Main\Type\DateTime()
    ]);

    echo json_encode(["returnCode" => "SUCCESS", "returnMsg" => "OK"]);
} else {
    CHttp::SetStatus("500 Internal Server Error");
    die("Account update failed");
}
?>

Проверка статуса оплаты через AJAX (Защита от перебора)

Пока пользователь сканирует QR-код, фронтенд опрашивает endpoint /ajax/check_binance_payment.php. В коде ниже реализована жесткая защита от уязвимости перебора номеров ордеров (ID Enumeration): в фильтр запроса обязательно подмешивается ID текущего авторизованного сессионного пользователя.

<?php
define("STOP_STATISTICS", true);
define("NO_KEEP_STATISTIC", true);
require_once($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");

use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;

$currentUserId = (int)$GLOBALS['USER']->GetID();
$orderId = trim($_POST['orderId']);

if ($currentUserId <= 0 || empty($orderId)) {
    echo json_encode(['status' => 'error', 'message' => 'Access Denied']);
    exit;
}

Loader::includeModule("highloadblock");
Loader::includeModule("sale");

$hlblock = HighloadBlockTable::getById(HL_BLOCK_ID_BINANCE)->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityDataClass = $entity->getDataClass();

// Фильтрация И по ID ордера, И по ID текущего пользователя
$order = $entityDataClass::getList([
    'filter' => [
        'UF_ORDER_ID' => $orderId,
        'UF_USER_ID'  => $currentUserId
    ]
])->fetch();

$userBalance = 0.00;
if ($order['UF_PAID']) {
    // Извлекаем актуальный бюджет из канонического счета
    if ($arAccount = CSaleUserAccount::GetByUserID($currentUserId, "USDT")) {
        $userBalance = (float)$arAccount["CURRENT_BUDGET"];
    }
}

echo json_encode([
    'status'  => $order['UF_PAID'] ? 'paid' : 'pending',
    'balance' => $userBalance
]);
?>

Фронтенд-проверка и интерфейс

На фронтенде запускается интервал, который при получении статуса paid не просто закрывает модальное окно, но и мгновенно обновляет сумму баланса в шапке личного кабинета без перезагрузки всей страницы:

let checkInterval = setInterval(function() {
    $.post('/ajax/check_binance_payment.php', { orderId: currentOrderId }, function(res) {
        if (res.status === 'paid') {
            clearInterval(checkInterval);
            $('#modal-pay-binance').fadeOut();
            $('.user-balance-value').text(res.balance + ' USDT'); // Динамическое обновление UI
            alert('Баланс успешно пополнен!');
        }
    }, 'json');
}, 3000);

Если в личном кабинете медленно отрабатывают скрипты или зависают асинхронные запросы, проблему нужно искать в общей оптимизации базы данных. В таких случаях мы рекомендуем провести независимый технический аудит сайта и производительности.

Типовые ошибки при интеграции Binance Pay

  • Использование кастомных полей баланса: порождает Race Condition, затирание данных и лишает проект прозрачной истории аудита транзакций.
  • Незащищенный AJAX-endpoint: отсутствие валидации $USER->GetID() в запросах проверки статуса позволяет хакерам сканировать чужие транзакции перебором параметров.
  • Игнорирование идемпотентности: доверие повторным вебхукам без сверки флага UF_PAID приводит к дублированию зачислений.
  • Неверный тип товара: отправка goodsType: "01" (физический товар) вместо "02" при пополнении виртуального счета.

Что проверить после внедрения

  1. Валидность формирования HMAC-SHA512 подписи во всех типах запросов.
  2. Изоляцию скрипта обработки вебхуков от выполнения вне протокола SSL/TLS.
  3. Корректность обработки флага UF_PAID при намеренном дублировании входящего POST-пакета.
  4. Отображение начисленных средств в админ-панели Битрикса в меню «Покупатели» -> «Внутренние счета».

Когда необходим внутренний счет

Каноническое пополнение баланса незаменимо в маркетплейсах, SaaS-платформах с моделью Pay-as-you-go, закрытых B2B-порталах, а также на сайтах с платным поштучным доступом к контенту или функциям. Посмотреть, как подобные задачи автоматизации финтех-сценариев разворачиваются на практике, можно в нашем разделе примеры работ.

Почему в финтехе нет места «костылям»

Любая архитектурная ошибка в платежном коде — это прямые финансовые и репутационные убытки бизнеса. Специфика кастомных доработок на Битрикс требует глубокого знания ядра D7 и стандартов безопасности данных.

В AG Studio интеграция платежных шлюзов проектируется с учетом пиковых нагрузок, защиты от сетевых сбоев и строгих требований систем интернет-эквайринга. Ознакомиться с нашими стандартами разработки можно на странице о студии.

Итог

Правильная интеграция Binance Pay v3 в 1С-Битрикс делегирует хранение баланса и логов штатному и безопасному механизму CSaleUserAccount. Highload-блок при этом выполняет исключительно диспетчерскую функцию контроля статуса транзакций, обеспечивая абсолютную защиту от фреймворк-ошибок.

Если вам требуется бесшовная и безопасная интеграция платежных систем, настройка вебхуков Binance Pay v3 или перевод кастомного баланса личного кабинета на надежные рельсы ядра Битрикс, свяжитесь с нами через страницу контактов.