Интеграция Multicard с 1С-Битрикс D7: инвойс, ссылка на оплату, webhook и сумма в тийинах
Подключение платежной системы к 1С-Битрикс — это не просто добавить кнопку “Оплатить”.
В реальном проекте нужно создать инвойс на стороне банка или платежного сервиса, получить ссылку на оплату,
показать её пользователю, принять webhook, проверить подпись и только после этого корректно перевести оплату
в статус PAID=Y внутри Битрикс.
В этой статье разберем пример интеграции Multicard с 1С-Битрикс через D7-платежную систему:
создание инвойса через POST /payment/invoice, получение checkout_url,
обработку callback/webhook, проверку sign и важный момент с суммой в тийинах.
Такая задача относится к полноценной технической доработке интернет-магазина или сервиса на Битрикс. Здесь важно не только отправить запрос в API, но и правильно встроить оплату в логику заказа, платежей, статусов и уведомлений. Похожие задачи мы выполняем в рамках услуги доработки Bitrix и PHP-задачи.
Что нужно реализовать
В базовом сценарии интеграция Multicard с 1С-Битрикс состоит из трех основных этапов:
- Создать инвойс в Multicard через
POST /payment/invoiceи получить ссылкуcheckout_url. - Показать пользователю кнопку или ссылку для перехода на оплату.
- Принять webhook от Multicard, проверить подпись и отметить оплату в Битрикс как
PAID=Y.
На первый взгляд логика выглядит простой. Но в реальной интеграции есть несколько критичных деталей: сумма передается не в сумах, а в тийинах, webhook должен быть защищен проверкой подписи, а оплата в Битрикс должна ставиться не по факту возврата пользователя на сайт, а после подтверждения от платежной системы.
Главный нюанс: amount передается в тийинах
Один из самых важных моментов при работе с Multicard — поле amount.
В API Multicard сумма передается в тийинах, а не в сумах.
То есть:
- 1 сум = 100 тийин;
- 100 сум = 10000 тийин;
- 15000 сум = 1500000 тийин.
Если передать в amount сумму заказа напрямую из Битрикс, платеж может быть создан на неправильную сумму.
Поэтому перед отправкой в Multicard сумму нужно умножать на 100.
private function amountToTiyin(Payment $payment): int
{
$sumUzs = (float)$payment->getSum();
$tiyin = (int)round($sumUzs * 100);
if ($tiyin < 10000) {
$tiyin = 10000;
}
return $tiyin;
}
Это хороший пример того, почему подключение онлайн-оплаты нельзя делать “на глаз”. Даже небольшая ошибка в формате суммы, валюте или статусе может привести к некорректным платежам.
Какие настройки нужны в админке Битрикс
Для платежной системы лучше вынести все параметры в настройки обработчика. Это позволит не хранить доступы прямо в коде и удобно переключаться между тестовым и боевым окружением.
MC_BASE_URL— базовый URL Multicard для sandbox или production;MC_APP_ID— идентификатор приложения;MC_APP_SECRET— секрет приложения;MC_STORE_ID— ID магазина;MC_LANG— язык платежной страницы;MC_RETURN_SUCCESS_URL— страница успешной оплаты;MC_RETURN_ERROR_URL— страница ошибки оплаты;MC_RETURN_BACK_URL— страница возврата в магазин;MC_CALLBACK_URL— URL webhook-обработчика;MC_ALLOWED_IP— IP, с которого разрешены callback-запросы.
Такой подход особенно важен для интернет-магазинов и сервисов, где платежная интеграция должна быть управляемой: без ручного редактирования файлов при смене ключей, домена, callback-адреса или тестового режима.
Структура обработчика платежной системы D7
Обработчик платежной системы можно разместить по пути:
/local/php_interface/include/sale_payment/multicard/handler.php
В нем описываются настройки платежной системы, получение токена, создание инвойса, вывод шаблона оплаты и обработка входящего запроса от Multicard.
Ниже пример структуры обработчика:
<?php
namespace Sale\Handlers\PaySystem;
use Bitrix\Main\Error;
use Bitrix\Main\Localization\Loc;
use Bitrix\Main\Request;
use Bitrix\Main\Type\DateTime;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json;
use Bitrix\Sale\PaySystem;
use Bitrix\Sale\Payment;
Loc::loadMessages(__FILE__);
class MulticardHandler extends PaySystem\ServiceHandler
{
public static function getHandlerDescription()
{
return [
'NAME' => 'Multicard',
'SORT' => 200,
'CODES' => [
'MC_BASE_URL' => [
'NAME' => 'Base URL',
'DESCRIPTION' => 'https://dev-mesh.multicard.uz/ или https://mesh.multicard.uz/',
'SORT' => 100
],
'MC_APP_ID' => [
'NAME' => 'application_id',
'DESCRIPTION' => 'Идентификатор приложения',
'SORT' => 110
],
'MC_APP_SECRET' => [
'NAME' => 'secret',
'DESCRIPTION' => 'Секрет приложения',
'SORT' => 120
],
'MC_STORE_ID' => [
'NAME' => 'store_id',
'DESCRIPTION' => 'ID магазина',
'SORT' => 130
],
'MC_LANG' => [
'NAME' => 'lang',
'DESCRIPTION' => 'ru/en/uz',
'SORT' => 140
],
'MC_RETURN_SUCCESS_URL' => [
'NAME' => 'URL успеха',
'DESCRIPTION' => 'https://site.ru/payment/success/',
'SORT' => 200
],
'MC_RETURN_ERROR_URL' => [
'NAME' => 'URL ошибки',
'DESCRIPTION' => 'https://site.ru/payment/error/',
'SORT' => 210
],
'MC_RETURN_BACK_URL' => [
'NAME' => 'URL вернуться в магазин',
'DESCRIPTION' => 'https://site.ru/payment/cancel/',
'SORT' => 220
],
'MC_CALLBACK_URL' => [
'NAME' => 'callback_url',
'DESCRIPTION' => 'https://site.ru/local/tools/multicard_webhook.php',
'SORT' => 230
],
'MC_ALLOWED_IP' => [
'NAME' => 'IP Multicard',
'DESCRIPTION' => '195.158.26.90',
'SORT' => 240
],
],
];
}
protected function getTemplatePath()
{
return '/local/php_interface/include/sale_payment/multicard/template/template.php';
}
private function amountToTiyin(Payment $payment): int
{
$sumUzs = (float)$payment->getSum();
$tiyin = (int)round($sumUzs * 100);
if ($tiyin < 10000) {
$tiyin = 10000;
}
return $tiyin;
}
}
Вынесение логики в отдельный D7-обработчик делает интеграцию понятнее и безопаснее. Не нужно размазывать платежные запросы по компонентам, шаблонам или кастомным страницам.
Получение токена Multicard
Перед созданием инвойса нужно получить токен через POST /auth.
Для этого используются application_id и secret, которые хранятся в настройках
платежной системы.
private function getToken(Payment $payment): string
{
$baseUrl = rtrim((string)$this->getBusinessValue($payment, 'MC_BASE_URL'), '/');
$appId = (string)$this->getBusinessValue($payment, 'MC_APP_ID');
$secret = (string)$this->getBusinessValue($payment, 'MC_APP_SECRET');
if ($baseUrl === '' || $appId === '' || $secret === '') {
throw new \RuntimeException('Multicard: заполните MC_BASE_URL, MC_APP_ID, MC_APP_SECRET');
}
$http = new HttpClient();
$http->setHeader('Content-Type', 'application/json');
$body = Json::encode([
'application_id' => $appId,
'secret' => $secret
], JSON_UNESCAPED_UNICODE);
$http->post($baseUrl . '/auth', $body);
$status = (int)$http->getStatus();
if ($status < 200 || $status >= 300) {
throw new \RuntimeException('Multicard /auth HTTP ' . $status . ': ' . $http->getResult());
}
$resp = json_decode((string)$http->getResult(), true) ?: [];
$token = (string)($resp['token'] ?? ($resp['data']['token'] ?? ''));
if ($token === '') {
throw new \RuntimeException('Multicard: token not found: ' . $http->getResult());
}
return $token;
}
На этом этапе важно логировать ответы API. Если токен не приходит, нужно видеть не только “ошибка оплаты”, а конкретный HTTP-статус и тело ответа Multicard. Это сильно ускоряет отладку.
Создание инвойса и получение checkout_url
После получения токена можно создавать инвойс через POST /payment/invoice.
В запрос передаются ID магазина, сумма в тийинах, внутренний ID инвойса, язык, URL возврата и callback URL.
private function createInvoice(Payment $payment, array $payload): array
{
$baseUrl = rtrim((string)$this->getBusinessValue($payment, 'MC_BASE_URL'), '/');
$token = $this->getToken($payment);
$http = new HttpClient();
$http->setHeader('Authorization', 'Bearer ' . $token);
$http->setHeader('Content-Type', 'application/json');
$http->post(
$baseUrl . '/payment/invoice',
Json::encode($payload, JSON_UNESCAPED_UNICODE)
);
$status = (int)$http->getStatus();
if ($status < 200 || $status >= 300) {
throw new \RuntimeException('Multicard /payment/invoice HTTP ' . $status . ': ' . $http->getResult());
}
$resp = json_decode((string)$http->getResult(), true) ?: [];
if (isset($resp['success']) && $resp['success'] === false) {
$code = $resp['error']['code'] ?? 'ERROR';
$details = $resp['error']['details'] ?? 'Unknown';
throw new \RuntimeException("Multicard invoice error {$code}: {$details}");
}
return (array)($resp['data'] ?? $resp);
}
В ответе нужно получить как минимум uuid и checkout_url.
uuid удобно сохранить в поле PS_INVOICE_ID, а ссылку на оплату передать в шаблон.
Создание платежа в initiatePay
Метод initiatePay отвечает за запуск оплаты. В нем мы берем заказ, платеж, настройки обработчика,
формируем payload для Multicard, создаем инвойс и показываем пользователю ссылку на оплату.
public function initiatePay(Payment $payment, ?Request $request = null)
{
$result = new PaySystem\ServiceResult();
try {
$order = $payment->getOrder();
$storeId = (int)$this->getBusinessValue($payment, 'MC_STORE_ID');
if ($storeId <= 0) {
$result->addError(new Error('Multicard: заполните MC_STORE_ID'));
return $result;
}
$lang = (string)$this->getBusinessValue($payment, 'MC_LANG');
if (!in_array($lang, ['ru', 'en', 'uz'], true)) {
$lang = 'ru';
}
$invoiceId = (string)$payment->getId();
$amount = $this->amountToTiyin($payment);
$successUrl = (string)$this->getBusinessValue($payment, 'MC_RETURN_SUCCESS_URL');
$errorUrl = (string)$this->getBusinessValue($payment, 'MC_RETURN_ERROR_URL');
$backUrl = (string)$this->getBusinessValue($payment, 'MC_RETURN_BACK_URL');
$callbackUrl = (string)$this->getBusinessValue($payment, 'MC_CALLBACK_URL');
if ($callbackUrl === '') {
$callbackUrl = 'https://' . (string)$_SERVER['HTTP_HOST'] . '/local/tools/multicard_webhook.php';
}
$payload = [
'store_id' => $storeId,
'amount' => $amount,
'invoice_id' => $invoiceId,
'lang' => $lang,
'return_url' => $successUrl,
'return_error_url' => $errorUrl,
'return_back_url' => $backUrl,
'callback_url' => $callbackUrl,
];
$data = $this->createInvoice($payment, $payload);
$uuid = (string)($data['uuid'] ?? '');
$checkoutUrl = (string)(
$data['checkout_url']
?? ($data['invoice']['checkout_url'] ?? '')
?? ($data['short_link'] ?? '')
);
if ($uuid === '' || $checkoutUrl === '') {
throw new \RuntimeException('Multicard: uuid/checkout_url not found');
}
$payment->setField('PS_INVOICE_ID', $uuid);
$payment->setField(
'PS_STATUS_DESCRIPTION',
Json::encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
$order->save();
$this->setExtraParams([
'CHECKOUT_URL' => $checkoutUrl,
'SHORT_LINK' => (string)($data['short_link'] ?? ''),
'DEEPLINK' => (string)($data['deeplink'] ?? ''),
'INVOICE_ID' => $invoiceId,
'UUID' => $uuid,
'AMOUNT_TIYIN' => (int)($data['amount'] ?? $amount),
]);
return $this->showTemplate($payment, 'template');
} catch (\Throwable $e) {
$result->addError(new Error($e->getMessage()));
return $result;
}
}
Важно не считать оплату успешной на этом этапе. Здесь мы только создали инвойс и получили ссылку. Реальная оплата должна подтверждаться через webhook или дополнительную проверку статуса.
Это частая ошибка при подключении онлайн-оплаты: разработчик переводит заказ в оплаченный статус после редиректа пользователя, хотя платеж еще не подтвержден платежной системой. В нормальной интеграции заказ должен становиться оплаченным только после серверного подтверждения.
Шаблон кнопки оплаты
После создания инвойса пользователю нужно показать ссылку или кнопку для перехода на оплату. Шаблон можно разместить по пути:
/local/php_interface/include/sale_payment/multicard/template/template.php
<?php
use Bitrix\Main\Localization\Loc;
if (!defined("B_PROLOG_INCLUDED") || B_PROLOG_INCLUDED !== true) {
die();
}
Loc::loadMessages(__FILE__);
$extra = $this->getExtraParams();
$checkoutUrl = (string)($extra['CHECKOUT_URL'] ?? '');
$shortLink = (string)($extra['SHORT_LINK'] ?? '');
$deeplink = (string)($extra['DEEPLINK'] ?? '');
$invoiceId = (string)($extra['INVOICE_ID'] ?? '');
$uuid = (string)($extra['UUID'] ?? '');
$amountTiyin = (int)($extra['AMOUNT_TIYIN'] ?? 0);
$amountUzs = $amountTiyin > 0 ? number_format($amountTiyin / 100, 2, '.', ' ') : '';
?>
<h2><?= Loc::getMessage('MC_TITLE') ?></h2>
<p>
<b><?= Loc::getMessage('MC_INVOICE') ?>:</b>
<?= htmlspecialcharsbx($invoiceId) ?>
</p>
<p>
<b>UUID:</b>
<?= htmlspecialcharsbx($uuid) ?>
</p>
<p>
<b><?= Loc::getMessage('MC_AMOUNT') ?>:</b>
<?= htmlspecialcharsbx($amountUzs) ?> UZS
</p>
<p>
<a href="<?= htmlspecialcharsbx($checkoutUrl) ?>" target="_blank" rel="noopener">
<?= Loc::getMessage('MC_PAY') ?>
</a>
</p>
<?php if ($deeplink): ?>
<p>
<a href="<?= htmlspecialcharsbx($deeplink) ?>" target="_blank" rel="noopener">
<?= Loc::getMessage('MC_OPEN_APP') ?>
</a>
</p>
<?php endif; ?>
<?php if ($shortLink): ?>
<p>
<?= Loc::getMessage('MC_ALT') ?>:
<a href="<?= htmlspecialcharsbx($shortLink) ?>" target="_blank" rel="noopener">
<?= htmlspecialcharsbx($shortLink) ?>
</a>
</p>
<?php endif; ?>
В рабочем проекте эту кнопку можно оформить под дизайн сайта: сделать заметный CTA-блок, вывести сумму, номер заказа, запасную ссылку и подсказку для пользователя.
Если платежная кнопка плохо отображается на мобильных, не вписывается в оформление корзины или сбивает процесс оформления заказа, это уже задача на доработку шаблона оплаты и оформление пользовательского сценария в Битрикс.
Языковые файлы для шаблона
Для нормальной поддержки нескольких языков текст кнопки и подписей лучше вынести в языковые файлы.
Русский файл:
/local/php_interface/include/sale_payment/multicard/template/lang/ru/template.php
<?php
$MESS['MC_TITLE'] = 'Оплата через Multicard';
$MESS['MC_INVOICE'] = 'Инвойс';
$MESS['MC_AMOUNT'] = 'Сумма';
$MESS['MC_PAY'] = 'Перейти к оплате';
$MESS['MC_OPEN_APP'] = 'Открыть в приложении';
$MESS['MC_ALT'] = 'Запасная ссылка';
Английский файл:
/local/php_interface/include/sale_payment/multicard/template/lang/en/template.php
<?php
$MESS['MC_TITLE'] = 'Pay via Multicard';
$MESS['MC_INVOICE'] = 'Invoice';
$MESS['MC_AMOUNT'] = 'Amount';
$MESS['MC_PAY'] = 'Proceed to payment';
$MESS['MC_OPEN_APP'] = 'Open in app';
$MESS['MC_ALT'] = 'Alternative link';
Такой подход особенно удобен для мультиязычных сайтов, где корзина, оформление заказа и платежные сценарии должны корректно работать на разных языках.
Получение Payment ID из webhook
В этой схеме invoice_id равен ID платежа в Битрикс. Поэтому из webhook можно получить
Payment ID напрямую:
public function getPaymentIdFromRequest(Request $request)
{
$input = json_decode((string)$request->getInput(), true);
return isset($input['invoice_id']) ? (int)$input['invoice_id'] : null;
}
Это упрощает поиск нужного платежа, но требует аккуратности: нельзя доверять входящему запросу без проверки подписи.
Обработка webhook внутри handler
Входящий webhook должен проверять обязательные поля, подпись и статус оплаты.
После проверки обработчик заполняет PS_* поля платежа.
public function processRequest(Payment $payment, Request $request)
{
$result = new PaySystem\ServiceResult();
$input = json_decode((string)$request->getInput(), true);
if (!is_array($input)) {
$result->addError(new Error('Invalid JSON'));
return $result;
}
$uuid = (string)($input['uuid'] ?? ($input['invoice_uuid'] ?? ''));
$invoiceId = (string)($input['invoice_id'] ?? '');
$amount = (string)($input['amount'] ?? '');
$sign = (string)($input['sign'] ?? '');
if ($uuid === '' || $invoiceId === '' || $amount === '' || $sign === '') {
$result->addError(new Error('Missing fields'));
return $result;
}
$secret = (string)$this->getBusinessValue($payment, 'MC_APP_SECRET');
$str = $uuid . $invoiceId . $amount . $secret;
$sha1 = sha1($str);
$md5 = md5($str);
if (!hash_equals($sha1, $sign) && !hash_equals($md5, $sign)) {
$result->addError(new Error('Bad sign'));
return $result;
}
$status = (string)($input['status'] ?? '');
$isPaid = ($status === 'success') || ($status === '' && !empty($input['payment_time']));
$result->setPsData([
'PS_INVOICE_ID' => $uuid,
'PS_STATUS' => $isPaid ? 'Y' : 'N',
'PS_STATUS_CODE' => $status ?: ($isPaid ? 'success' : 'progress'),
'PS_STATUS_DESCRIPTION' => json_encode($input, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'PS_RESPONSE_DATE' => new DateTime(),
]);
return $result;
}
Проверка подписи — обязательная часть интеграции. Без неё любой внешний запрос мог бы попытаться изменить статус оплаты. Поэтому webhook нельзя делать как обычный публичный URL без валидации.
Публичный webhook, который ставит PAID=Y
Сам webhook-файл можно разместить по адресу:
/local/tools/multicard_webhook.php
Он принимает JSON, проверяет IP, находит платеж по invoice_id, вызывает обработчик платежной системы,
сохраняет ответ и при успешном статусе переводит платеж в PAID=Y.
<?php
define('NO_KEEP_STATISTIC', true);
define('NOT_CHECK_PERMISSIONS', true);
require($_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php');
use Bitrix\Main\Context;
use Bitrix\Main\Web\Json;
use Bitrix\Sale\Order;
use Bitrix\Sale\PaySystem\Manager as PaySystemManager;
use Bitrix\Sale\Internals\PaymentTable;
header('Content-Type: application/json; charset=utf-8');
function reply(bool $ok, string $msg = ''): void
{
http_response_code(200);
echo Json::encode(
$ok ? ['success' => true] : ['success' => false, 'message' => $msg],
JSON_UNESCAPED_UNICODE
);
exit;
}
$request = Context::getCurrent()->getRequest();
$raw = (string)$request->getInput();
if ($raw === '') {
$raw = (string)file_get_contents('php://input');
}
$input = json_decode($raw, true);
if (!is_array($input)) {
reply(false, 'Invalid JSON');
}
$remoteIp = (string)($_SERVER['REMOTE_ADDR'] ?? '');
if ($remoteIp !== '195.158.26.90') {
reply(false, 'Forbidden IP');
}
$paymentId = (int)($input['invoice_id'] ?? 0);
if ($paymentId <= 0) {
reply(false, 'Missing invoice_id');
}
$row = PaymentTable::getById($paymentId)->fetch();
if (!$row) {
reply(false, 'Payment not found');
}
$order = Order::load((int)$row['ORDER_ID']);
if (!$order) {
reply(false, 'Order not found');
}
$payment = null;
foreach ($order->getPaymentCollection() as $p) {
if ((int)$p->getId() === $paymentId) {
$payment = $p;
break;
}
}
if (!$payment) {
reply(false, 'Payment object not found');
}
$service = PaySystemManager::getObjectById($payment->getPaymentSystemId());
if (!$service) {
reply(false, 'PaySystem service not found');
}
$result = $service->processRequest($request);
if (!$result->isSuccess()) {
$errors = $result->getErrors();
$msg = $errors ? $errors[0]->getMessage() : 'Unknown error';
reply(false, $msg);
}
$inputStatus = (string)($input['status'] ?? '');
if ($inputStatus === '') {
$inputStatus = !empty($input['payment_time']) ? 'success' : 'progress';
}
$payment->setField('PS_STATUS_CODE', $inputStatus);
$payment->setField(
'PS_STATUS_DESCRIPTION',
json_encode($input, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
$payment->setField('PS_INVOICE_ID', (string)($input['uuid'] ?? ($input['invoice_uuid'] ?? '')));
$payment->setField('PS_RESPONSE_DATE', new \Bitrix\Main\Type\DateTime());
$isPaid = (!empty($input['status']) && $input['status'] === 'success')
|| (empty($input['status']) && !empty($input['payment_time']));
if ($isPaid && !$payment->isPaid()) {
$payment->setPaid('Y');
}
$order->save();
reply(true);
Важно: сам webhook должен быть доступен извне. Если сайт закрыт Basic Auth, Cloudflare, firewall-правилами или нестандартной защитой, Multicard может не достучаться до callback URL.
Почему нельзя ставить PAID=Y только по return_url
URL возврата нужен для пользователя: показать страницу успеха, ошибки или возврата в магазин. Но он не должен быть единственным источником истины для оплаты.
Пользователь может попасть на return URL разными способами:
- после успешной оплаты;
- после отмены платежа;
- после ошибки на стороне банка;
- после повторного открытия ссылки;
- вручную, если URL известен.
Поэтому статус PAID=Y должен ставиться только после серверного подтверждения:
webhook, проверка подписи, валидный статус и сохранение ответа платежной системы.
Именно такие детали отличают рабочую интеграцию платежной системы от временного решения, которое может привести к потерянным заказам или некорректным статусам.
Что нужно проверить после подключения
После внедрения Multicard важно пройти весь сценарий оплаты от начала до конца, а не ограничиваться проверкой создания ссылки.
- Проверить, что при создании инвойса
amountуходит в тийинах. - Убедиться, что в ответе Multicard есть
uuidиcheckout_url. - Проверить, что ссылка на оплату открывается для пользователя.
- Проверить, что
return_url,return_error_urlиreturn_back_urlведут на нужные страницы. - Проверить, что webhook приходит на
/local/tools/multicard_webhook.php. - Проверить, что подпись
signвалидируется корректно. - Проверить, что после успешной оплаты платеж в Битрикс получает
PAID=Y. - Проверить, что заказ, письмо, склад, доставка или дальнейшая бизнес-логика срабатывают корректно.
Если после оплаты заказ не меняет статус, не уходит письмо, не обновляется склад или не запускается дальнейший процесс обработки, значит проблема может быть уже не в Multicard, а во внутренней логике сайта.
Типовые ошибки при интеграции Multicard с Битрикс
Сумма передается не в тийинах
Это одна из самых частых ошибок. В Битрикс сумма платежа хранится в основной валюте, а Multicard ожидает сумму в минимальных единицах. Поэтому значение нужно умножать на 100.
Webhook не доходит до сайта
Причиной может быть закрытый URL, неправильный домен, HTTPS-проблемы, firewall, запрет по IP или ошибка в пути к файлу обработчика.
Подпись не проходит проверку
Нужно проверить порядок склейки строки, используемый secret, формат суммы и алгоритм подписи.
В некоторых случаях приходится учитывать разные варианты подписи, например sha1 и md5,
если это встречается на отдельных стендах.
Оплата прошла, но заказ не стал оплаченным
В этом случае нужно смотреть, нашелся ли платеж по invoice_id, вызвался ли processRequest,
сохранился ли заказ и не возникла ли ошибка при $payment->setPaid('Y').
Пользователь оплатил, но не получил письмо
Это уже может быть связано с внутренней логикой Битрикс: событиями, почтовыми шаблонами, статусами заказа, обработчиками, агентами или кастомной логикой после оплаты.
Такие ситуации часто решаются не настройкой платежной системы, а исправлением логики заказа, почтовых уведомлений и PHP-обработчиков в Битрикс.
Почему платежные интеграции лучше делать аккуратно
Платежная система связана не только с деньгами. Она влияет на корзину, заказ, статусы, склад, уведомления, доставку, личный кабинет и работу менеджеров.
Поэтому при подключении Multicard важно продумать:
- как создается платеж;
- какой ID используется как
invoice_id; - куда сохраняется
uuid; - как проверяется подпись;
- как обрабатываются повторные webhook-запросы;
- что происходит, если заказ уже оплачен;
- как логируются ошибки API;
- какие действия запускаются после
PAID=Y.
Если сайт уже давно работает, в нем могут быть старые доработки, нестандартные обработчики, особая логика статусов или кастомное оформление заказа. В таких случаях перед подключением новой оплаты полезно провести техническую проверку сайта и окружения, чтобы заранее увидеть риски.
Когда такая доработка нужна бизнесу
Интеграция Multicard может потребоваться интернет-магазину, сервису онлайн-заказов, B2B-порталу, сайту с личным кабинетом или проекту, где нужно принимать оплату в UZS и автоматически обновлять заказы после платежа.
Обычно такая задача идет вместе с другими доработками:
- настроить онлайн-оплату картами;
- добавить новый платежный способ в корзину;
- исправить ошибки после оплаты;
- доработать оформление заказа;
- починить уведомления клиенту и менеджеру;
- связать оплату со статусами заказа;
- добавить логирование платежных запросов;
- подключить CRM или внутреннюю систему учета.
Посмотреть похожие технические задачи и реализованные проекты можно в разделе примеры работ.
Итог
Интеграция Multicard с 1С-Битрикс через D7 строится вокруг понятной схемы: создать инвойс, получить ссылку на оплату, показать её пользователю, принять webhook, проверить подпись и только после этого отметить платеж как оплаченный.
Ключевые моменты, на которые нужно обратить внимание: сумма передается в тийинах, webhook должен быть защищен,
PAID=Y нельзя ставить только по возврату пользователя, а ответы платежной системы нужно сохранять
для диагностики.
Если нужно подключить Multicard, исправить оплату в Битрикс, доработать обработчик платежной системы или разобраться, почему заказ не становится оплаченным после платежа, можно обратиться через страницу контактов.