Выплата в 1 клик (виджет)
Клиент один раз привязывает банковский счёт во встроенном виджете и получает следующие выплаты в один клик
Перед тем как начать
Авторизация
Как подписывать запросы API-токеном.
Обзор выплат
Общий формат запроса, поля ответа и статусы.
Это направление выплат доступно не всем мерчантам. Уточните доступность у вашего менеджера и убедитесь, что на вашем балансе мерчанта достаточно средств, прежде чем создавать выплату.
Обзор
Виджет выплаты — это страница, которую вы встраиваете на свой сайт в iframe (или открываете поп-апом). Он выплачивает на рублёвый счёт физического лица в российском банке — это та же выплата BANK_ACCOUNT, что и в разделе Россия (банковский счёт), но реквизиты клиент вводит в виджете, а не на вашем сайте:
- Первая выплата. Клиент указывает ФИО, телефон, привязанный к счёту, номер счёта, БИК банка и по желанию дату рождения. Мы сохраняем реквизиты за этим клиентом и создаём выплату.
- Следующие выплаты. Виджет показывает сохранённый счёт (логотип банка, последние четыре цифры, ФИО), и клиент подтверждает выплату одной кнопкой. Перед подтверждением клиент может заменить реквизиты.
Реквизиты хранятся для каждого клиента отдельно: ваш мерчант плюс userId, который вы передаёте. Виджет работает на русском языке, в светлой теме. Причину неудачной выплаты он не показывает: её узнаёте вы — по балансу, лимитам сервиса или у менеджера, — а не клиент.
| Валюта | Направление | Тип выплаты |
|---|---|---|
RUB | Счёт физического лица в российском банке | BANK_ACCOUNT |
Порядок действий
Соберите параметры выплаты
На своём бэкенде, когда клиент запрашивает выплату, соберите параметры в JSON-объект:
{
"merchantId": "0198c3f1-1111-7222-8333-444455556666",
"userId": "user_12345",
"amount": "1500.00",
"currency": "RUB",
"externalId": "payout-123",
"serviceId": 123,
"expiresAt": 1790000000
}Поля описаны в разделе Поля payload.
Подпишите их
Закодируйте JSON в base64url (это payload) и подпишите именно эту строку своим API-токеном: signature — HMAC-SHA512 от payload в hex, тот же алгоритм и ключ, что у подписи вебхуков. Подробнее — в разделе Подпись.
Откройте виджет
Подставьте оба значения в адрес виджета и покажите его в iframe или поп-апе:
https://pay.platinout.com/payout-widget?payload={payload}&signature={signature}Подробнее — в разделе Встраивание.
Следите за событиями виджета
Виджет сообщает вашей странице о каждом шаге через postMessage: реквизиты сохранены, выплата создана или не удалась, клиент нажал «Закрыть». По ним меняйте высоту iframe, закрывайте его и обновляйте свой интерфейс. Подробнее — в разделе События.
Отслеживайте выплату по вебхукам
Виджет создаёт обычную выплату с externalId из ссылки. Её статусы приходят обычными вебхуками по выплатам (X-Type: PAYOUT_UPDATE), а запросить её можно через GET /v1/payouts/external/{externalId}. Итог выплаты определяет вебхук, а не событие виджета.
Поля payload
Все поля, кроме serviceId, обязательны. Неизвестные поля отклоняются, а строковые поля не должны содержать управляющих символов (переводов строки и т. п.).
| Поле | Тип | Описание |
|---|---|---|
merchantId | string (UUID) | ID вашего мерчанта: merchantId из GET /v1/merchants/balance, он же есть в каждом вебхуке |
userId | string | ID клиента в вашей системе, до 255 символов. Реквизиты сохраняются по userId: передавайте свой постоянный внутренний ID, а не значение, которое может выбрать сам клиент |
amount | string или number | Сумма выплаты в рублях, от 1 до 99 999 999.99, не больше двух знаков после точки. Надёжнее строкой ("1500.00"): числа вида 0.30000000000000004 отклоняются |
currency | string | RUB |
externalId | string | Ваш уникальный ID выплаты, до 255 символов, по тем же правилам, что в POST /v1/payouts. Одна ссылка создаёт не больше одной выплаты: для каждой выплаты нужен новый externalId |
serviceId | integer | Сервис выплат на счёт из GET /v1/payouts/services. Без него используется ваш сервис выплат по умолчанию — убедитесь, что он работает с BANK_ACCOUNT |
expiresAt | integer | Срок действия ссылки, Unix-время в секундах. Не дальше чем через 24 часа; рекомендуем 15–30 минут — клиенту хватит времени найти свои реквизиты |
Подпись
payload — base64url-кодировка (RFC 4648 §5: - и _ вместо + и /, выравнивание = не обязательно) JSON в UTF-8. signature — hex(HMAC-SHA512(apiToken, payload)), где payload — закодированная строка, а не JSON. Оба значения безопасны для URL и подставляются как есть.
Подписывайте только на своём бэкенде. API-токен — секрет: он не должен попасть в браузер, поэтому подпись нельзя вычислять в клиентском коде.
import crypto from "crypto";
const params = {
merchantId: process.env.MERCHANT_ID,
userId: "user_12345",
amount: "1500.00",
currency: "RUB",
externalId: "payout-123",
serviceId: 123,
expiresAt: Math.floor(Date.now() / 1000) + 30 * 60, // 30 минут
};
const payload = Buffer.from(JSON.stringify(params)).toString("base64url");
const signature = crypto
.createHmac("sha512", process.env.API_TOKEN)
.update(payload)
.digest("hex");
const widgetUrl = `https://pay.platinout.com/payout-widget?payload=${payload}&signature=${signature}`;import base64, hashlib, hmac, json, os, time
params = {
"merchantId": os.environ["MERCHANT_ID"],
"userId": "user_12345",
"amount": "1500.00",
"currency": "RUB",
"externalId": "payout-123",
"serviceId": 123,
"expiresAt": int(time.time()) + 30 * 60, # 30 минут
}
raw = json.dumps(params, separators=(",", ":")).encode()
payload = base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
signature = hmac.new(os.environ["API_TOKEN"].encode(), payload.encode(), hashlib.sha512).hexdigest()
widget_url = f"https://pay.platinout.com/payout-widget?payload={payload}&signature={signature}"<?php
$params = [
'merchantId' => getenv('MERCHANT_ID'),
'userId' => 'user_12345',
'amount' => '1500.00',
'currency' => 'RUB',
'externalId' => 'payout-123',
'serviceId' => 123,
'expiresAt' => time() + 30 * 60, // 30 минут
];
$json = json_encode($params, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$payload = rtrim(strtr(base64_encode($json), '+/', '-_'), '=');
$signature = hash_hmac('sha512', $payload, getenv('API_TOKEN'));
$widgetUrl = "https://pay.platinout.com/payout-widget?payload={$payload}&signature={$signature}";Проверьте свою реализацию
С API-токеном YOUR_API_TOKEN и параметрами из первого шага должны получиться:
payload = eyJtZXJjaGFudElkIjoiMDE5OGMzZjEtMTExMS03MjIyLTgzMzMtNDQ0NDU1NTU2NjY2IiwidXNlcklkIjoidXNlcl8xMjM0NSIsImFtb3VudCI6IjE1MDAuMDAiLCJjdXJyZW5jeSI6IlJVQiIsImV4dGVybmFsSWQiOiJwYXlvdXQtMTIzIiwic2VydmljZUlkIjoxMjMsImV4cGlyZXNBdCI6MTc5MDAwMDAwMH0
signature = 7c39e6f057794d8efdcf2b8859daa91bfdab06caf59e940ecb4d20e4905af2d1c8ef1cdbe6c0d9ab16f4c44deca24bcff3663e6123bc7eafd20ea20176045109Ваш язык может сериализовать JSON иначе (порядок ключей, пробелы) — это не страшно: главное, подписать ту самую строку payload, которая попадёт в URL.
Встраивание
<iframe
id="payout-widget"
src="https://pay.platinout.com/payout-widget?payload=...&signature=..."
title="Выплата"
style="width: 100%; max-width: 440px; height: 560px; border: 0"
></iframe>- Размер. Виджет подстраивается под ширину от 320 px, удобнее всего — 360–480 px. Высота меняется от экрана к экрану: задавайте высоту iframe по событию
resize, а не фиксированным значением. - Поп-ап. Тот же адрес можно открыть через
window.open(widgetUrl, "payout", "width=480,height=720"). Тогда события приходят вwindow.opener, а «Закрыть» закрывает поп-ап сам. - Повторное открытие. Ссылка, по которой выплата уже создана, показывает её текущее состояние (принята, выполнена или не удалась) вместо формы, поэтому перезагрузка страницы не создаст вторую выплату.
- Замена реквизитов. Клиент может заменить сохранённые реквизиты в виджете, пока выплата по ссылке не создана. После этого ссылка их больше не меняет.
События
Виджет отправляет сообщения странице, которая его встроила (родителю iframe) или открыла (opener поп-апа):
{ "source": "payout-widget", "type": "payout_created", "data": { "externalId": "payout-123", "payoutId": "0198c3f1-2222-7333-8444-555566667777", "status": "CREATED" } }type | data | Когда |
|---|---|---|
ready | { hasRequisites: boolean, payout: { id, status } | null } | Ссылка действительна, показан первый экран. hasRequisites — есть ли у клиента сохранённые реквизиты; payout — выплата по этой ссылке, если она уже создана |
resize | { height: number } | Изменилась высота содержимого в CSS-пикселях. Задайте её iframe |
requisites_saved | { externalId: string, changed: boolean } | Клиент впервые привязал реквизиты (changed: false) или заменил сохранённые (changed: true) |
payout_created | { externalId: string, payoutId: string, status: string } | Выплата создана, обычно в статусе CREATED. Итоговый статус придёт вебхуком |
payout_failed | { externalId: string, payoutId: string | null } | Выплата не создана (payoutId: null, клиент может повторить в виджете) или создана и сразу не удалась. Причина не передаётся |
error | { code: string, detail?: string } | Ссылкой нельзя воспользоваться или сервис недоступен. Коды — ниже |
close | {} | Клиент нажал «Закрыть» на итоговом экране. Закройте iframe или поп-ап |
Прежде чем доверять сообщению, проверьте его источник:
const WIDGET_ORIGIN = "https://pay.platinout.com";
const frame = document.getElementById("payout-widget");
window.addEventListener("message", (event) => {
if (event.origin !== WIDGET_ORIGIN) return;
const message = event.data;
if (!message || message.source !== "payout-widget") return;
switch (message.type) {
case "resize":
frame.style.height = `${message.data.height}px`;
break;
case "payout_created":
// Покажите «выплата принята»; итоговый статус ждите в вебхуке.
break;
case "payout_failed":
case "error":
// Предложите клиенту повторить позже или запишите message.data.code.
break;
case "close":
frame.remove();
break;
}
});События нужны только для интерфейса. Зачисляйте или возвращайте средства на баланс клиента по вебхуку выплаты — он подписан, — а не по событию postMessage.
Коды error
code | Что означает | Что делать |
|---|---|---|
PAYLOAD_INVALID | payload не декодируется, поле отсутствует или некорректно. detail называет поле | Исправьте payload по разделу Поля payload |
SIGNATURE_INVALID | Подпись не сходится или merchantId не ваш | Подпишите ровно строку payload действующим API-токеном |
LINK_EXPIRED | Наступило время expiresAt | Создайте новую ссылку |
UNAVAILABLE | Сервис временно недоступен | Предложите клиенту повторить позже |
Проверка реквизитов
Виджет проверяет реквизиты по мере ввода и ещё раз при сохранении — по тем же правилам, что и выплаты на счёт: ФИО кириллицей, телефон в формате +7XXXXXXXXXX, БИК из списка банков и 20-значный рублёвый счёт физического лица, контрольный ключ которого сходится с БИК. Необязательная дата рождения должна давать возраст от 14 до 120 лет.
Рекомендации
- Создавайте ссылку в момент, когда клиент запрашивает выплату, с коротким
expiresAtи каждый раз с новымexternalId. - Не записывайте ссылки в логи и никому их не передавайте: пока выплата не создана, по ссылке можно заменить реквизиты клиента.
- Зарезервируйте сумму у себя до открытия виджета и проведите её по вебхуку выплаты.