Checkout: Публичная T-Bank ссылка — параметры через Base64

Зачем это нужно

Публичная T-Bank ссылка EasyPay (https://pay.appload.tech/<slug>) по умолчанию работает в ad-hoc режиме: вы шарите один URL, плательщик сам вводит email и телефон, цена фиксирована = default_amount_rub из конфига чекаута. Этого достаточно для простых сценариев — «оплатить курс», «оплатить услугу».

Когда вам нужно больше — pre-fill полей формы, фиксированная цена в коридоре, или передача своего идентификатора заказа для атрибуции — добавьте к URL query-параметр ?d=<base64url(JSON)>. Кодируете payload у себя на стороне, фронтенд pay.appload.tech декодирует и предзаполняет форму. После оплаты ваш order_id возвращается обратно — в поле Payment.Data.ep_order вебхука и строкой Order: в Telegram-уведомлении.

Как это работает

Использование сводится к трём шагам:

  1. Соберите payload с нужными полями (order_id, email, phone, amount_rub — все опциональны).

  2. Закодируйте его в Base64URL (см. примеры ниже).

  3. Подставьте в URL: https://pay.appload.tech/<slug>?d=<base64>.

После успешной оплаты EasyPay присылает вам уведомление через настроенный канал (Telegram-триггер tbank.transaction_confirmed или webhook). Ваш order_id возвращается ровно в том виде, в котором вы его передали:

  • webhook — поле Payment.Data.ep_order (структура тела: «Webhook EasyPay: структура данных платежей T-Bank»);

  • Telegram — строка Order: в сообщении об оплате (наш внутренний OrderID в том же сообщении — это идентификатор попытки на нашей стороне, для сопоставления он не нужен).

URL-контракт

https://pay.appload.tech/<slug>?d=<base64url(JSON)>
  • <slug>product_slug чекаута. Lowercase, регулярка [a-z0-9][a-z0-9-]{1,62}.

  • <base64url(JSON)> — закодированный payload (см. ниже). Опционален — без ?d= URL работает в ad-hoc режиме.

Payload

{
  "order_id":   "u_a1b2c3",
  "email":      "user@example.com",
  "phone":      "+79991234567",
  "amount_rub": 5990
}

Поле

Обязательное

Описание

order_id

Нет

Ваш идентификатор заказа. Регулярка [A-Za-z0-9_-]{1,64}. После оплаты возвращается в Payment.Data.ep_order вебхука и в Telegram-уведомлении. Если не передан — EasyPay сгенерит auto-<slug>-<ms>-<rand4>.

email

Нет

Pre-fill email на форме. Если задан — должен пройти валидацию (^[^\s@]+@[^\s@]+\.[^\s@]+$, ≤254 chars), иначе плательщик увидит ошибку при submit.

phone

Нет

Pre-fill телефона. Формат ^\+?\d{11,15}$.

amount_rub

Нет

Точная цена в рублях. Учитывается ТОЛЬКО если задан **order_id** (anti-underpayment защита). Должен быть в коридоре [min_amount_rub, max_amount_rub] конфига. Без order_id сервер форсит default_amount_rub.

Anti-underpayment. Если передаёте amount_rub без order_id, поле игнорируется и checkout сворачивается в ad-hoc (= default). Это сознательная защита: нестандартная цена должна быть привязана к конкретному заказу, иначе можно случайно дать скидку по битой ссылке.

Поведение при ошибке декодинга. Если ?d= присутствует, но не парсится — фронтенд покажет ошибку «Некорректная ссылка» и не упадёт в ad-hoc. Это защита от молчаливого «проглатывания» битой ссылки с intended discount или order_id.

Кодирование Base64URL

Алгоритм: сериализовать payload в JSON → UTF-8 → стандартный Base64 → URL-safe замены (+-, /_, padding = опционально).

JavaScript / Node:

function encodePayload(payload) {
  return Buffer.from(JSON.stringify(payload), "utf8")
    .toString("base64")
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

const url = `https://pay.appload.tech/matrixon-colearn?d=${encodePayload({
  order_id:   "u_a1b2c3",
  email:      "user@example.com",
  amount_rub: 5990
})}`;

Python:

import base64, json

def encode_payload(payload):
    raw = json.dumps(payload, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
    return base64.urlsafe_b64encode(raw).decode().rstrip("=")

url = f"https://pay.appload.tech/matrixon-colearn?d={encode_payload({
    'order_id':   'u_a1b2c3',
    'email':      'user@example.com',
    'amount_rub': 5990,
})}"

Как параметры возвращаются обратно

После оплаты вам приходит уведомление на ваш канал. В теле вебхука поле Payment.Data.ep_order — ровно то, что вы положили в order_id внутри ?d=.

На вашей стороне:

  1. Получите свой идентификатор из Payment.Data.ep_order.

  2. Если order_id вы не передавали, EasyPay сгенерировал свой — такой начинается с auto-:

if (order_id.startsWith("auto-")) {
  // идентификатор сгенерировали мы — вашего заказа за ним нет
  return null;
}

Полный пример

Сценарий. Отправить персональную ссылку по заказу A-1043 клиенту user@example.com по цене 5990 ₽ (default по чекауту — 9990 ₽).

const slug   = "my-product";
const payload = { order_id: "A-1043", email: "user@example.com", amount_rub: 5990 };
const dParam  = encodePayload(payload);

const url = `https://pay.appload.tech/${slug}?d=${dParam}`;
// Шлёте url клиенту → клиент оплачивает →
// EasyPay присылает вебхук с Payment.Data.ep_order = "A-1043" →
// находите у себя заказ A-1043 и отмечаете его оплаченным.

Ограничения

  • Размер **order_id**: ≤64 символа, charset [A-Za-z0-9_-]. Если ваш идентификатор длиннее — храните у себя короткий ключ-ссылку на заказ.

  • Schema payload фиксированная — только 4 поля (order_id, email, phone, amount_rub). Свои ключи в payload игнорируются.

  • Anti-underpayment: amount_rub без order_id всегда игнорируется. Хотите кастомную цену — обязательно передавайте и order_id.

  • Anti-corrupt link: битый ?d= (не декодится) → ошибка на странице, не degradation в ad-hoc.

  • Безопасность. order_id уезжает в T-Bank отдельным параметром платежа (виден в их кабинете), приходит к вам в уведомлении и хранится в БД EasyPay. Не кладите туда секреты — только публичный контекст: ID, источники, флаги. Если нужно прокинуть что-то чувствительное — держите у себя по короткому ключу.

  • Идемпотентность вебхука. У платежей по публичной ссылке Payment.Data.ep_payment_uuid приходит null — он существует только у платежей, созданных через API с idempotency_key. Ключ дедупликации повторных доставок здесь — Payment.OrderId.

  • Идемпотентность ссылки. Тот же order_id с теми же (slug, amount, email, phone) → replay, вернётся та же T-Bank ссылка. Тот же order_id с другими полями → idempotency_conflict. Если планируете retry — держите order_id стабильным для одной попытки.

Ошибки

Все ошибки возвращаются с HTTP 200 + success: false.

error_code

Когда

invalid_slug

<slug> в URL не матчит регулярку [a-z0-9][a-z0-9-]{1,62}.

invalid_order_id

order_id длиннее 64 символов или содержит символы вне [A-Za-z0-9_-] (например +, /, = из стандартного Base64 вместо URL-safe).

invalid_email / invalid_phone

Email/телефон не прошёл валидацию при submit.

invalid_amount / amount_out_of_range

amount_rub ≤0 или вне коридора [min, max] конфига.

idempotency_conflict

order_id уже использован с другими (slug, amount, email, phone). Сгенерируйте новый.

init_in_progress / init_pending_stale

Параллельный запрос с тем же order_id ещё обрабатывается. Подождите 60 сек и повторите.

order_id_already_used

Прошлая попытка с этим order_id завершилась init_failed. Используйте новый.

product_not_found

Чекаута с таким slug нет, либо статус не active.

Дополнительно фронтенд показывает страницу «Некорректная ссылка» при невалидном slug или нераспарсенном ?d=.