Sale 26.450.0: «Сбербанк Онлайн» переехал на новый шлюз и сломал наследников

4 мин чтения Устаревших API: 11

Ломающее обновление

Удалены или изменены публичные API — прикладной код может перестать работать.

Весь sale 26.450.0 посвящён одному обработчику: SberbankOnlineHandler переписан с нуля под новый шлюз Сбера. Схема БД и ядро модуля не тронуты, дифф небольшой (11 файлов, +1195/−604), и магазинам без Сбера и Альфы можно обновляться спокойно. Если же магазин принимает оплату через Сбер или у вас в /local/ живёт наследник этого обработчика, после обновления надо руками проверить, что деньги и уведомления доходят.

Что сломается

Сбер теперь ходит на другой шлюз

Старые адреса securepayments.sberbank.ru/payment/ и 3dsec.sberbank.ru/payment/ из обработчика ушли. Запросы идут на https://epay.sberbank.ru/ecomm/gw/partner/api/v1/<action>, в тестовом режиме на ecomtest.sberbank.ru. Покупателя отправляют платить на https://payecom.ru/pay_ru?orderId=... (тест: sbox.payecom.ru). Действий три: register.do, getOrderStatusExtended.do и refund.do, тело запроса уходит как JSON.

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

Секретного ключа больше нет, подпись колбэка не проверяется

Из .description.php удалена настройка SBERBANK_SECRET_KEY, а вместе с ней метод isCheckSumCorrect(). Поле пропадёт из формы платёжной системы в админке. Сохранённое значение при этом никто не чистит: миграции в диффе нет, новый код его не читает.

Подлинность уведомления теперь проверяется иначе. processRequest() сверяет mdOrder с PS_INVOICE_ID платежа, номер заказа, operation === 'deposited' и status = 1. Встречный запрос в банк был и раньше, старый processRequest() тоже ходил в getOrderStatus(). Сумму по его ответу сверяли и раньше. Теперь к ней добавились номер заказа, валюта, mdOrder в атрибутах ответа getOrderStatusExtended.do и orderStatus = 2. Если запрос не прошёл или хоть одно поле не совпало, заказ останется неоплаченным, даже когда деньги списались.

Уведомление принимается только как POST с JSON

Старый обработчик читал параметры через $request->get('operation'), get('mdOrder') и так далее. Новый приватный getNotification() принимает только POST с JSON-телом. На всё остальное он возвращает пустой массив, и обработка на этом заканчивается.

Если колбэк приходит GET-запросом или POST-формой, а не JSON, isMyResponse() вернёт false и уведомление не обработается. Статусы оплат перестанут обновляться, и менеджеры увидят «оплаченные, но не оплаченные» заказы. Самописный прокси или тестовый скрипт, который дёргает URL уведомления, тоже придётся переделать под JSON.

Наследники SberbankOnlineHandler упадут

Из класса удалены:

  • константы PAYMENT_OPERATION_DEPOSITED, PAYMENT_STATUS_SUCCESS, RESPONSE_CODE_SUCCESS, PAYMENT_STATE_CREATED, PAYMENT_DELIMITER;
  • методы isCheckSumCorrect(), registerOrder(), getMerchantParams(), getRegisterOrderParams(), getDescriptionCode(), getDescriptionCodesMap();
  • переопределения getIndicativeFields() и isMyResponseExtended(). Базовые версии в ServiceHandler остались (возвращают [] и true), но новый public static function isMyResponse(Request $request, $paySystemId): bool их не вызывает, поэтому переопределять их в наследнике бесполезно.

Класс в /local/php_interface/include/sale_payment/, который наследует SberbankOnlineHandler и, например, переопределяет getRegisterOrderParams(), чтобы дописать свои поля в заказ, после обновления поведёт себя плохо в любом варианте. Обращение к удалённой константе или вызов parent::registerOrder() даст фатал. Переопределённый метод, который родитель больше не вызывает, перестанет работать, и ваши доработки молча исчезнут из запроса к банку.

У методов появились возвращаемые типы: static isMyResponse(Request $request, $paySystemId): bool (раньше класс наследовал его от ServiceHandler, теперь переопределяет сам), getPaymentIdFromRequest(Request $request): string, getUrlList(): array, getOrderDescription(Payment $payment): string. У isMyResponse() в базовом ServiceHandler типа нет, поэтому наследник, который переопределял его по старой сигнатуре без : bool, тоже упадёт. Переопределение без типа или с другим типом даёт фатал несовместимости сигнатур, и падает он при подключении файла, ещё до оплаты.

Рабочее переопределение теперь выглядит так:

        <?php declare(strict_types=1);

namespace Sale\Handlers\PaySystem;

use Bitrix\Main\Loader;
use Bitrix\Sale\Payment;
use Bitrix\Sale\PaySystem;

Loader::includeModule('sale');
PaySystem\Manager::includeHandler('SberbankOnline');

final class ShopSberbankHandler extends SberbankOnlineHandler
{
    protected function getOrderDescription(Payment $payment): string
    {
        $accountNumber = (string)$payment->getOrder()->getField('ACCOUNT_NUMBER');

        return 'Оплата заказа №' . $accountNumber;
    }
}

    

Альфа-Банк больше не Сбер

Раньше AlfaBankHandler наследовался от SberbankOnlineHandler. Теперь у него новый родитель AlfabankLegacyHandler, файл alfabank/legacyhandler.php подключается прямым require_once. По составу методов и констант это прежняя реализация Сбера: её вынесли отдельно, чтобы Альфа продолжила работать по старому протоколу.

Для самой оплаты через Альфу ничего не меняется. Для Альфа-Банка $handler instanceof SberbankOnlineHandler теперь возвращает false. Если на таком условии у вас держится общая логика для «сбероподобных» платёжек (в обработчике события, в отчёте, в выгрузке), Альфа из неё тихо выпадет, без ошибок. Условие придётся расширить:

        <?php declare(strict_types=1);

use Sale\Handlers\PaySystem\AlfabankLegacyHandler;
use Sale\Handlers\PaySystem\SberbankOnlineHandler;

$isBankGateway = $handler instanceof SberbankOnlineHandler
    || $handler instanceof AlfabankLegacyHandler;

    

Скопированный шаблон оплаты остался без защиты

В template_bank_card.php адрес формы теперь берётся из нового параметра $params['FORM_ACTION'] (URL без query-строки), а не из $params['URL']. URL по-прежнему передаётся в setExtraParams(), поэтому копия шаблона в /local/ продолжит работать.

В штатном шаблоне имена и значения hidden-полей и action формы теперь проходят через htmlspecialcharsbx(), а раньше выводились как есть. Ваша копия этого фикса не получила и выводит значения из ответа банка без экранирования. Шаблон нужно перенести заново с нового оригинала. В своём варианте вывод должен выглядеть так:

        <form action="<?= htmlspecialcharsbx($params['FORM_ACTION']) ?>" method="get">
    <?php foreach ($params['FORM_PARAMS'] ?? [] as $name => $value): ?>
        <input type="hidden"
               name="<?= htmlspecialcharsbx($name) ?>"
               value="<?= htmlspecialcharsbx($value) ?>">
    <?php endforeach; ?>
</form>

    

Новое

isPaymentUrlValid(): проверка ссылки на оплату

SberbankOnlineHandler::isPaymentUrlValid(string $url, bool $testMode): bool — публичный статический метод. Пропускает только https на порту 443, без userinfo и fragment, с хостом из белого списка: payecom.ru и epay.sberbank.ru в бою, sbox.payecom.ru и ecomtest.sberbank.ru в тесте.

Обработчик применяет его к formUrl из ответа банка: невалидная ссылка в форму не попадает, вместо неё возвращается ошибка. Метод публичный, поэтому им можно пользоваться и в своём коде. Например, перед тем как отдать ссылку на оплату в мобильное приложение или в письмо:

        <?php declare(strict_types=1);

use Bitrix\Main\Error;
use Bitrix\Main\Loader;
use Bitrix\Main\Result;
use Bitrix\Sale\PaySystem\Manager;
use Sale\Handlers\PaySystem\SberbankOnlineHandler;

Loader::includeModule('sale');
Manager::includeHandler('SberbankOnline');

function checkPaymentLink(string $url, bool $testMode): Result
{
    $result = new Result();

    if (!SberbankOnlineHandler::isPaymentUrlValid($url, $testMode)) {
        $result->addError(new Error('Ссылка на оплату ведёт не на шлюз банка', 'PAYMENT_URL'));
    }

    return $result;
}

    

createHttpClient(): фабрика клиента, которую можно переопределить

Protected-метод createHttpClient(): Web\HttpClient собирает клиент с жёсткими настройками: проверка SSL включена, редиректы отключены, socketTimeout 10 секунд, streamTimeout 20, логгер заменён на \Psr\Log\NullLogger. Это одна из немногих точек расширения нового обработчика. Если запросам к банку нужен прокси, её и надо переопределять:

        protected function createHttpClient(): \Bitrix\Main\Web\HttpClient
{
    $client = parent::createHttpClient();
    $client->setProxy('proxy.shop.local', 3128);

    return $client;
}

    

Коды ошибок SBERBANK_*

Ошибки обработчика теперь приходят в ServiceResult как Main\Error с кодами шести видов: SBERBANK_SETTINGS, SBERBANK_TRANSPORT, SBERBANK_NOTIFICATION, SBERBANK_PAYMENT, SBERBANK_REFUND, SBERBANK_RESPONSE. По коду сразу понятно, где искать причину: в настройках, в сети, в данных платежа или в ответе банка. Если же банк сам вернул ошибку (errorCode не равен 0), её отдаёт send() с банковским errorCode в качестве кода, без префикса SBERBANK_.

        foreach ($serviceResult->getErrors() as $error) {
    $code = (string)$error->getCode();
    // сетевую ошибку имеет смысл повторить, ошибку настроек — нет
    $isNetworkProblem = $code === 'SBERBANK_TRANSPORT';
    // отказ самого банка приходит с его errorCode
    $isBankDecline = !str_starts_with($code, 'SBERBANK_');
}

    

AlfabankLegacyHandler

Новый класс Sale\Handlers\PaySystem\AlfabankLegacyHandler на 740 строк. Это старая реализация под новым именем, со всеми прежними registerOrder(), isCheckSumCorrect(), getIndicativeFields() и getDescriptionCodesMap().

БД

Схема не менялась, db.diff пуст. Значение SBERBANK_SECRET_KEY остаётся лежать в настройках платёжной системы, хотя читать его больше некому.

Мелочи и находки

  • Регистрация заказа переживает обрыв связи. Если register.do не удался, приватный recoverOrder() запрашивает getOrderStatusExtended.do по orderNumber и вытаскивает mdOrder из атрибутов ответа.
  • Повторная регистрация устроена отдельно. Номер заказа в банке собирается как <ID оплаты>_<ID платёжной системы>, например 123_5. Если у оплаты уже есть PS_INVOICE_ID, а заказ в банке отклонён (orderStatus 6) или сумма не совпала, обработчик регистрирует его заново с третьим сегментом: 123_5_1, затем 123_5_2. Суффикс попыток был и раньше (getOrderNumber(…, int $attempt), ORDER_ATTEMPT_NUMBER), новое здесь лимит в 36 символов и более узкое условие: заново регистрируется только неоплаченная оплата при orderStatus 6 или при orderStatus 0 с несовпавшей суммой, остальные статусы дают SBERBANK_PAYMENT, а ошибка запроса статуса возвращается как есть. Номер длиннее обработчик не отправит и вернёт SBERBANK_PAYMENT. Учитывайте такие номера, если сверяете оплаты с выписками и кабинетом банка.
  • PS_SUM берётся из платежа, а не из ответа банка: getMinorAmount($payment->getSum(), ...) / 100. Добавлены приватные matchesPayment() и getCurrencyCode(string $currency): ?int, в use появился Bitrix\Currency\CurrencyTable.
  • Отладочный лог обеднел. Вызовы PaySystem\Logger::addDebugInfo() из processRequest() исчезли, HTTP-клиенту принудительно ставится NullLogger. Разбирать инцидент «банк прислал уведомление, а заказ не оплатился» стало сложнее, в привычном месте ответа шлюза больше нет.
  • Редиректы у HTTP-клиента отключены, так что увести запрос на сторонний хост через 302 не получится.
  • В языковых файлах sberbankonline/lang/{ru,en} удалены фразы SALE_HPS_SBERBANK_SECRET_KEY и SALE_HPS_SBERBANK_SECRET_KEY_DESC, добавлены SALE_HPS_SBERBANK_ERROR_PAYMENT, …_ERROR_REFUND и …_ERROR_RESPONSE (по коду в эталоне).
  • В version.php пропала завершающая запятая и пустая строка после <?php. Версия 26.450.0 датирована 2026-09-11.

Что делать

  1. Перед обновлением найдите в /local/ наследников SberbankOnlineHandler, подойдёт grep по extends SberbankOnlineHandler. Сверьте их со списком удалённых методов и констант, добавьте возвращаемые типы в переопределения.
  2. Там же найдите instanceof SberbankOnlineHandler и getIndicativeFields. Первое больше не ловит Альфа-Банк, а getIndicativeFields() у Сбера теперь возвращает пустой массив.
  3. Если шаблон template_bank_card.php скопирован в /local/, перенесите его заново с обновлённого оригинала, с FORM_ACTION и htmlspecialcharsbx().
  4. Проверьте, что сервер видит epay.sberbank.ru и ecomtest.sberbank.ru по 443 порту, а колбэк в кабинете Сбера приходит как POST с JSON.
  5. После обновления проведите тестовую оплату целиком: регистрация заказа, переход на форму, возврат, смена статуса по уведомлению, возврат денег через refund.do. Начните с тестового режима.
  6. Поле секретного ключа в настройках платёжной системы искать не нужно, его убрали намеренно.

Устаревшие и удалённые API в этой версии

Символ Статус Чем заменять
Sale\Handlers\PaySystem\SberbankOnlineHandler::isCheckSumCorrect() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::registerOrder() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::getMerchantParams() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::getRegisterOrderParams() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::getDescriptionCode() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::getDescriptionCodesMap() Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::PAYMENT_OPERATION_DEPOSITED Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::PAYMENT_STATUS_SUCCESS Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::RESPONSE_CODE_SUCCESS Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::PAYMENT_STATE_CREATED Удалено —
Sale\Handlers\PaySystem\SberbankOnlineHandler::PAYMENT_DELIMITER Удалено —

Читайте дальше

sale 26.500.0 Безопасность Свежее

Sale 26.500.0: пачка защитных правок от личного кабинета до импорта местоположений

Sale 26.500.0 почти целиком состоит из точечных защитных правок: CSRF-проверки в ajax-обработчиках личного кабинета, сверка платежа с платёжной системой, ограничения на пути и адреса при импорте местоположений, экранирование в админке и печатных формах. Заодно установщик модуля переехал с `install.s...

5 мин
voximplant 26.700.0 Ломающее Свежее

Voximplant 26.700.0: оценку качества связи убрали, у notifyAdmins() появился тип

Модуль телефонии voximplant 26.700.0 вышел в коробочный Битрикс24 11 сентября 2026 года, по данным официального канала «Битрикс24 changelog». Из карточки звонка убрали оценку качества связи вместе с её JS-событиями, у `Im::notifyAdmins()` появился тип параметра, а публичные константы `SipStatusInfor...

3 мин
ui 26.687.0 Рутинное Свежее

UI 26.687.0: триал VibePlus в облаке и дизайн чипа TintedBitrixGpt

`Bitrix\UI\Controller\InfoHelper` при активации демо сначала спрашивает у модуля bitrix24, включён ли старт VibePlus, и если да, запускает триал VibePlus вместо обычного демо тарифа. Ветка срабатывает только при подключённом модуле bitrix24 и определённой константе `BX24_HOST_NAME`. В `ui.system.chi...

1 мин
ui 26.675.0 Ломающее Свежее

UI 26.675.0: ui.actionpanel переехал в бандл, а rich_text по-новому обходится с квадратными скобками

Модуль ui 26.675.0 собран 26 августа 2026. Из PHP поменялись `Converter` и `Whitelist` в `Bitrix\UI\Format\BBCode`, и пользовательские поля типа `rich_text` теперь иначе сохраняют и индексируют текст с квадратными скобками. На фронтенде старую панель групповых действий `ui.actionpanel` перевели на б...

4 мин
Мы используем файлы cookie для улучшения работы сайта. Продолжая использовать сайт, вы соглашаетесь с нашей политикой конфиденциальности.
AI Домовой

AI Домовой История

на связи

пишет…
Нет истории чатов
AI Домовой

Нужна авторизация

Войдите, чтобы задавать вопросы AI Домовому.

Войти