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)

#ИсходЧто произошлоТипТриггер потери (если есть)
E1OK_FULLpayout полностью закрыт одним или несколькими payin'ами в пределах toleranceуспех
E2OK_PARTIAL_PROGRESSpayout частично закрыт, ожидает следующий payin (только для Orbit/MoXB/MRGB)успех в процессе
E3OK_EPK_OVERPAYпереплата съедена через расширение payoutAmount (EPK-8290), мерчант согласенуспех с расширением
E4FREE_TIMEOUT_7Mюзер ушёл сразу не скопировав реквизиты, payin REJECTED, payout freesimple reject
E5FREE_TIMEOUT_30Mюзер ушёл с реквизитами, не нажал «я заплатил», апелляции нет за 30 минsimple reject
E6CROSS_BANK_REJECTбанк-эмитент юзера не поддерживает банк recipientCard, юзер уходит → timeout 7Mcapacity простой
E7REVIEW_MANUAL_OKчек partial/sus → REVIEW → саппорт apdrove → COMPLETEDзадержка часоврастягивает p90
E8REVIEW_MANUAL_DECLINEчек подозрительный → REVIEW → саппорт declinedsimple rejectвозможна late appeal от sender → E11
E9STUCK_FAILURES_5countMatchingFailures ≥ 5 → payout заблокирован, освобождает acqUnlockerlong delayрастягивает p90, операторская нагрузка
E10PARALLEL_RACEдва payin на один payout, один лишнийduplicate рисквозможна апелляция → E11/E12
E11LATE_APPEAL_PAYINpayin REJECTED, но позже sender апеллирует → нам нужно вернутьпотеря: возврат игроку40k+ за 30 дней
E12LATE_APPEAL_PAYOUTpayout COMPLETED, но мерчант обнаружил недополучение → апелляция → 2-я выплатапотеря: 2× выплата5 за 30 дней (только IMPS); UPI = 0
E13SENDER_CHARGEBACKпосле успеха sender инициировал chargeback в банке-источникепотеря (кто несёт — уточнить)не измерено
E14FAKE_RECEIPT_AUTO_OKфейк / повторный UTR прошёл авто-проверку → деньги не пришли → апелляция → 2× выплатапотеря: false-positive payinчасть E12
E15OVERPAY_GIFT_LOSSпереплата без EPK-разрешения → Expense SYSTEM_ERRORSпотеря: giftизмеримая величина
E16FRAUD_PASSED_ANTISCAMsender прошёл 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
Внешний chargebackE13Sender отозвал перевод в своём банке после успехане измерено

4. R-1 — early-close hypothesis: данные

4.1 Стороны апелляций (30 дней, UPI transit)

4.2 Apel rate per payin status

Статус payinPayins/30dС апелляциейRate
successed_by_partner252 75315 2726.04 %
rejected_by_partner250 9712 4340.97 %
review291227.56 %

4.3 Распределение времени от создания payin до первой апелляции

ОкноApel%Кумулятив
0..5 мин7 15239.939.9
5..15 мин4 04622.662.5
15..30 мин3672.066.5
30..60 мин2721.568.0
1..3 ч4982.870.8
3..12 ч1 6929.480.2
12..24 ч1 0085.685.8
1..3 дня2 20712.398.1
3..7 дней4912.7100.8
>7 дней2011.1~

4.4 Выводы

  1. Merchant-side апелляции по UPI = 0. Early-close payout безопасен по этой оси.
  2. 62 % sender-апелляций приходит за 15 мин от создания payin'а — это внутри текущего pending-окна (7+30 мин). Удлинять окно почти не поможет — апелляции уже в нём.
  3. 6 % successful payins получают апелляцию — это огромный поток (15k/мес). Это и есть основной канал потерь категории «возврат игроку». Лекарство: лучший fingerprint sender'а, scoring по поведению на виджете.
  4. Длинный хвост 1–3 дня (12.3 %) — отложенные апелляции, поймать удержанием pending не получится, payin давно закрыт.

4.5 Переформулировка гипотезы

Идея не «держать payin дольше», а разделить судьбу payin и payout:

Эффект: −часы от p90 при стоимости небольшого incremental loss budget. Симулируется на исторических данных (есть всё нужное в appeals + payments).

5. Базовые метрики (snapshot 2026-05-05)

МетрикаЗначение
p50 time-to-close UPI payout58 мин
p75281 мин (4.7 ч)
p90 (целевой ≤ 60 мин)672 мин (11.2 ч)
p9916.6 ч
Closure path: transit_only99.6 %
Closure path: trader_only0.2 % (avg ₹16k)
Capacity payin/payout по count (сутки)+28 % payin'ов
Capacity payin/payout по volume (сутки)76 % (дефицит 24%)
Utilization payin: Orbit / MoXB / MRGB30 % / 28 % / 32 %