Transit matching — BPMN flow & loss analysis
UPI P2P · Индия · snapshot 2026-05-05 · окно baseline 7 дней
Эта страница описывает полный путь движения денег при transit-матчинге payin ↔ payout, включая все возможные исходы (happy path, обрывы, потери). Цвета: успех · задержка / простой · потеря.
backend / алгоритм
юзер / виджет
авто-проверка / антифрод
саппорт / REVIEW
успешный исход
исход с потерей
исход без потерь, но с задержкой
1. BPMN-flow — три блока
Блок A. Подбор payout и создание payin
flowchart TD
M0(["Мерчант создаёт ClientPayout
status=PENDING"])
M0 --> POOL[("Pool ожидающих payouts")]
ENTRY(["Юзер инициирует payin
Orbit/MoXB/MRGB виджет"])
ENTRY --> PRE{"Pre-checks: amount, sender,
conversion ≥ 30 percent,
blacklist, partner enabled"}
PRE -->|fail| TR1(["payin отдан в trader pool"]):::gp
PRE -->|pass| MUTEX["connectPayoutMutex.lock"]
MUTEX --> ALG["getPayoutBankAccountForPayinP2P:
фильтр amount A/B/C,
countMatchingFailures менее 5,
roundness guard,
ORDER BY 7 ключей"]
POOL --> ALG
ALG --> FOUND{"Кандидат найден?"}
FOUND -->|нет| TR2(["payin в trader pool"]):::gp
FOUND -->|да| CREATE["Создан ClientPayin:
bankAccount=TRANSIT,
payout=Б, manager=transitMgr,
status=PENDING"]
CREATE --> NEXT(["далее → Блок B"]):::next
classDef gp fill:#e2e8f0,stroke:#94a3b8,color:#334155
classDef next fill:#dbeafe,stroke:#3b82f6,color:#1e40af
Блок B. Юзер на виджете
flowchart TD
START(["ClientPayin создан, юзер видит реквизиты"]):::next
START --> COPY{"Скопировал реквизиты?"}
COPY -->|нет, ушёл| T7(["7 мин timeout
payin REJECT
payout free"]):::delay
COPY -->|да| BANK["Идёт в банк, делает перевод"]
BANK --> COMPAT{"Банк-эмитент совместим
с recipient bank?"}
COMPAT -->|нет| GIVE_UP(["Cross-bank reject,
юзер уходит → 7 мин timeout"]):::delay
COMPAT -->|да, перевод сделан| CLAIM{"Нажал я заплатил?"}
CLAIM -->|нет| T30{"Подал апелляцию за 30 мин?"}
CLAIM -->|да| RECEIPT["Загрузил квитанцию или UTR"]
T30 -->|нет| LATE_NA(["payin REJECT"]):::delay
T30 -->|да| REVIEW(["далее → Блок C, REVIEW"]):::next
RECEIPT --> AUTO(["далее → Блок C, авто-проверка"]):::next
classDef delay fill:#fef3c7,stroke:#f59e0b,color:#78350f
classDef next fill:#dbeafe,stroke:#3b82f6,color:#1e40af
Блок C. Авто-проверка, закрытие, late appeals
flowchart TD
AUTO_IN(["payin с квитанцией"]):::next
AUTO_IN --> AUTO{"Авто-проверка:
account, amount, date, utr"}
AUTO -->|OK| MATCH["payin COMPLETED,
Transaction created,
payoutAmountPaid += bnCalc"]
AUTO -->|partial| REVIEW
AUTO -->|очевидный фрод| RJ_F(["payin REJECT фрод"]):::delay
AUTO -->|сомнительно| REVIEW
REVIEW["REVIEW, ждёт саппорта часами"]
REVIEW --> SUP{"Решение саппорта"}
SUP -->|approve| MATCH
SUP -->|decline| RJ_S(["payin DECLINED"]):::delay
MATCH --> OVER{"Переплата amount больше остатка?"}
OVER -->|нет| CLOSED{"paid больше или равен amount?"}
OVER -->|да| EPK{"EPK-8290 разрешён?"}
EPK -->|нет| GIFT(["Expense SYSTEM_ERRORS
gift loss"]):::loss
EPK -->|да| INC["payoutAmount увеличен,
failures=0"]
INC --> CLOSED
CLOSED -->|да| OK(["payout COMPLETED,
manager=NULL"]):::ok
CLOSED -->|нет, остаток| PART[("PROCESSING с partial,
назад в pool")]:::delay
RJ_F --> FI["failures += 1,
payout free"]
RJ_S --> FI
FI --> STK{"failures больше или равен 5?"}
STK -->|нет| BACK[("payout назад в pool")]:::delay
STK -->|да| BLK[("payout заблокирован,
acqUnlocker 3 мин")]:::delay
%% downstream потери
OK -.->|merchant обнаружил недополучение| LATE_M(["Апелляция мерчанта
2x выплата"]):::loss
OK -.->|sender chargeback| CB(["Sender chargeback"]):::loss
RJ_F -.->|sender апеллирует позже| LP(["Late appeal sender
возврат игроку"]):::loss
RJ_S -.-> LP
GIFT -.-> LP
classDef ok fill:#d1fae5,stroke:#10b981,color:#065f46
classDef loss fill:#fee2e2,stroke:#ef4444,color:#7f1d1d
classDef delay fill:#fef3c7,stroke:#f59e0b,color:#78350f
classDef next fill:#dbeafe,stroke:#3b82f6,color:#1e40af
2. Перечень всех исходов (end states)
| # | Исход | Что произошло | Тип | Триггер потери (если есть) |
| E1 | OK_FULL | payout полностью закрыт одним или несколькими payin'ами в пределах tolerance | успех | — |
| E2 | OK_PARTIAL_PROGRESS | payout частично закрыт, ожидает следующий payin (только для Orbit/MoXB/MRGB) | успех в процессе | — |
| E3 | OK_EPK_OVERPAY | переплата съедена через расширение payoutAmount (EPK-8290), мерчант согласен | успех с расширением | — |
| E4 | FREE_TIMEOUT_7M | юзер ушёл сразу не скопировав реквизиты, payin REJECTED, payout free | simple reject | — |
| E5 | FREE_TIMEOUT_30M | юзер ушёл с реквизитами, не нажал «я заплатил», апелляции нет за 30 мин | simple reject | — |
| E6 | CROSS_BANK_REJECT | банк-эмитент юзера не поддерживает банк recipientCard, юзер уходит → timeout 7M | capacity простой | — |
| E7 | REVIEW_MANUAL_OK | чек partial/sus → REVIEW → саппорт apdrove → COMPLETED | задержка часов | растягивает p90 |
| E8 | REVIEW_MANUAL_DECLINE | чек подозрительный → REVIEW → саппорт declined | simple reject | возможна late appeal от sender → E11 |
| E9 | STUCK_FAILURES_5 | countMatchingFailures ≥ 5 → payout заблокирован, освобождает acqUnlocker | long delay | растягивает p90, операторская нагрузка |
| E10 | PARALLEL_RACE | два payin на один payout, один лишний | duplicate риск | возможна апелляция → E11/E12 |
| E11 | LATE_APPEAL_PAYIN | payin REJECTED, но позже sender апеллирует → нам нужно вернуть | потеря: возврат игроку | 40k+ за 30 дней |
| E12 | LATE_APPEAL_PAYOUT | payout COMPLETED, но мерчант обнаружил недополучение → апелляция → 2-я выплата | потеря: 2× выплата | 5 за 30 дней (только IMPS); UPI = 0 |
| E13 | SENDER_CHARGEBACK | после успеха sender инициировал chargeback в банке-источнике | потеря (кто несёт — уточнить) | не измерено |
| E14 | FAKE_RECEIPT_AUTO_OK | фейк / повторный UTR прошёл авто-проверку → деньги не пришли → апелляция → 2× выплата | потеря: false-positive payin | часть E12 |
| E15 | OVERPAY_GIFT_LOSS | переплата без EPK-разрешения → Expense SYSTEM_ERRORS | потеря: gift | измеримая величина |
| E16 | FRAUD_PASSED_ANTISCAM | sender прошёл pre-checks (conv≥30%, не blacklist), но фрод | потеря | часть E12 / E14 |
3. Где реально теряются деньги (категории)
| Категория | Кейсы | Природа | Frequency |
Возврат игроку (false-negative payin) | E11, E5+late, E8+late, E10 | Мы reject'нули payin → sender апеллирует → возврат | 40 095 apel/30d |
2× выплата мерчанту (false-positive payin) | E12, E14, E16 | Поверили payin'у-фейку → закрыли payout → мерчант обнаружил → 2-я выплата | ~5 apel/30d (только IMPS, UPI=0) |
| Структурные потери | E15 (gift), частично E3 | Переплата съедается без учёта | измерим из Expense |
| Внешний chargeback | E13 | Sender отозвал перевод в своём банке после успеха | не измерено |
4. R-1 — early-close hypothesis: данные
4.1 Стороны апелляций (30 дней, UPI transit)
- 40 095 апелляций от sender'ов (payin-side)
- 5 апелляций от мерчантов (payout-side) — все IMPS, по UPI 0
4.2 Apel rate per payin status
| Статус payin | Payins/30d | С апелляцией | Rate |
| successed_by_partner | 252 753 | 15 272 | 6.04 % |
| rejected_by_partner | 250 971 | 2 434 | 0.97 % |
| review | 291 | 22 | 7.56 % |
4.3 Распределение времени от создания payin до первой апелляции
| Окно | Apel | % | Кумулятив |
| 0..5 мин | 7 152 | 39.9 | 39.9 |
| 5..15 мин | 4 046 | 22.6 | 62.5 |
| 15..30 мин | 367 | 2.0 | 66.5 |
| 30..60 мин | 272 | 1.5 | 68.0 |
| 1..3 ч | 498 | 2.8 | 70.8 |
| 3..12 ч | 1 692 | 9.4 | 80.2 |
| 12..24 ч | 1 008 | 5.6 | 85.8 |
| 1..3 дня | 2 207 | 12.3 | 98.1 |
| 3..7 дней | 491 | 2.7 | 100.8 |
| >7 дней | 201 | 1.1 | ~ |
4.4 Выводы
- Merchant-side апелляции по UPI = 0. Early-close payout безопасен по этой оси.
- 62 % sender-апелляций приходит за 15 мин от создания payin'а — это внутри текущего pending-окна (7+30 мин). Удлинять окно почти не поможет — апелляции уже в нём.
- 6 % successful payins получают апелляцию — это огромный поток (15k/мес). Это и есть основной канал потерь категории «возврат игроку». Лекарство: лучший fingerprint sender'а, scoring по поведению на виджете.
- Длинный хвост 1–3 дня (12.3 %) — отложенные апелляции, поймать удержанием pending не получится, payin давно закрыт.
4.5 Переформулировка гипотезы
Идея не «держать payin дольше», а разделить судьбу payin и payout:
- Сейчас: payin pending → блокирует payout → 30 мин → reject → payout free для другого
- Альт: закрыть payout по слабому позитивному сигналу (квитанция есть, но с дефектами) → payin в long-pending с флагом «possibly invalid» → если апелляция придёт за 24ч (85% поймали бы) → разбираемся; иначе → confirm
Эффект: −часы от p90 при стоимости небольшого incremental loss budget. Симулируется на исторических данных (есть всё нужное в appeals + payments).
5. Базовые метрики (snapshot 2026-05-05)
| Метрика | Значение |
| p50 time-to-close UPI payout | 58 мин |
| p75 | 281 мин (4.7 ч) |
| p90 (целевой ≤ 60 мин) | 672 мин (11.2 ч) |
| p99 | 16.6 ч |
| Closure path: transit_only | 99.6 % |
| Closure path: trader_only | 0.2 % (avg ₹16k) |
| Capacity payin/payout по count (сутки) | +28 % payin'ов |
| Capacity payin/payout по volume (сутки) | 76 % (дефицит 24%) |
| Utilization payin: Orbit / MoXB / MRGB | 30 % / 28 % / 32 % |