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-схваленням.
Санкції/РЕР: при поверненнях, ініційованих на «червоні» акаунти/реквізити - обов'язкова перевірка.
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).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.