# P2P 매칭 룰 — 현재 구현(AS-IS) 정리

> 코드 기준 정리 (2026-06-15). 개선점 도출 토대.
> 핵심 파일: `core/p2p/P2pMatchingService.java`, `common/mapper/P2pMatchingMapper.java`,
> `scheduler/job/P2pScrapingVerifyJob.java`, `scheduler/job/P2pMatchExpiryJob.java`

---

## 0. 상수 / 설정

| 항목 | 값/키 | 위치 |
|------|-------|------|
| 매칭 만료 | `MATCH_EXPIRY_MINUTES = 10`분 (CREATED/BANK_PENDING) | 상수 |
| 레거시 분할 한도 | `MAX_SPLIT_COUNT = 3` | 상수 |
| 통합 P2P 레그 한도 | `p2p.max_p2p_legs` (기본 2) | system_settings |
| 기본 네트워크 | `DEFAULT_NETWORK_ID = 3` (TRON) | 상수 |
| 통합 매칭 게이트 | `p2p.unified_matching_enabled` (기본 false) | system_settings |
| 통합 매칭 파트너 스코프 | `p2p.unified_matching_partner_ids` (CSV, 빈값=전체) | system_settings |
| TORQ fallback | `p2p.torq_fallback_enabled` (기본 true) | system_settings |
| 파트너 fallback | `p2p.partner_fallback_enabled` (기본 true) | system_settings |
| 수수료율 | `p2p.fee_rate` (0.02) | system_settings |

---

## 1. 진입 & 분기

`tryMatchDeposit(depositOrder)` — 입금 주문 생성(createAndMatch) 및 재매칭 시 호출.

```
remaining = depositOrder.remainingAmount  (≤0이면 종료)
게이트 ON(파트너 스코프 포함)?
  → YES: tryMatchDepositUnified  (폭포: P2P → TORQ → 파트너)
  → NO : 레거시 P2P 전액 정책
```

---

## 2. 라우팅 판정 (`checkRoute`, 위젯 매칭 전 호출, 읽기전용)

- 가용액 = `findAvailableWithdrawOrders`(PENDING/PARTIALLY_MATCHED, 잔여>0, 점검계좌 제외) 의 `(krw - matched)` 합.
- `route = available >= krwAmount ? "P2P" : "TORQ"` — **전액(100%) 커버 기준**.
- 네트워크 무관(구매자 체인 미지정). 전체 풀 합산.
- ⚠️ Javadoc엔 "70% 이상이면 P2P"로 적혀 있으나 **실제 코드는 100% 전액 기준** (주석 stale).

---

## 3. 레거시 P2P 매칭 (게이트 OFF) — 전액 성립만

**부분 충족 금지**: 입금 주문은 전액 매칭되거나 대기(MATCHING). 출금 부분 소진 없음.

1. **Exact 1:1**: `findExactMatchesForUpdate(remaining)` — `(krw-matched)=remaining`, FIFO, LIMIT 5 FOR UPDATE.
   점검계좌 제외 → 첫 잠금 성공 건 → `createMatch`(전액) → 주문 MATCHED.
2. **분할(≤3)**: `findMatchableWithdrawOrdersForUpdate` — 잔여>0, **잔여 큰 순(DESC) + FIFO**, LIMIT 10 FOR UPDATE.
   - 사전 계획: 점검 제외 + 큰 잔여 우선 + **(파트너,네트워크) 가용액 누적 검증**으로 잠금 실패 없이 실행 보장.
   - 계획 합 ≥ 주문액이면 일괄 잠금+매칭 → MATCHED. 아니면 **MATCHING 대기**(전액 유지).
- 잠금 USDT = `legAmount / exchangeRate` (RoundingMode.UP).

---

## 4. 통합 매칭 폭포 (게이트 ON) — `tryMatchDepositUnified`

```
1) P2P 레그 (최대 max_p2p_legs=2)
   findMatchableWithdrawOrdersForUpdate 순회, 점검 제외
   legAmount = min(available, remaining-covered), lockForMatch → createMatch(P2P)
   ※ 레거시와 달리 부분 충족 허용
2) 잔여(need = remaining - covered) 있으면 단일 슬롯:
   fillRemainderWithTorq(need)  ||  fillRemainderWithPartner(need)   (TORQ 우선)
3) 잔여 충족 → MATCHED / 미충족 → failUnifiedOrder (P2P 레그 롤백 + 주문 CANCELLED)
```

- **TORQ fallback**: `torq_fallback_enabled` + kycUid 필요 + getQuote(matchable & 전액) → createTrade(외부 escrow) → leg_type=TORQ(withdraw_order_id=null, torq_escrow_id).
  ⚠️ `createTrade`가 **매칭 트랜잭션 내 외부 동기 호출**(FOR UPDATE 잠금 보유 중) — TODO: AFTER_COMMIT 분리.
- **파트너 fallback**: `partner_fallback_enabled` + 파트너 서비스계좌(`bank_accounts` owner=PARTNER, **첫 번째**) + 파트너 MASTER 유동성 잠금 → standing 출금주문 즉석 생성(partner_user_id=PARTNER_STANDING, krw=need) → createMatch(PARTNER).

---

## 5. 매칭 생성 (`createMatch`)

- usdt = `krw / wo.exchangeRate` (DOWN), status=CREATED, expiresAt = now+10분.
- `wo.matchedAmount += krw` → matched≥krw면 FULLY_MATCHED, 아니면 PARTIALLY_MATCHED.
- 환율은 **각 출금 주문 스냅샷**(주문 생성 시점). 분할 시 레그마다 환율 다를 수 있음 → 구매자 혼합 환율.

---

## 6. 동일 계좌 레그 병합 (§12, 확인 레이어)

- 스크래핑 확인잡이 (입금주문, bank_account) 동일 활성 레그를 **합산 금액 1건**으로 대조 → 그룹 일괄 confirm. 정산은 레그별 분리. (서로 다른 판매자·다른 계좌면 여전히 분리 송금)

---

## 7. 재매칭 (`rematchWaitingDeposits`)

- 트리거(이벤트 기반): ① 신규 출금주문 생성 ② 매칭 취소 ③ 주문 취소 경로.
- 대상: `findWaitingDepositOrdersForUpdate`(PENDING/MATCHING, 잔여>0, **expires_at>NOW**, FIFO LIMIT 50 FOR UPDATE), 특정 id 제외 가능.
- ⚠️ **주기적(스케줄) 재매칭 없음** — 이벤트가 없으면 대기 입금은 만료까지 재시도 안 됨.

---

## 8. 만료 / 종료

- **매칭**: 10분(CREATED/BANK_PENDING) → `P2pMatchExpiryJob` → `failExpiredMatch`(잠금 해제 + 출금 복원, 매칭 단위 tx 격리).
- **입금 주문**: `expires_at` 기반 만료(주문 만료 잡).
- **출금 주문**: **자동 만료 없음** (2026-06-11 정책 — 회원/파트너 취소만).

---

## 9. 잠금 / 동시성

- 출금 주문 선점: `FOR UPDATE`(매퍼). 입금 주문도 재매칭 시 `FOR UPDATE`.
- P2P 유동성: `p2p_partner_locks`(파트너/네트워크) — lockForMatch / settleFromLocked / unlockForMatch.
- 출금 USDT 동결: settlement_balances frozen (별도).
- ⚠️ 잠금 순서 비대칭(취소 wo→dpo vs 재매칭 dpo→wo) — 이론상 데드락(MySQL 감지/롤백). 트래픽 증가 시 AFTER_COMMIT 분리 후보.

---

## 10. 정렬/우선순위 정책 요약

| 단계 | 정렬/선택 | 비고 |
|------|----------|------|
| Exact | FIFO(created_at ASC) LIMIT 5 | 첫 잠금 성공 |
| 분할/폭포 | 잔여 큰 순(DESC) + FIFO | 잔여 최소화 목적 |
| 재매칭 대기 | FIFO LIMIT 50 | |
| 라우팅 가용 | 잔여 큰 순 LIMIT 100 | 합산만 사용 |

판매자 공정성(로테이션)·파트너별 한도·가중치 **없음**.

---

## 11. 읽으면서 눈에 띈 개선 후보 (논의용)

1. **라우팅 doc/코드 불일치** — Javadoc 70% vs 코드 100% 전액. + 통합 게이트 ON이면 폭포가 부분 P2P+TORQ/파트너로 어차피 메우므로 route="TORQ" 판정 의미 축소 → 라우팅 로직을 통합 매칭과 재정합 필요.
2. **주기적 재매칭 부재** — 대기 입금이 이벤트 없으면 만료까지 방치. 점검창 해제·유동성 회복이 이벤트 없이 일어나면 stall. 주기 잡 검토.
3. **createTrade 매칭 tx 내 외부 호출** — 잠금 보유 중 외부 I/O. AFTER_COMMIT 분리(하드닝).
4. **분할 전략 트레이드오프** — "잔여 큰 순"은 잔여 최소화하나 카운터파티(=송금 횟수) 증가. §12로 동일계좌는 병합되나 다른 판매자면 여전히 다건 송금. fewest-legs vs 잔여최소 균형 검토.
5. **레거시 3 vs 통합 2 레그 한도 불일치** (`MAX_SPLIT_COUNT=3` vs `max_p2p_legs=2`).
6. **파트너 fallback 서비스계좌 = 무조건 첫 번째**(`accts.get(0)`) — 점검/잔액/다계좌 선택 로직 없음.
7. **혼합 체인 정산** — 네트워크 무관 매칭으로 한 입금이 BSC+TRON 레그로 분할 가능. 구매자 파트너가 각 체인 MASTER 보유 전제. TORQ/파트너 fallback은 DEFAULT(TRON) 고정.
8. **환율 혼합** — 레그별 출금주문 환율 스냅샷 → 한 구매에 여러 환율. 표시/공정성 정책 명확화.
9. **판매자 공정성/한도 부재** — 잔여크기+FIFO 외 우선순위 없음. 특정 판매자 편중 가능.
10. **라운딩** — 잠금 UP vs 매칭 DOWN 비대칭(동결 dust는 완료 시 해제로 처리 완료, but 일관성 점검).
11. **stale 주석/TODO** — 레거시 `tryMatchDeposit`의 "TODO TORQ fallback"은 통합에서 해소됨. §7.1 등 설계 참조 정리.
