GH GambleHub

Сверка платежей и отчетов PSP

TL;DR

Сверка — это ежедневное автоматизированное сшивание вашего леджера и событий (auth/capture/refund/payout) с отчетами PSP/эквайеров/банков. Ключ к успеху: единая модель данных, детерминированные ключи сопоставления, строгая идемпотентность, толерансы по суммам/FX/времени, очередь DLQ для спорных кейсов и плейбуки автокоррекций. KPI: Recon Mismatch Rate↓, Aging of Unreconciled↓, Auto-match%↑.

1) Зачем и что сверяем

Цели: подтверждение выручки и комиссий, обнаружение дублей/утерь, корректный settlement по дням и валютам, контроль refund-to-source, соответствие аудиту/регулятору.

Объекты сверки:
  • Deposits: `auth → capture → settle`
  • Refunds: full/partial, статусы и суммы
  • Payouts/Withdrawals: исходящие платежи по методам
  • Fees & Adjustments: комиссии PSP, схемы интерчейнджа, коррекции
  • Chargebacks/Disputes: вне вашей инициативы
  • FX/Конвертации: курсы, спреды, момент фиксации

2) Источники данных

Internal Events (ваши): шина событий/Kafka, `payments_flat`, `refunds`, `payouts`, ваш Ledger.

PSP Reports (SFTP/API/webhook dump):
  • Transactions (операционные выписки)
  • Settlements/Batches (разбивки зачислений T+N)
  • Fees/Statements (комиссии, корректировки)
  • Chargebacks/Disputes
  • Payouts/OCT/RTP/SEPA реестры
  • Bank Statements: MT940/CSV/ISO20022 CAMT, подтяжка зачислений.
💡 Хранилище: landing → raw → normalized → matched. Все входящие файлы — с контрольными суммами и версионированием.

3) Ключи сопоставления (matching keys)

Приоритетное дерево ключей (по убыванию точности):

1. provider_txid ↔ provider_txid (уникальный ID PSP)

2. idempotency_key / merchant_reference (если стабильны у PSP)

3. (amount, currency, timestamp_bucket, last4/bin, auth_code)

4. Fuzzy-слой: ±толерансы по сумме/времени, BIN/issuer country, status family

Рекомендации:
  • Храните у себя оба: `payment_id` и `provider_txid`.
  • Для partial/refund — добавляйте `sequence_index` или `refund_line_id`.
  • Для payouts — `payout_batch_id + line_id`.
  • Для FX — `exec_id` (конвертация) и источник курса.

4) Модель данных (нормализованный слой)

json
{
"source": "INTERNAL    PSP_TX    PSP_SETTLEMENT    BANK",
"provider": "Acquirer_A",
"payment_id": "pay_123",
"provider_txid": "psp_tx_789",
"kind": "AUTH    CAPTURE    REFUND    PAYOUT    FEE    SETTLEMENT    CHARGEBACK",
"sequence": 0,
"amount": 100. 00,
"currency": "EUR",
"fee_amount": 1. 20,
"fx_rate": 1. 0000,
"fx_src": "PSP    ECB    BANK",
"status": "APPROVED    CAPTURED    SUCCESS    FAILED    SETTLED",
"event_ts": "2025-11-03T12:00:00Z",
"settlement_date": "2025-11-05",
"account": "PSP_MERCHANT_CARD_A",
"matching_keys": {
"provider_txid": "psp_tx_789",
"merchant_ref": "mr_456",
"idem_key": "idem_abc"
},
"hash_row": "sha256(...)"
}

5) Процесс сверки (ETL/оркестрация)

1. Ingest: забираем отчеты PSP (SFTP/API), валидируем схему/подписи, сохраняем в `raw`.
2. Normalize: маппим поля в унифицированный формат (currency ISO, decimals, таймзона UTC).
3. Match: алгоритм сопоставления по дереву ключей с толерансами.
4. Post-match: формируем diff (расхождения) и journal entries для леджера/коррекций.
5. Settle: сшиваем `PSP_SETTLEMENT ↔ BANK` (зачисления на счет), разбрасываем по дням/батчам.
6. Report: дашборд, алерты; спорные в DLQ на ручной разбор/автопереигровку.

Идемпотентность: на каждый файл/страницу — `ingest_id`. Повторная загрузка не изменяет результат.

6) Толерансы (tolerances) и правила

Время: `±15 мин` для транзакций, `±1 дн` для settlement.
Сумма: `≤ 0.01` базовой валюты или `≤ 10 bps` для FX/fee-разниц.
FX: допускаем расхождение с банком, если источник курса разный; фиксируем `fx_src`.
Partial/Multiple: сумма по линиям partial/refund должна равняться внутреннему остатку.

7) Обработка расхождений (diff taxonomy)

Тип diffОписаниеДействие
MISSING_INTERNALВ PSP есть, у нас нетСоздать orphan case, проверить webhooks/ретраи
MISSING_PSPУ нас есть, у PSP нетПроверить статус/повтор, контакт PSP
AMOUNT_MISMATCHСуммы различаются > толерансАвтокоррекция/журнал, эскалация при необходимости
FEE_MISMATCHКомиссии отличаютсяПринять PSP как «истину» (policy) или потребовать credit note
STATUS_DRIFTCAPTURE у нас, AUTH у PSPПроверить вебхуки capture/settlement
DUPLICATEДубли строкДедуп по `provider_txid/idempotency_key`
FX_DRIFTКурсы расходятсяЗадать официальный источник, скорректировать P&L
REFUND_OVERRefund > capturedСрочный блок, ручной разбор, журнал обратной коррекции

8) Ledger & Accounting (проводки)

Capture: `DR Accounts Receivable / CR Revenue` и `DR Cash (upon settle) / CR Accounts Receivable`

Fee: `DR Fees / CR Cash or Payable`

Refund: обратные проводки пропорционально partial

Chargeback: отдельные счета и резерв под споры

FX reval: ежедневная переоценка остатка AR/кэша по курсу `fx_src_policy`

9) KPI и цели

Auto-match % = автоматически сопоставленные строки / все строки (цель ≥ 95%)

Recon Mismatch Rate = diff-строки / все строки (≤ 1–2%)

Aging of Unreconciled: p50/p95 дней нахождения в DLQ (p95 ≤ 3 дн)

Settlement Timeliness: доля батчей, сшитых с банком D-день (≥ 99%)

Fee Accuracy: расхождения комиссий по провайдерам (≤ 0.1% оборота)

Duplicate/Orphan Incidents: стремится к 0

10) SQL-срезы

10.1 Базовое сопоставление по provider_txid

sql
WITH i AS (
SELECT provider, provider_txid, kind, amount, currency, event_ts
FROM internal_norm
),
p AS (
SELECT provider, provider_txid, kind, amount, currency, event_ts
FROM psp_norm
)
SELECT
COALESCE(i. provider_txid, p. provider_txid) AS txid,
COALESCE(i. provider, p. provider) AS provider,
i.kind AS kind_internal, p. kind AS kind_psp,
i.amount AS amount_internal, p. amount AS amount_psp,
i.currency, p. currency,
CASE
WHEN i.provider_txid IS NULL THEN 'MISSING_INTERNAL'
WHEN p. provider_txid IS NULL THEN 'MISSING_PSP'
WHEN ABS(i. amount - p. amount) > 0. 01 THEN 'AMOUNT_MISMATCH'
ELSE 'MATCHED'
END AS recon_status
FROM i
FULL OUTER JOIN p USING (provider, provider_txid);

10.2 Settlement ↔ Bank

sql
SELECT s. settlement_date, s. batch_id, s. currency,
s. amount_settled, b. amount_bank,
(b. amount_bank - s. amount_settled) AS diff
FROM psp_settlements s
LEFT JOIN bank_statements b
ON b. value_date = s. settlement_date
AND b. currency = s. currency
AND ABS(b. amount_bank - s. amount_settled) <= 0. 5;

10.3 Aging DLQ

sql
SELECT diff_type,
COUNT() AS cnt,
PERCENTILE_CONT(0. 5) WITHIN GROUP (ORDER BY AGE(NOW(), created_at)) AS p50_age,
PERCENTILE_CONT(0. 95) WITHIN GROUP (ORDER BY AGE(NOW(), created_at)) AS p95_age
FROM recon_dlq
GROUP BY diff_type
ORDER BY cnt DESC;

11) Особенности по рельсам/кейсам

Карты: различия между `auth` и `capture`, поздние `settlement` корректировки, интерчейндж/схемные fee — отдельными линиями.
A2A/Open Banking/RTP: мгновенные подтверждения, но возможны `reversal`; сверяйте `payout` и возвраты.
Кошельки: часто идеальные `provider_txid`, быстрые `refund`; следите за fee-линиями.
Ваучеры: нет симметричного refund — отражайте корректно в политике и отчетах.
Крипто: он-чейн hash ↔ provider_txid; конфирмы N; учет комиссий сети и возможных ребейтов; курс — на момент конвертации.

12) Операционные плейбуки

Всплеск MISSING_INTERNAL: проверить потерю вебхуков/ретраев, переиграть ingestion, включить поллинг API.
AMOUNT_MISMATCH у одного PSP: сравнить округления/НДС/fee-модель, запросить корректирующий statement.
Settlement не сшивается с банком: проверить value date, комиссии банка, задержки T+N; временно ставить в «Suspense Account».
Массовые REFUND_OVER: немедленный стоп авто-рефандов, аудит идемпотентности, ручная коррекция.
FX_DRIFT: зафиксировать policy источника курса (ECB/PSP/BANK), пересчитать P&L-разницы.

13) Контроль и безопасность

Идемпотентность ingestion: `file_id + checksum` и журнал загрузок.
Доступы (RBAC) и 4-глазный контроль: на ручные коррекции/журнальные проводки.
Аудит-трейл: все матчи/дифы/коррекции — в неизменяемом журнале.
Качество данных: схемы, обязательные поля, валидация валют/скейла.

14) Дашборд и алерты

Виджеты: Auto-match%, Mismatch Rate, Aging DLQ, Settlement Timeliness, Fee Accuracy, топ-PSP по дифам, карта diff-типов.

Алерты:
  • `Auto-match% < 90%` по провайдеру/дню → P1
  • `Aging p95 > 3 дн` → P2
  • `AMOUNT_MISMATCH spike` → P1
  • `Bank≠Settlement` по сумме/валюте → P0

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

1. Повторная загрузка того же файла → 0 побочных эффектов (идемпотентность).
2. Частичные рефанды (3 линии) → сумма совпадает, матч по sequence.
3. FX-конвертация: расхождение курса в пределах толеранса → корректный match.
4. Дубликаты provider_txid в отчете → дедуп и алерт.
5. Пропавший webhook capture → поллинг закрыл gap, статус выровнен.
6. Settlement батч с fee-линией → правильная разбивка на Revenue/Fee/Net.

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

Смешение баз сравнения (compare `attempt` vs `capture`) → держите одинаковую гранулярность.
Отсутствие `provider_txid` в логе → теряется точность матча.
Игнор таймзон → смещения по датам settlement.
Нет DLQ/ретраев → «вечные» расхождения.
Ручные правки без журнала → несходимость с аудитом.
Нечеткие толерансы → либо пере-матч, либо «все в DLQ».

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

  • Единая схема нормализации и справочники PSP/методов/аккаунтов
  • Дерево ключей сопоставления (txid → merchant_ref → fuzzy)
  • Толерансы по сумме/времени/FX, политика источника курса
  • Идемпотентный ingestion, DLQ, ретраи, алерты
  • Сверка Settlement↔Bank, Suspense Account политика
  • Дашборд KPI, отчетность для финансов/аудита
  • Плейбуки и SLA разбора diff-кейсов

Резюме

Сверка — это инженерная дисциплина: нормализация, надежные ключи, толерансы, автоматические матчи и прозрачные коррекции. С таким контуром вы стабилизируете выручку и комиссии, минимизируете «черные дыры», ускоряете закрытие периода и проходите аудит без боли: Auto-match%↑, Mismatch↓, Aging↓.

Contact

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

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

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

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

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

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