GH GambleHub

Частичные и полные рефанды

TL;DR

Рефанд — это обратная операция по captured сумме. Полный закрывает транзакцию целиком, частичный возвращает часть (может быть серия partial до полного). Критично: refund-to-source, строгая идемпотентность, журналирование причин, и оркестрация с вебхуками/ретраями. Измеряем Refund Rate, TtR p95, Refund Error и устраняем дубли/несоответствия через авто-сверки.

1) Термины и принципиальные различия

Full Refund — возврат всей зафиксированной суммы (`refund_amount = capture_amount`).
Partial Refund — возврат части (`0 < refund_amount < capture_amount`), допускает остальные partial до суммарного `capture_amount`.
Refund to Source — возврат на исходный метод/рельсы платежа (регуляторно предпочтительно/обязательно).
Void — отмена до capture (если поддерживается рельсами), не считается рефандом.
Reversal/Chargeback — банковые/рельсовые механики вне вашей инициативы (споры, чарджбэки) — не путать с рефандом.

2) Когда выдавать полный vs частичный

Полный (Full):
  • Отмена заказа/услуги целиком, дублирующее списание, системная ошибка.
  • Обязателен при несостоявшемся предоставлении услуги (по правилам потребителя/регулятора).
Частичный (Partial):
  • Частичная отмена услуги, пропорциональные корректировки (скидки, компенсации задержек).
  • Технические лимиты рельс (макс. сумма за одну операцию) — серия partial.
  • Пост-фактум удержание комиссий (где регуляторно разрешено) — реже в iGaming.
Решение: зашиваем матрицу `reason_code × method × jurisdiction` → policy = fullpartialboth, лимиты, необходимый уровень одобрения.

3) Политики и лимиты

Refund-to-source = true по умолчанию; исключения — через MLRO/комплаенс-кейсы (логируется).
Cut-off: рефанды допускаются N дней с момента capture (по методу/юрисдикции).
Max Partial Count: не более K partial на payment (типично K ≤ 5).
Min Partial Amount: не ниже технического минимума рельс/PSP.

Approval Matrix:
  • Агент саппорта: partial ≤ X, full ≤ Y.
  • Менеджер/финансы: свыше лимитов, кросс-методные исключения.
  • Cooling-off на повторные попытки (анти-дребезг).

4) Архитектура и поток событий

Компоненты:
  • Payment Orchestrator — источник истины статусов.
  • Refund Service — API, идемпотентность, оркестрация ретраев, журналирование.
  • PSP Adapters — интеграции по методам.
  • Reconciliation — авто-сверки, DLQ, коррекции.
  • Ledger/Accounting — проводки, деферы, выравнивание с клирингами.
  • Risk/Compliance — проверки на санкции/SoF при спорных сценариях.
Последовательность (partial/full):

1. `Refund.Create` (API) → валидации (лимиты, остаток, policy, KYC/SoF при необходимости).

2. Генерация idempotency_key (`hash(payment_id + refund_amount + reason + nonce)`).

3. Вызов PSP → статус `PENDING`.

4. Вебхук/поллинг → `SUCCESS`/`FAILED`; при тайм-ауте — ретраи с тем же ключом.

5. Публикация события в Kafka → Ledger, BI, алерты.

6. Авто-сверка: сопоставление `provider_refund_id` с реестром.

5) Идемпотентность и анти-дубли

Один и тот же рефанд не может зачислиться дважды: вся логика через idempotency storage (KV/Redis + TTL).
Ключи на payment_id × amount × reason (и, при необходимости, `partial_index`).
Ретраи используют тот же ключ.
Параллельные partial защищаются row-level locks/optimistic version на aggregate суммы.

Псевдокод:
python def refund(payment_id, amount, reason, idem_key):
if idem_store. exists(idem_key): return idem_store. get(idem_key)
with tx():
p = db. get_payment(payment_id, for_update=True)
assert p. captured_amount - p. refunded_amount >= amount > 0 r = p. create_refund(amount, reason, status='PENDING', idem_key=idem_key)
resp = psp. refund(p. provider_txid, amount, idem_key)
return finalize(r, resp. status, resp. ext_id)

6) Модель данных (минимально достаточная)

json
{
"payment_id": "pay_123",
"captured_amount": 150. 00,
"currency": "EUR",
"refunded_amount": 40. 00,
"refunds": [
{
"refund_id": "rf_001",
"type": "partial    full",
"amount": 20. 00,
"reason_code": "PARTIAL_SERVICE",
"idempotency_key": "idem_a1",
"status": "PENDING    SUCCESS    FAILED",
"provider_refund_id": "psp_rf_9xz",
"created_at": "2025-11-03T12:00:00Z",
"credited_at": "2025-11-03T15:05:00Z",
"notes": "ticket #456"
}
],
"flags": {
"refund_to_source": true,
"jurisdiction": "EEA",
"kyc_tier_required": "tier2"
}
}

7) Особенности по платежным рельсам

Карты (Visa/Mastercard)

Поддерживают full/partial; часто несколько partial; TtR зависит от банка клиента (T+1…T+5 б.д.).
Вебхуки об успехе приходят быстро, но зачисление на выписке может запаздывать → объясняем в шаблонах саппорта.

A2A/Open Banking/RTP

Часто мгновенный возврат (reversal/credit push); некоторые провайдеры поддерживают только full или 1 partial.
Строгая привязка к исходному счету; refund-to-source обязателен.

Электронные кошельки

Обычный full/partial; TtR минутами; ограничения по количеству partial и минимальной сумме.

Ваучеры/Prepaid

Обычно refund-to-source недоступен → политика: возврат во внутренний кошелек или re-issue ваучера (если провайдер умеет). Требует комплаенс-оговорок.

Крипто

Рельсы — волатильные; предпочтительно не использовать как метод рефанда. Если разрешено: возврат на тот же адрес/биржу с документированным курсом и комиссиями; AML-скрининг.

8) Учет, сверки и финансы

Ledger: проводки `DR Revenue / CR Cash` при capture; при refund — обратные записи. Partial отражается пропорционально.
Recognition: в iGaming рефанд уменьшает GGR соответствующего периода (учетная политика).
Reconciliation: ежедневные сверки `merchant_refund_id ↔ provider_refund_id`, статусы, суммы, курсы FX.
FX: фиксируйте логику курсов (на момент capture или на момент refund), где применимо; держите решетку спредов.

9) KPI, цели и алерты (Refund Health)

Refund Rate = `Refunded_Tx / Captured_Tx` (сегментировать: по причинам).
Refund Amount Ratio = `Refunded_Amount / Captured_Amount`.
TtR p95 = p95(`credited_at - created_at`) по методу.
Refund Error Rate = `Failed / Attempted` (<0.3%).
Refund-to-Source % ≥ 95% (где доступно).
Double Refund Incidents = 0.

Алерты:
  • `TtR p95` выше SLO по методу → P2.
  • Spikes по `Refund Rate` в одном провайдере/BIN → P1 (проверить захваты/дубли).
  • Любой `Double Refund > 0` → P0 (немедленная заморозка авто-рефандов).

10) SQL-срезы

10.1 Профиль рефандов

sql
SELECT
DATE_TRUNC('day', r. created_at) AS d,
method_code, provider,
COUNT() FILTER (WHERE r. status='SUCCESS')  AS refunds_ok,
COUNT() FILTER (WHERE r. status='FAILED')  AS refunds_fail,
SUM(r. amount) AS refunded_amount,
PERCENTILE_CONT(0. 95) WITHIN GROUP (ORDER BY EXTRACT(EPOCH FROM (r. credited_at - r. created_at))) AS ttr_p95_sec
FROM refunds r
JOIN payments p ON p. payment_id = r. payment_id
GROUP BY 1,2,3;

10.2 Контроль остатка для partial

sql
SELECT p. payment_id,
p. captured_amount,
SUM(r. amount) AS refunded_sum,
(p. captured_amount - SUM(r. amount)) AS refundable_left
FROM payments p
LEFT JOIN refunds r ON r. payment_id = p. payment_id AND r. status IN ('SUCCESS','PENDING')
GROUP BY 1,2
HAVING (p. captured_amount - SUM(r. amount)) < 0;

11) UX и саппорт

Шаблоны сообщений по методам: картам объясняем возможную задержку на выписке, A2A — почти мгновенно.
Статусы в кабинете: `Оформлен → В обработке → Возвращен`; показывать ожидаемую дату зачисления.
Причины (reason_code) — человекочитаемые: `Дублирование списания`, `Отмена услуги`, `Частичная компенсация`.
Self-service partial — безопасно только с лимитами и четкими правилами.

12) Риск и комплаенс

Анти-отмывание: рефанд не должен превращаться в вывод на альтернативный канал; фиксируйте исключения с MLRO-одобрением.
Санкции/PEP: при возвратах, инициированных на «красные» аккаунты/реквизиты — обязательная проверка.
DSAR/Retention: храните следы рефандов в рамках политики хранения данных.
Локальные правила: сроки и порядок возвратов (например, потребительские регламенты) — отражаем в policy.

13) Частые ошибки и как их избежать

Двойной рефанд из-за отсутствия идемпотентности и повторных вебхуков → хранить idem-ключ/статус, проверять остаток.
Partial > остаток → row-lock/optimistic version и строгие проверки.
Cross-method refund без комплаенс-разрешения → нарушает refund-to-source.
Смешение void и refund в отчетах → искажение KPI.
Нет авто-сверок → «черные дыры» между PSP и вашим леджером.

14) Плейбуки

Всплеск возвратов по провайдеру → проверить авторизационные сбои/дубли capture, включить фейловер, контакт с PSP.
Массовые partial-компенсации (кампания) → поднять лимит partial, включить групповые операции, усилить сверки.
Ошибка вебхуков → переключиться на поллинг, увеличить TTL идемпотентности, отложить авто-рефанды.
Исключение refund-to-source (редко) → эскалация MLRO, документированная выплата и пометка `comp_approved=true`.

15) Тест-кейсы (UAT/Prod)

1. Full refund после одного capture → правильно обнуляет остаток.
2. Серия partial (3×) → сумма ≤ capture; затем full на остаток.
3. Идемпотентность: повтор одного и того же запроса → 1 результат.
4. Вебхук-дребезг: 3 одинаковых уведомления → одно списание/зачисление.
5. Сверки: искусственный mismatch → алерт и автокоррекция.
6. Ограничение прав: агент не может превысить лимит partial.
7. Cut-off: попытка позднего рефанда → корректный отказ и логирование.

16) Контрольный чек-лист внедрения

  • Политики full/partial + refund-to-source по юрисдикциям/методам.
  • Идемпотентность, ретраи, вебхуки и поллинг, DLQ.
  • Модель данных с остатком к возврату и reason_code.
  • Леджер и ежедневные авто-сверки.
  • KPI/дашборд: Refund Rate, TtR, Error, Double Refund=0.
  • Права и аппрув-матрица, шаблоны саппорта.
  • Тест-кейсы UAT и алерты прод-уровня.

Резюме

Управление рефандами — это строгая дисциплина процессов: refund-to-source, идемпотентность, прозрачная модель данных, авто-сверки и понятные политики partial/full. С такими основами вы держите TtR низким, ошибки — у нуля, дубли — невозможны, а комплаенс и финансы — синхронизированы с бизнес-целями.

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Telegram
@Gamble_GC
Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.