Виджет оплаты
Виджет принимает оплату прямо на странице мерчанта способами, подключёнными магазину: СБП, Alfa Pay, SberPay, T-Pay. Покупатель выбирает способ, и по клику виджет сразу заводит платёж. Это альтернатива сценарию из §5.2 «СБП и SberPay»: счёт создаётся тем же CreateInvoice, но вместо перенаправления клиента по AuthorizationUrl страница встраивает виджет у себя.
Набор и порядок кнопок задаёт настройка магазина на стороне платформы. Способы включаются через менеджера, выбрать их в API мерчант не может. Если магазину подключён один способ, экрана выбора нет — виджет выглядит как прежний СБП-виджет. Магазину, который рисует кнопки способов сам, виджет не нужен: для него есть редирект-маршрут (§11.7).
Виджет работает и в песочнице — со всеми четырьмя способами и с редирект-маршрутом. embed. публикуется на её домене, а токен приходит в ответе CreateInvoice — как и в бою. На демо-проекте песочницы подключены все четыре способа, и экран выбора виден сразу; у остальных проектов по умолчанию только СБП, набор меняется по запросу. Настоящих банков в тестовой среде нет: кнопка Alfa Pay, SberPay или T-Pay открывает имитацию приложения банка с кнопкой подтверждения, а внутри виджета по-прежнему есть кнопка «Симулировать оплату». Сценарий с редиректом по AuthorizationUrl описан отдельно — Сценарий: СБП. Пошаговая проверка виджета — Виджет оплаты в песочнице.
11.1 Как встроить
- Создать счёт server-to-server. Из ответа
CreateInvoiceвзятьinvoiceIdи токенAuthorizationInfo..AuthorizationToken - Подключить
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. то же самое доступно ссылкой — её можно открыть в iframe или в WebView самостоятельно:
GET /Card/Sbp/widget.html?invoiceId={invoiceId}&t={token}&methods=sbp,alfapayПараметр methods в ссылке необязателен и работает так же, как в mount().
Создание счёта остаётся на backend мерчанта — во всех сценариях этой страницы. Креды и подпись в браузер не попадают.
11.2 Способы оплаты
| Код | Способ | Что происходит по клику |
|---|---|---|
sbp | СБП | На компьютере — QR-код, на телефоне — переход в приложение банка по диплинку |
alfapay | Alfa Pay | Создаётся платёж, покупатель уходит в банк |
sberpay | SberPay | Создаётся платёж, покупатель уходит в банк |
tpay | T-Pay | Создаётся платёж, покупатель уходит в банк |
Коды везде одни и те же — в methods, в ссылке на widget. и в редирект-маршруте. Коды провайдера наружу не выходят.
Как виджет ведёт себя в зависимости от набора:
- подключён один способ — экрана выбора нет, всё как раньше;
- способов несколько — виджет показывает кнопки способов. Они оформлены по гайдам банков, поэтому цвет магазина (
brand.) на них не действует;primary - клик по кнопке сразу создаёт платёж и уводит в банк, промежуточного экрана нет;
- под экраном оплаты есть ссылка «Выбрать другой способ»;
- возврат из банка или перезагрузка вкладки открывают уже начатый платёж, а не просят выбрать способ заново.
methods только сужает набор. Способ, который магазину не подключён, через него не включить; порядок кнопок всё равно задаёт настройка магазина. Если от пересечения не осталось ничего — например, способ отключили, а кнопка на странице магазина осталась, — виджет не падает: показывает весь доступный набор и вызывает onError с code: 'method_unavailable'. Оплата после этого продолжается.
11.3 Параметры mount() и open()
| Параметр | Обязателен | Описание |
|---|---|---|
invoiceId | Да | Идентификатор инвойса из ответа CreateInvoice |
token | Да | Токен счёта из ответа CreateInvoice — AuthorizationInfo. в XML, authorization. в JSON |
methods | Нет | Массив кодов (sbp, alfapay, sberpay, tpay) или строка через запятую. Только сужает набор магазина, см. §11.2 |
webview | Нет | true — виджет открыт в WebView приложения магазина. В WebView новая вкладка не открывается, поэтому переход в банк уводит всю страницу. Без флага виджет определяет среду сам, но менее надёжно |
apiBase | Нет | Адрес платёжного домена. По умолчанию — origin, с которого загружен embed. |
brand | Нет | Объект брендирования (см. ниже) |
poll | Нет | false отключает автопроверку статуса; по умолчанию включена |
lang | Нет | Язык интерфейса виджета |
onSuccess | Нет | Колбэк успешной оплаты |
onFail | Нет | Колбэк отказа / неуспеха |
onExpired | Нет | Колбэк истёкшего QR-кода — заказ нужно пересоздать |
onError | Нет | Колбэк ошибки; получает объект Error, у которого может быть поле code (§11.5) |
onClose | Нет | Только для open(): модальное окно закрыто |
Без token метод бросает исключение — это единственный обязательный параметр на стороне SDK.
Поле токена — расширение блока AuthorizationInfo в ответе CreateInvoice. Токен привязан к счёту и живёт 2 часа.
11.4 Внешний вид — объект brand
| Поле | Описание | По умолчанию |
|---|---|---|
name | Название магазина или продукта | «СБП» |
logo | URL логотипа — только http(s):// или data:image/ | нет |
primary | Акцентный цвет кнопок и выделений: hex, именованный цвет или rgb() / rgba(). На кнопки способов оплаты не действует | #1a1f36 |
background | Цвет фона виджета | #ffffff |
textColor | Цвет текста | #1a1f36 |
Все цветовые значения и logo проходят серверную валидацию и санитайз: параметры приходят через query публичной страницы widget., и без этого получился бы 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. из ответа CreateInvoice |
- Неизвестный код
method—400. Подменять выбор покупателя на СБП маршрут не будет. - У
sbpсвоей страницы банка нет, поэтому открывается страница платформы с QR-кодом. Экрана выбора на ней нет — способ уже выбран. - Адрес не зависит от эквайринга, через который работает магазин: имени канала в нём нет.
Отдельного метода со списком подключённых магазину способов нет и не планируется: список нужен только виджету и приходит вшитым в его страницу.
11.8 Что выбрать
| Способ | Когда |
|---|---|
mount() | Виджет встроен в вёрстку страницы; мерчант сам решает, где и когда его показать |
open() / close() | Встраиванию негде разместиться: узкая страница, оплата из списка заказов |
renderButton() | Быстрый drop-in с готовой фирменной кнопкой СБП и управлением состояниями |
Ссылка на widget. | Подключить embed. нельзя — например, виджет открывается в WebView |
Свои кнопки + /pay | Магазин рисует кнопки способов сам; виджет не подключается |