Интеграция Binance Pay v3 в Битрикс: создание платежа, webhook и каноническое пополнение баланса
Интеграция Binance Pay с сайтом на 1С-Битрикс может использоваться не только для оплаты обычных заказов, но и для пополнения внутреннего баланса пользователя. Такой сценарий часто встречается в личных кабинетах, сервисах, маркетплейсах, форумах с платными функциями, B2B-порталах и проектах, где пользователь заранее вносит средства, а потом расходует их внутри сайта.
На первый взгляд задача выглядит просто: пользователь ввел сумму, оплатил через Binance Pay, а сайт увеличил его баланс. Но в реальном продакшене финтех-логика не прощает халатных решений. Чтобы защитить проект от «состояния гонки» (Race Condition), фейковых оплат и утечки данных, необходима профессиональная интеграция платежных систем на базе канонической архитектуры ядра самого Битрикса.
Такие задачи относятся к сложной кастомной разработке на PHP и Битрикс. Если вам нужно подключить криптовалютные платежи, глубоко доработать личный кабинет или связать внешние шлюзы с внутренним счетом магазина, это можно реализовать в рамках услуги доработки Bitrix и PHP-задачи.
Как работает безопасная схема пополнения баланса
Весь процесс интеграции Binance Pay v3 строится на строгом асинхронном взаимодействии сервер-сервер:
- Пользователь вводит сумму пополнения в личном кабинете.
- Сервер создает платежный ордер в Binance Pay.
- Binance возвращает данные для оплаты: безопасную ссылку, QR-код или deeplink.
- Пользователь совершает транзакцию через приложение или платежную страницу Binance.
- Binance отправляет подписанный асимметричным ключом webhook на сервер сайта.
- Сервер валидирует подпись, проверяет статус платежа и находит внутреннюю техническую запись.
- Если платеж успешен и ранее не обрабатывался, баланс пользователя атомарно увеличивается на уровне СУБД.
- Фронтенд через защищенный 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"при пополнении виртуального счета.
Что проверить после внедрения
- Валидность формирования HMAC-SHA512 подписи во всех типах запросов.
- Изоляцию скрипта обработки вебхуков от выполнения вне протокола SSL/TLS.
- Корректность обработки флага
UF_PAIDпри намеренном дублировании входящего POST-пакета. - Отображение начисленных средств в админ-панели Битрикса в меню «Покупатели» -> «Внутренние счета».
Когда необходим внутренний счет
Каноническое пополнение баланса незаменимо в маркетплейсах, SaaS-платформах с моделью Pay-as-you-go, закрытых B2B-порталах, а также на сайтах с платным поштучным доступом к контенту или функциям. Посмотреть, как подобные задачи автоматизации финтех-сценариев разворачиваются на практике, можно в нашем разделе примеры работ.
Почему в финтехе нет места «костылям»
Любая архитектурная ошибка в платежном коде — это прямые финансовые и репутационные убытки бизнеса. Специфика кастомных доработок на Битрикс требует глубокого знания ядра D7 и стандартов безопасности данных.
В AG Studio интеграция платежных шлюзов проектируется с учетом пиковых нагрузок, защиты от сетевых сбоев и строгих требований систем интернет-эквайринга. Ознакомиться с нашими стандартами разработки можно на странице о студии.
Итог
Правильная интеграция Binance Pay v3 в 1С-Битрикс делегирует хранение баланса и логов штатному и безопасному механизму CSaleUserAccount. Highload-блок при этом выполняет исключительно диспетчерскую функцию контроля статуса транзакций, обеспечивая абсолютную защиту от фреймворк-ошибок.
Если вам требуется бесшовная и безопасная интеграция платежных систем, настройка вебхуков Binance Pay v3 или перевод кастомного баланса личного кабинета на надежные рельсы ядра Битрикс, свяжитесь с нами через страницу контактов.