Skip to Content
Разовые платежиВиджет оплаты

Виджет оплаты

Виджет принимает оплату прямо на странице мерчанта способами, подключёнными магазину: СБП, Alfa Pay, SberPay, T-Pay. Покупатель выбирает способ, и по клику виджет сразу заводит платёж. Это альтернатива сценарию из §5.2 «СБП и SberPay»: счёт создаётся тем же CreateInvoice, но вместо перенаправления клиента по AuthorizationUrl страница встраивает виджет у себя.

Набор и порядок кнопок задаёт настройка магазина на стороне платформы. Способы включаются через менеджера, выбрать их в API мерчант не может. Если магазину подключён один способ, экрана выбора нет — виджет выглядит как прежний СБП-виджет. Магазину, который рисует кнопки способов сам, виджет не нужен: для него есть редирект-маршрут (§11.7).

Виджет работает и в песочнице — со всеми четырьмя способами и с редирект-маршрутом. embed.js публикуется на её домене, а токен приходит в ответе CreateInvoice — как и в бою. На демо-проекте песочницы подключены все четыре способа, и экран выбора виден сразу; у остальных проектов по умолчанию только СБП, набор меняется по запросу. Настоящих банков в тестовой среде нет: кнопка Alfa Pay, SberPay или T-Pay открывает имитацию приложения банка с кнопкой подтверждения, а внутри виджета по-прежнему есть кнопка «Симулировать оплату». Сценарий с редиректом по AuthorizationUrl описан отдельно — Сценарий: СБП. Пошаговая проверка виджета — Виджет оплаты в песочнице.

11.1 Как встроить

  1. Создать счёт server-to-server. Из ответа CreateInvoice взять invoiceId и токен AuthorizationInfo.AuthorizationToken.
  2. Подключить embed.js с платёжного домена и вызвать SbpWidget.mount() или SbpWidget.open().
Страница оплаты мерчанта
<div id="pay"></div> <script src="https://pay.digitalspb.com/Card/Sbp/embed.js"></script> <script> SbpWidget.mount('#pay', { invoiceId: 'A1B2C3...', // из ответа CreateInvoice token: 'eyJ...', // AuthorizationInfo.AuthorizationToken (обязателен) methods: ['sbp', 'alfapay'], // необязательно: сузить набор способов webview: true, // необязательно: виджет открыт в WebView приложения brand: { name: 'Тёплый угол', logo: '/logo.png', primary: '#c2410c', background: '#fff8f3', textColor: '#1a1f36' }, onSuccess: function () { location.href = '/thanks'; }, onFail: function () {}, onExpired: function () {}, // срок QR истёк, заказ нужно пересоздать onError: function (e) {} }); </script>

mount() рисует <iframe src=".../Card/Sbp/widget.html?...">. Сам виджет живёт внутри iframe — это полная изоляция стилей и скриптов — и работает same-origin с коннектором, поэтому его запросы к платёжному домену идут без CORS. Виджет и страница мерчанта общаются через postMessage: авторесайз высоты iframe и события success / fail / expired / error транслируются в колбэки.

Модальное окно — SbpWidget.open(). Второй режим показа, по возможностям такой же, как mount(): виджет появляется поверх страницы, а не в её вёрстке. Принимает те же параметры, только без селектора, и дополнительно колбэк onClose. Открыт всегда один экран оплаты — новое открытие закрывает предыдущее. Окно закрывается крестиком, клавишей Esc или кликом по фону; SbpWidget.close() закрывает его из кода.

Модальное окно
SbpWidget.open({ invoiceId: 'A1B2C3...', token: 'eyJ...', onSuccess: function () { location.href = '/thanks'; }, onClose: function () {} // окно закрыто });

Без embed.js то же самое доступно ссылкой — её можно открыть в iframe или в WebView самостоятельно:

GET /Card/Sbp/widget.html?invoiceId={invoiceId}&t={token}&methods=sbp,alfapay

Параметр methods в ссылке необязателен и работает так же, как в mount().

Создание счёта остаётся на backend мерчанта — во всех сценариях этой страницы. Креды и подпись в браузер не попадают.

11.2 Способы оплаты

КодСпособЧто происходит по клику
sbpСБПНа компьютере — QR-код, на телефоне — переход в приложение банка по диплинку
alfapayAlfa PayСоздаётся платёж, покупатель уходит в банк
sberpaySberPayСоздаётся платёж, покупатель уходит в банк
tpayT-PayСоздаётся платёж, покупатель уходит в банк

Коды везде одни и те же — в methods, в ссылке на widget.html и в редирект-маршруте. Коды провайдера наружу не выходят.

Как виджет ведёт себя в зависимости от набора:

  • подключён один способ — экрана выбора нет, всё как раньше;
  • способов несколько — виджет показывает кнопки способов. Они оформлены по гайдам банков, поэтому цвет магазина (brand.primary) на них не действует;
  • клик по кнопке сразу создаёт платёж и уводит в банк, промежуточного экрана нет;
  • под экраном оплаты есть ссылка «Выбрать другой способ»;
  • возврат из банка или перезагрузка вкладки открывают уже начатый платёж, а не просят выбрать способ заново.

methods только сужает набор. Способ, который магазину не подключён, через него не включить; порядок кнопок всё равно задаёт настройка магазина. Если от пересечения не осталось ничего — например, способ отключили, а кнопка на странице магазина осталась, — виджет не падает: показывает весь доступный набор и вызывает onError с code: 'method_unavailable'. Оплата после этого продолжается.

11.3 Параметры mount() и open()

ПараметрОбязателенОписание
invoiceIdДаИдентификатор инвойса из ответа CreateInvoice
tokenДаТокен счёта из ответа CreateInvoice — AuthorizationInfo.AuthorizationToken в XML, authorization.token в JSON
methodsНетМассив кодов (sbp, alfapay, sberpay, tpay) или строка через запятую. Только сужает набор магазина, см. §11.2
webviewНетtrue — виджет открыт в WebView приложения магазина. В WebView новая вкладка не открывается, поэтому переход в банк уводит всю страницу. Без флага виджет определяет среду сам, но менее надёжно
apiBaseНетАдрес платёжного домена. По умолчанию — origin, с которого загружен embed.js
brandНетОбъект брендирования (см. ниже)
pollНетfalse отключает автопроверку статуса; по умолчанию включена
langНетЯзык интерфейса виджета
onSuccessНетКолбэк успешной оплаты
onFailНетКолбэк отказа / неуспеха
onExpiredНетКолбэк истёкшего QR-кода — заказ нужно пересоздать
onErrorНетКолбэк ошибки; получает объект Error, у которого может быть поле code (§11.5)
onCloseНетТолько для open(): модальное окно закрыто

Без token метод бросает исключение — это единственный обязательный параметр на стороне SDK.

Поле токена — расширение блока AuthorizationInfo в ответе CreateInvoice. Токен привязан к счёту и живёт 2 часа.

11.4 Внешний вид — объект brand

ПолеОписаниеПо умолчанию
nameНазвание магазина или продукта«СБП»
logoURL логотипа — только http(s):// или data:image/нет
primaryАкцентный цвет кнопок и выделений: hex, именованный цвет или rgb() / rgba(). На кнопки способов оплаты не действует#1a1f36
backgroundЦвет фона виджета#ffffff
textColorЦвет текста#1a1f36

Все цветовые значения и logo проходят серверную валидацию и санитайз: параметры приходят через query публичной страницы widget.html, и без этого получился бы reflected XSS. Недопустимый цвет молча заменяется значением по умолчанию, logo принимается только по схемам http(s) или data:image.

11.5 События и жизненный цикл

Виджет шлёт странице пять видов сообщений postMessage:

СобытиеЧто означает
successОплата прошла успешно
failОтказ или неуспех
expiredИстекло время жизни QR-кода
errorОшибка виджета; в onError приходит объект Error
resizeАвто-подстройка высоты iframe — обрабатывается обёрткой автоматически

У объекта Error может быть поле code. Сейчас определён один код — method_unavailable (см. methods в §11.2); после него виджет продолжает работать, это не конец оплаты. Если страница виджета не загрузилась — ответ 5xx или сбой сети, — обёртка тоже вызывает onError, а не оставляет пустой фрейм.

Метод destroy() у результата mount() снимает обработчик postMessage и удаляет iframe со страницы.

11.6 Готовая кнопка СБП — renderButton()

Тонкий хелпер поверх mount(): SDK сам рисует брендированную кнопку СБП, а создание инвойса делегируется колбэку createInvoice() на backend мерчанта. Кнопка ведёт состояния — loading, disabled, защита от двойного клика, ошибки — и после успешного создания счёта сама монтирует виджет. Удобно, когда своего чекаута нет.

Кнопка СБП на странице мерчанта
<div id="pay"></div> <script src="https://pay.digitalspb.com/Card/Sbp/embed.js"></script> <script> SbpWidget.renderButton('#pay', { createInvoice: async function () { // на backend мерчанта: CreateInvoice const r = await fetch('/api/create-invoice', { method: 'POST' }); return r.json(); // возвращает invoiceId и token }, text: 'Оплатить', // необязательно (дефолт «Оплатить») brand: { name: 'Тёплый угол', primary: '#c2410c', background: '#fff8f3' }, onSuccess: function () { location.href = '/thanks'; }, onFail: function () {}, onError: function (e) {} }); </script>
ПараметрОбязателенОписание
createInvoiceДаAsync-колбэк: создаёт инвойс на backend мерчанта (CreateInvoice) и возвращает объект с полями invoiceId и token
textНетТекст кнопки; по умолчанию «Оплатить»
brandНетБрендирование виджета (таблица выше). На саму кнопку СБП не влияет — она фирменная
apiBase, poll, lang, onSuccess, onFail, onExpired, onErrorНетТо же, что у mount()

Кнопка выглядит одинаково у всех мерчантов: фирменный тёмно-синий фон #1D1346, слева текст «Оплатить», справа официальный логотип СБП — цветной знак и слово «сбп» белым начертанием для тёмного фона, без перекраски. Построено по брендбуку НСПК (П.231).

У результата renderButton() два метода: reset() возвращает кнопку на место — например, для «оплатить ещё раз», — а destroy() снимает со страницы и кнопку, и виджет.

11.7 Свои кнопки магазина — редирект-маршрут

Если магазин рисует кнопки способов сам, виджет не нужен. Кнопка ведёт на редирект-маршрут: сервер создаёт платёж выбранным способом и отвечает 302 на страницу банка.

GET /Card/Handlers/{invoiceId}/pay?method=alfapay&t={token}
ПараметрОбязателенОписание
invoiceIdДаИдентификатор инвойса из ответа CreateInvoice (часть пути)
methodДаКод способа: sbp, alfapay, sberpay, tpay
tДаТот же AuthorizationInfo.AuthorizationToken из ответа CreateInvoice
  • Неизвестный код method — 400. Подменять выбор покупателя на СБП маршрут не будет.
  • У sbp своей страницы банка нет, поэтому открывается страница платформы с QR-кодом. Экрана выбора на ней нет — способ уже выбран.
  • Адрес не зависит от эквайринга, через который работает магазин: имени канала в нём нет.

Отдельного метода со списком подключённых магазину способов нет и не планируется: список нужен только виджету и приходит вшитым в его страницу.

11.8 Что выбрать

СпособКогда
mount()Виджет встроен в вёрстку страницы; мерчант сам решает, где и когда его показать
open() / close()Встраиванию негде разместиться: узкая страница, оплата из списка заказов
renderButton()Быстрый drop-in с готовой фирменной кнопкой СБП и управлением состояниями
Ссылка на widget.htmlПодключить embed.js нельзя — например, виджет открывается в WebView
Свои кнопки + /payМагазин рисует кнопки способов сам; виджет не подключается