Payment Docs
Выплаты

Выплата в 1 клик (виджет)

Клиент один раз привязывает банковский счёт во встроенном виджете и получает следующие выплаты в один клик

Перед тем как начать

Это направление выплат доступно не всем мерчантам. Уточните доступность у вашего менеджера и убедитесь, что на вашем балансе мерчанта достаточно средств, прежде чем создавать выплату.

Обзор

Виджет выплаты — это страница, которую вы встраиваете на свой сайт в 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, обязательны. Неизвестные поля отклоняются, а строковые поля не должны содержать управляющих символов (переводов строки и т. п.).

ПолеТипОписание
merchantIdstring (UUID)ID вашего мерчанта: merchantId из GET /v1/merchants/balance, он же есть в каждом вебхуке
userIdstringID клиента в вашей системе, до 255 символов. Реквизиты сохраняются по userId: передавайте свой постоянный внутренний ID, а не значение, которое может выбрать сам клиент
amountstring или numberСумма выплаты в рублях, от 1 до 99 999 999.99, не больше двух знаков после точки. Надёжнее строкой ("1500.00"): числа вида 0.30000000000000004 отклоняются
currencystringRUB
externalIdstringВаш уникальный ID выплаты, до 255 символов, по тем же правилам, что в POST /v1/payouts. Одна ссылка создаёт не больше одной выплаты: для каждой выплаты нужен новый externalId
serviceIdintegerСервис выплат на счёт из GET /v1/payouts/services. Без него используется ваш сервис выплат по умолчанию — убедитесь, что он работает с BANK_ACCOUNT
expiresAtintegerСрок действия ссылки, 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" } }
typedataКогда
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_INVALIDpayload не декодируется, поле отсутствует или некорректно. detail называет полеИсправьте payload по разделу Поля payload
SIGNATURE_INVALIDПодпись не сходится или merchantId не вашПодпишите ровно строку payload действующим API-токеном
LINK_EXPIREDНаступило время expiresAtСоздайте новую ссылку
UNAVAILABLEСервис временно недоступенПредложите клиенту повторить позже

Проверка реквизитов

Виджет проверяет реквизиты по мере ввода и ещё раз при сохранении — по тем же правилам, что и выплаты на счёт: ФИО кириллицей, телефон в формате +7XXXXXXXXXX, БИК из списка банков и 20-значный рублёвый счёт физического лица, контрольный ключ которого сходится с БИК. Необязательная дата рождения должна давать возраст от 14 до 120 лет.

Рекомендации

  • Создавайте ссылку в момент, когда клиент запрашивает выплату, с коротким expiresAt и каждый раз с новым externalId.
  • Не записывайте ссылки в логи и никому их не передавайте: пока выплата не создана, по ссылке можно заменить реквизиты клиента.
  • Зарезервируйте сумму у себя до открытия виджета и проведите её по вебхуку выплаты.

Следующие шаги

На этой странице