# P2P 어드민 콘솔 재설계 스펙

> 작성 2026-06-30. 현재 어드민 P2P는 컨트롤러 6종이 존재하나 "골격만 있고 미성숙"하다.
> 본 문서는 (1) 메뉴/IA 구조, (2) 회원 중심 상세 뷰(우선), (3) 영역별 재설계+갭, (4) 공통 이슈,
> (5) 로드맵, (6) 데이터 모델 연결을 정의한다. 구현은 IntelliJ(백엔드)/admin-ui에서 본 스펙대로 수행.

---

## 0. 현 상태 진단 (왜 재정비)

전수 점검 결과 핵심 결함:

1. **상세 조회가 raw 엔티티 반환** — 출금/입금 주문 상세, 매칭 분쟁판정 응답이 DTO가 아닌 엔티티 직반환 → 프론트가 DB 스키마에 결합, 스키마 변경 시 API 깨짐.
2. **회원 중심 집계 부재** — 한 회원의 총출금/매칭/잔여/정산을 한 번에 보는 엔드포인트가 없음. 주문 목록 받아 수동 합산해야 함.
3. **회원 상세가 얕음** — 대표 은행계좌 1개만 노출(회원 다계좌 가능), 출금이력·매칭이력·인증 타임라인 없음.
4. **감사 누락** — 정지/활성/분쟁판정/주문취소 등 관리자 액션이 admin_audit_logs에 미기록(SLF4J 로그만).
5. **응답 비대칭** — 출금 주문에는 환불 필드(refundType 등), 입금 주문에는 없음. 유사 객체인데 형태 다름.
6. **수수료 설정 글로벌만** — 파트너 오버라이드가 코드엔 있으나 어드민에서 안 보임/관리 불가.
7. **대시보드 과도하게 상위만** — 추세(7d/30d), 정산 성공률, 분쟁 추이, 수수료 수익 없음.

---

## 1. 메뉴 / IA 구조 (P2P 관리)

### 1-1. 현재 (암묵적)
홈 > P2P 관리 > 주문 조회(출금/입금 탭) — 그 외 매칭/회원/정산/수수료/대시보드 화면이 산발적으로 존재하나 메뉴 체계가 정리되지 않음.

### 1-2. 제안 메뉴 트리

```
P2P 관리
├── 1) 현황 대시보드        — KPI + 추세 + 경보(분쟁/정산실패/잔액)
├── 2) 회원(사용자) 관리     ★ — 회원 목록 → 회원 상세(프로필/은행/인증/금액/이력/감사)
├── 3) 거래 관리           ★ — 입금 거래(주문) 단위. 거래정보·상태 + 매칭 건별(50+50): 당사자·타임라인·정산·TORQ (+분쟁)
├── 4) 출금 주문            — 출금자 주문(유동성 공급측) 목록/상세/취소
├── 5) 정산                 — 정산 목록/상세 + 재시도/취소 + 실패 리컨실
└── 6) 수수료 · 정책 설정    — 글로벌 수수료/보너스 + 파트너 오버라이드 + 매칭 정책(min_amount 등)
```

> **'거래' 단위 정의**: 거래 = **입금 주문(p2p_deposit_orders) 1건**. 입금 100만원이 50+50으로
> 매칭되면, 그 100만원 입금 주문이 하나의 '거래'이고 50+50 매칭은 그 거래 안의 **'매칭 건별'(레그)**이다.
> 거래 관리(§3, §2.5)는 입금 거래를 1급으로 다루고 매칭 건별을 하위로 펼친다. (기존 '입금 주문' 메뉴를 흡수.)
> 출금 주문은 공급측 성격이 달라(만료 없음·취소) 별도 메뉴로 유지.

공통: 모든 액션은 **감사 로그(admin_audit_logs)** 에 기록되고, 회원/주문/매칭/정산 화면은 서로 **딥링크**(회원→그 회원의 주문→그 주문의 매칭→그 매칭의 정산)로 이동 가능해야 한다.

### 1-3. 각 메뉴 목적 (1줄)

| # | 메뉴 | 목적 |
|---|------|------|
| 1 | 현황 대시보드 | 운영 한눈 파악 + 이상 징후 경보 |
| 2 | 회원 관리 | "이 사용자가 누구고 무엇을 했나"를 완전히 추적 (본 문서 §2) |
| 3 | **거래 관리** | **입금 거래(주문) 단위 추적 — 거래정보·상태 + 매칭 건별(당사자·타임라인·정산·TORQ) + 분쟁 (본 문서 §2.5)** |
| 4 | 출금 주문 | 공급측 주문 상태/잔여/취소 관리 |
| 5 | 정산 | 온체인/내부 정산 상태 + 실패 복구 |
| 6 | 수수료/정책 | 요율·보너스·매칭 파라미터 운영 |

---

## 2. 회원(사용자) 상세 뷰 — 우선 구현

> 요구: "사용자 - 파트너, 사용자 아이디, 사용자 페이지 생성일, 은행 설정, 인증여부 등 → 출금 금액, 매칭 금액, 잔여금액, 출금 이력 상세."
> 아래 4개 섹션을 한 화면(회원 상세)에 탭/카드로 구성한다.

### 2-1. 섹션 A — 프로필 + 은행 + 인증

**프로필 (P2pMember)**

| 표시 | 출처 컬럼 |
|------|-----------|
| 파트너 (코드/명) | p2p_members.partner_id → partners.partner_code, name |
| 사용자 ID | p2p_members.partner_user_id |
| 회원 토큰(페이지 링크) | p2p_members.member_token |
| 회원(페이지) 생성일 | p2p_members.created_at |
| 상태 | p2p_members.status (ACTIVE/SUSPENDED) |
| 핀 설정 여부/일시 | p2p_members.pin_set_at (null=미설정) |

**은행 설정 — 다계좌 전부** (bank_accounts WHERE owner_type=MEMBER AND owner_id=member.id)

| 표시 | 출처 |
|------|------|
| 은행코드/은행명 | bank_accounts.bank_code, bank_name |
| 계좌번호/예금주 | account_number, account_holder |
| 계좌 상태 | bank_accounts.status (ACTIVE/INACTIVE) |
| 스크래핑(빠른조회) 상태 | bank_accounts.scraping_status |
| 동의 만료일 | bank_accounts.scraping_consent_expires_at |

**인증여부 — 스크래핑 자격증명** (credentials, via bank_accounts.scraping_credential_id) — 계좌별로 표시

| 표시 | 출처 |
|------|------|
| 인증 상태 | credentials.status (CREATED/ACTIVE/EXPIRED/DEACTIVATED/REVOKED) |
| 빠른조회 패턴 | credentials.pattern_snapshot (A_TYPE0/B_NO_IDENTITY/D_FAST_PLUS_ACCT) |
| 사전등록 확인 | credentials.preregistered |
| 동의 활성/만료 | consent_activated_at / consent_expires_at |
| 최근 검증 성공 | last_verified_at |
| 최근 에러 | last_error_code / last_error_at |

> ⚠️ encrypted_blob(자격증명 평문)은 **절대 노출 금지**. 상태/타임라인만.
> "인증여부" = credentials.status==ACTIVE && consent_expires_at>now 로 판정해 뱃지(인증완료/만료/미인증)로 표기.

### 2-2. 섹션 B — 금액 집계

회원의 출금 주문(p2p_withdraw_orders WHERE partner_id=member.partner_id AND partner_user_id=member.partner_user_id) 기준 집계.

| 지표 | 산식 |
|------|------|
| 총 출금요청 KRW | Σ krw_amount (전체 주문) |
| 매칭완료 KRW | Σ matched_amount |
| 입금확정 KRW | Σ confirmed_amount |
| 잔여(미매칭) KRW | Σ (krw_amount − matched_amount), 활성 주문만 |
| 정산완료 USDT | Σ p2p_matches.usdt_amount WHERE status=SETTLED (해당 회원 출금주문의 매칭) |
| 진행중 주문 수 | count(status IN PENDING/PARTIALLY_MATCHED/FULLY_MATCHED/SETTLING) |
| 분쟁 건수 | count(matches.status=DISPUTED) |

> 표기 일관성: KRW는 정수, USDT는 소수. "잔여"는 활성 주문 기준(취소/완료 제외).

### 2-3. 섹션 C — 출금 이력 상세

주문별 행 + 펼치면 매칭 레그/정산.

**주문 레벨** (각 행)

| 열 | 출처 |
|----|------|
| 주문코드 | order_code (→ 출금주문 상세 딥링크) |
| 등록일 | created_at |
| 출금 KRW / USDT | krw_amount / usdt_amount |
| 매칭/확정/잔여 | matched_amount / confirmed_amount / (krw−matched) |
| 상태 | status |
| 입금계좌 | bank_account_id → 은행/계좌 |
| 종료시각 | settled_at/completed_at/cancelled_at |

**매칭 레그 (주문 펼침)** — p2p_matches WHERE withdraw_order_id=order.id

| 열 | 출처 |
|----|------|
| 매칭코드 | match_code (→ 매칭 상세 딥링크) |
| 레그유형 | leg_type (P2P/TORQ/PARTNER) |
| 상대 입금주문 | deposit_order_id → 파트너/유저 |
| KRW/USDT | krw_amount/usdt_amount |
| 상태 | status (… /SETTLED/FAILED/DISPUTED) |
| 은행확인/정산 | bank_confirmed_at / settled_at / settlement_tx_hash |
| 정산 | p2p_settlements(match_id) → status/tx_hash |

### 2-4. 섹션 D — 관리자 액션 + 감사

**액션** (감사 기록 필수)

| 액션 | 엔드포인트(기존/신규) | 비고 |
|------|----------------------|------|
| 회원 정지 | POST /members/{id}/suspend (기존) | 사유 입력 필수화 |
| 회원 활성 | POST /members/{id}/activate (기존) | |
| 핀 초기화 | (신규) POST /members/{id}/reset-pin | partner-api에 있음 → admin 이관/추가 |
| 출금주문 취소 | POST /orders/withdraw/{id}/cancel (기존) | 회원 상세에서 직접 호출 |

**감사 타임라인** — admin_audit_logs (신규 연동)

각 액션 시 `admin_audit_logs`에 (admin_id, action, target_type=P2P_MEMBER/ORDER, target_id, before/after, reason, created_at) 기록하고, 회원 상세 하단에 그 회원 관련 감사 이력을 시간순 표시.

### 2-5. 신규/확장 엔드포인트 (admin-api)

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/admin/p2p/members/{id}` | **확장** — 프로필+다계좌+인증+금액집계 모두 포함 (현재 대표계좌 1개/부분집계 → 전체로) |
| GET | `/api/admin/p2p/members/{id}/bank-accounts` | 회원 다계좌 + 계좌별 인증 상세 |
| GET | `/api/admin/p2p/members/{id}/withdraw-orders` | 회원 출금 주문 목록(페이지네이션) — 주문 레벨 |
| GET | `/api/admin/p2p/members/{id}/matches` | 회원 매칭 이력(레그 단위) |
| GET | `/api/admin/p2p/members/{id}/audit` | 회원 관련 감사 이력 |
| POST | `/api/admin/p2p/members/{id}/reset-pin` | 핀 초기화(감사 기록) |

> 집계는 매퍼에서 GROUP BY/SUM으로 단일 쿼리화(N+1 금지). 목록은 XPagination 3규칙 준수.

### 2-6. 신규 응답 DTO (요지)

`P2pMemberDetailResponse` 확장:
- profile{ partnerId, partnerCode, partnerName, partnerUserId, memberToken, status, pinSetAt, createdAt }
- bankAccounts[]{ bankCode, bankName, accountNumber, accountHolder, status, scrapingStatus, consentExpiresAt, credential{ status, pattern, preregistered, lastVerifiedAt, lastErrorCode, lastErrorAt } }
- amounts{ totalWithdrawKrw, matchedKrw, confirmedKrw, remainingKrw, settledUsdt, activeOrderCount, disputeCount }
- (이력은 별도 페이지네이션 엔드포인트)

---

## 2.5 거래 관리 (입금 거래) — 상세 스펙

> 거래 = **입금 주문(p2p_deposit_orders) 1건**(예: 100만원). 그 거래 안의 매칭(50+50)이 **매칭 건별(레그)**.
> 화면은 (목록) 거래(입금 주문) 일람 + (상세) 거래 1건 = 거래정보·거래상태 + 매칭 건별 N개(각 건의 당사자·타임라인·정산·TORQ).

### 2.5-1. 거래 목록 (입금 거래 일람)

각 행 = **입금 거래(주문) 1건**.

| 열 | 출처 |
|----|------|
| 주문코드(거래) | deposit_orders.order_code (→ 거래 상세) |
| 파트너/유저 | partner_id→파트너, partner_user_id |
| 구매자 | buyer_name |
| 거래금액 KRW | krw_amount |
| 매칭/잔여 | (krw_amount − remaining_amount) / remaining_amount |
| 매칭 건수 | count(p2p_matches WHERE deposit_order_id) — 예: 2 (50+50) |
| 라우팅 | 레그 구성 요약 (P2P n / TORQ n / PARTNER n) |
| 상태 | deposit_orders.status (PENDING/MATCHING/MATCHED/CONFIRMED/COMPLETED/CANCELLED/EXPIRED) |
| 분쟁 | 하위 매칭에 DISPUTED 있으면 뱃지 |
| 생성/만료 | created_at / expires_at |

**필터**: 상태, 파트너, 레그유형 포함, 분쟁 포함만, 날짜, 주문코드/구매자 키워드.
**탭**: 전체 / 진행중 / 분쟁 포함 / 실패·만료 / 완료.

### 2.5-2. 거래 상세 — A. 거래 정보 + 거래 상태 (입금 주문 레벨)

| 표시 | 출처 (p2p_deposit_orders) |
|------|------|
| 주문코드 | order_code |
| 파트너/유저 | partner_id→파트너코드/명, partner_user_id |
| 구매자 스냅샷 | buyer_name, buyer_bank_code, buyer_account_number, buyer_phone, kyc_uid |
| 거래금액 / 잔여 | krw_amount / remaining_amount |
| 수수료 | fee_rate / fee_amount |
| 상태 | status (PENDING/MATCHING/MATCHED/CONFIRMED/COMPLETED/CANCELLED/EXPIRED) |
| 결제링크 | payment_link_code (있으면) |
| 시각 | created_at / expires_at / completed_at / cancelled_at, cancel_reason |
| **매칭 요약** | 총 N건, 합계 KRW/USDT, 정산완료 건수, 분쟁/실패 건수 |

### 2.5-3. 거래 상세 — B. 매칭 건별 (50+50) — 핵심

이 거래(입금 주문)에 속한 매칭들(p2p_matches WHERE deposit_order_id=order.id)을 **건별로 나열**.
각 건(레그)은 펼치면 아래 4개 상황(당사자/타임라인/정산/TORQ)을 보여준다.

**건별 요약 행**

| 열 | 출처 (p2p_matches) |
|----|------|
| 매칭코드 | match_code |
| 레그유형 | leg_type (P2P/TORQ/PARTNER) |
| 금액 | krw_amount / usdt_amount (예: 50만 / …) |
| 환율/보너스 | exchange_rate / withdraw_bonus_rate |
| 출금측(공급) | withdraw_order_id→파트너/유저 (TORQ면 LP) |
| 상태 | status |
| 핵심시각 | bank_confirmed_at / settled_at / disputed_at |

#### B-1. 매칭 건별 — 양측 당사자 정보

| 구분 | 표시 | 출처 |
|------|------|------|
| 입금자(구매자) | 파트너/유저ID, 구매자명, 은행/계좌, 전화 | 상위 deposit_order: buyer_name/bank/account/phone |
| 출금자(공급) | 파트너/유저ID, 수취 은행계좌/예금주 | withdraw_order: partner, partner_user_id, bank_account_id→은행/계좌/예금주 |
| (TORQ 건) | LP 판매자/계좌 | torq_trades: seller_name, seller_bank, seller_account |
| 실제 입금자명 | confirmed_depositor_name (CODEF 스크래핑) |
| 이체 참조 | bank_transfer_ref |

#### B-2. 매칭 건별 — 상태 타임라인 (건별 상황)

```
생성(created_at)
  → 이체 대기(BANK_PENDING / AWAITING_TRANSFER, expires_at)
  → 은행 확인(bank_confirmed_at, confirmed_depositor_name)
  → [분쟁] disputed_at / dispute_reason / dispute_submitted_by / dispute_source / dispute_evidence_url
  → [판정] resolved_at / resolution(CONFIRM·CANCEL) / resolved_by(admin) / resolve_memo
  → 정산(settled_at, settlement_tx_hash)  |  실패(FAILED)  |  취소(CANCELLED)
```
각 단계 발생/미발생 + 시각 + 행위자. (p2p_matches 컬럼으로 전부 표현 가능.)

#### B-3. 매칭 건별 — 정산 · 자금 추적

| 표시 | 출처 |
|------|------|
| 정산 레코드 | p2p_settlements WHERE match_id → settlement_code, type(ONCHAIN/INNER), status, tx_hash, network |
| 정산 시각/실패 | completed_at / failed_at / failure_reason / retry_count |
| 매칭 정산 해시 | p2p_matches.settlement_tx_hash, settled_at |
| 원장 크레딧 | ⚠️ 아래 참조 이원화 주의 — `reference_id=match.id` **단독 조인 금지** |
| 입금 레코드 | deposits WHERE order_code=deposit_order.order_code (TORQ 레그) |

☠️ **원장 조인은 반드시 구/신 두 참조를 모두 흡수해야 한다.**
`reference_type IN ('P2P_SETTLEMENT','P2P_MATCH')` 의 `reference_id` 는 **매칭 ID 또는 정산 ID** 를 가리킨다.
`reference_id = m.id` 로만 조인하면 (a) 구 참조 레그를 **통째로 놓치고**, (b) 두 ID 공간이 충돌하는 구간에서
**남의 원장 행이 딸려 붙는다**(`ref=48` 이 매칭 48 이자 정산 48).

```sql
LEFT JOIN ledger_entries le
       ON le.reference_type IN ('P2P_SETTLEMENT','P2P_MATCH')
      AND ( le.reference_id = m.id
            OR le.reference_id = (SELECT s.id FROM p2p_settlements s WHERE s.match_id = m.id) )
      AND le.description LIKE CONCAT('%', m.match_code, '%')   -- ★ 충돌 방지 판별자
```
`description` 의 `pm_` 매칭코드가 **유일한 확실한 판별자**다(ID 는 아니다).
단, `P2P_WITHDRAW_FEE` 와 DEBIT 보정 행은 `pm_` 코드가 없으므로 이 필터에서 빠진다 — 수수료·보정 표시가
필요하면 별도 조회할 것.

기존 `AdminSettlementRebateMapper` 의 `CASE` 호환 계층이 같은 문제를 흡수하고 있다. **지우지 마라.**
근거: `P2P_SETTLEMENT_RESTRUCTURE.md` §2.5 · 지식 repo `domains/p2p/fee-structure.md` · decision-log 2026-08-17.
잔여 구 참조 4행 이관이 끝나면 이 호환 계층을 걷어낼 수 있다.

→ 건별로 "돈이 어디까지 흘렀나"(매칭→정산→원장크레딧→온체인 tx) 추적.

#### B-4. 매칭 건별 — TORQ 연동 상태 (leg_type=TORQ)

| 표시 | 출처 (torq_trades, via match.torq_escrow_id) |
|------|------|
| escrow_id / trade_id | torq_escrow_id → torq_trades.id |
| TORQ 상태 | status (CREATED/…/COMPLETED/CANCELLED/IN_DISPUTE/FORCE_RELEASED) |
| 판정 | resolution (RELEASE_TO_BUYER / REFUND_TO_BUYER) |
| 시각 | accepted_at / transferred_at / completed_at / cancelled_at / disputed_at |
| 온체인 | tx_hash, from_address(LP 지갑) |
| 취소사유 | cancel_reason |

> ⚠️ **지각 완료 불일치 경보**: torq_trades.status=COMPLETED인데 매칭이 FAILED/EXPIRED면 해당 건에 경보 + "사후 정산(reconcile)" 액션. (2026-06-30 trade #159 사례 — TORQ_LATE_COMPLETION_RECONCILE_GUIDE.md)

### 2.5-4. 거래 상세 — 관리자 액션 (건별, 감사 필수)

| 액션 | 엔드포인트 | 조건 | 자금효과 |
|------|-----------|------|----------|
| 분쟁 강제 확인 | POST /matches/{matchId}/resolve {CONFIRM} | 건 DISPUTED | 구매자 정산(creditTorqLeg/confirmBankTransfer) |
| 분쟁 강제 취소 | POST /matches/{matchId}/resolve {CANCEL} | 건 DISPUTED | 레그 CANCELLED + 잠금해제/환불 |
| (신규) 사후 정산 | POST /matches/{matchId}/reconcile | 건 FAILED + TORQ COMPLETED | 지각완료 보정 정산 |
| 입금 거래 취소 | (신규) POST /deposit-orders/{id}/cancel | 거래 활성 | 미매칭 잔여 종결 |

모든 액션: 사유 입력 + 확인 모달 + admin_audit_logs 기록.

### 2.5-5. 신규/확장 엔드포인트 (거래 관리)

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/admin/p2p/deposit-orders` | **거래 목록** — 입금 주문 일람 + 매칭 건수/라우팅/분쟁 요약 (기존 orders/deposit 확장) |
| GET | `/api/admin/p2p/deposit-orders/{id}` | **거래 상세** — 거래정보·상태 + 매칭 건별 N개(B-1~B-4 통합). DTO화(현재 raw 엔티티) |
| GET | `/api/admin/p2p/matches/{matchId}` | 매칭 건 단건 상세(딥링크용, DTO화) |
| POST | `/api/admin/p2p/matches/{matchId}/resolve` | (기존, DTO화) 분쟁 판정 |
| POST | `/api/admin/p2p/matches/{matchId}/reconcile` | (신규) 지각 TORQ 완료 사후 정산 |
| POST | `/api/admin/p2p/deposit-orders/{id}/cancel` | (신규) 입금 거래 취소 |

> 거래 상세 응답 = `P2pTransactionDetailResponse`{ order(거래정보·상태) + legs[]{ match + 당사자 + 타임라인 + 정산 + torq } }.
> legs는 deposit_order_id로 묶인 매칭 전부. 단일 쿼리 조인(N+1 금지).

---

## 2.6 출금 주문 관리 — 상세 스펙

> 출금 주문 = P2P **공급측**. 출금자(member)가 "USDT를 KRW로 받겠다"고 등록 → USDT는 원본 withdrawal에
> 동결(freeze)되고, 수취 은행계좌로 구매자들의 KRW를 받는다. 입금 거래(§2.5)와 **방향이 반대**이고
> 성격도 다르다(아래).

### 2.6-1. 입금 거래와의 핵심 차이

| 구분 | 입금 거래(§2.5) | 출금 주문(§2.6) |
|------|------|------|
| 역할 | 수요(구매자, KRW 지불→USDT 수령) | 공급(출금자, USDT 동결→KRW 수령) |
| 만료 | 있음(expires_at, 만료잡) | **없음** — 회원/파트너/관리자 직접 종료만 (2026-06-11 정책) |
| 매칭 | 1건이 여러 레그로 분할 | **여러 입금에 부분 매칭 누적**(matched_amount 증가) |
| 금액 추적 | remaining_amount | matched_amount / confirmed_amount / 잔여=krw−confirmed |
| 종료 잔여 | 만료/취소 | **잔여 3분기**: 취소 / 강제정산 / USDT 전환 |
| 자금 동결 | — | 원본 withdrawal에 USDT freeze(잔여 처리 시 해제/복원) |

### 2.6-2. 출금 주문 라이프사이클

```
등록(PENDING, USDT freeze)
  → 부분 매칭(PARTIALLY_MATCHED, matched_amount↑)  ──반복──┐
  → 전액 매칭(FULLY_MATCHED)                              │
  → 입금확인 누적(confirmed_amount↑) → SETTLING           │
  → 전액 confirmed → COMPLETED                            │
분기(잔여 처리, 만료 없음):                                │
  · 취소(cancelRemaining/cancelOrder) → 잔여 freeze 해제, refund_type=CANCEL
  · 강제 정산(forceSettle) → 파트너가 KRW 선지급, 잔여 USDT 복원, FORCE_SETTLE
  · USDT 전환(convertToDirectWithdrawal) → 잔여를 신규 USDT 출금으로, USDT_WITHDRAW
USDT 전환 요청 플로우: requestUsdtConvert(REQUESTED) → 파트너 승인(approve)/거부(reject)
```

### 2.6-3. 목록 (출금 주문 일람)

| 열 | 출처 (p2p_withdraw_orders) |
|----|------|
| 주문코드 | order_code (→ 상세) |
| 출금자 | partner_id→파트너, partner_user_id (→회원 상세 딥링크) |
| 네트워크/통화 | network_id / request_currency(KRW·USDT) |
| 출금금액 | krw_amount / usdt_amount |
| 매칭/확정/잔여 | matched_amount / confirmed_amount / (krw−confirmed) |
| 상태 | status (PENDING/PARTIALLY_MATCHED/FULLY_MATCHED/SETTLING/COMPLETED/CANCELLED/FAILED) |
| USDT전환 | usdt_convert_status (REQUESTED 뱃지 — 승인 대기 강조) |
| 수취계좌 | bank_account_id→은행/계좌 |
| 등록일 | created_at |

**필터**: 상태, 파트너, 네트워크, USDT전환 요청만, 잔여>0만, 날짜, 주문코드 키워드.
**탭**: 진행중(PENDING/PARTIALLY/FULLY/SETTLING) / 전환요청 / 완료 / 취소·실패.

### 2.6-4. 상세 — A. 출금 주문 정보 + 상태

| 표시 | 출처 |
|------|------|
| 주문코드 / 상태 | order_code / status |
| 출금자 | partner, partner_user_id (→회원 상세) |
| 네트워크 / 통화 | network_id / request_currency |
| 출금 KRW / USDT / 환율 | krw_amount / usdt_amount / exchange_rate |
| 매칭 / 확정 / 잔여 | matched_amount / confirmed_amount / 잔여(krw−confirmed) |
| 수취 은행계좌 | bank_account_id → 은행/계좌/예금주 |
| 원본 출금 | withdrawal_id → withdrawals(코드/상태/동결) — "P2P 전환 출금" 여부 |
| 시각 | matched_at / settled_at / completed_at / cancelled_at, cancel_reason |

### 2.6-5. 상세 — B. 매칭 건별 (역방향: 이 출금이 받은 입금들)

p2p_matches WHERE withdraw_order_id=order.id — 이 출금 주문에 매칭된 입금(구매자)들.

| 열 | 출처 |
|----|------|
| 매칭코드 | match_code (→ 거래 상세 §2.5 딥링크) |
| 입금측(구매자) | deposit_order_id → 파트너/유저/구매자명 |
| KRW / USDT | krw_amount / usdt_amount |
| 상태 | status (BANK_PENDING/…/SETTLED/FAILED/DISPUTED) |
| 은행확인/정산 | bank_confirmed_at / settled_at / settlement_tx_hash |

> 예: 출금 #30(611만원)이 입금 3건(18만+1만+5만 SETTLED)과 1건(552만 FAILED)에 매칭 — 이 표로 한눈에.

### 2.6-6. 상세 — C. 잔여 처리 + USDT 전환 (관리자 액션)

현재 어드민은 **취소만** 가능(POST /orders/withdraw/{id}/cancel). 나머지는 partner-api/widget에만 있음 → 어드민에 이관 필요.

| 액션 | 서비스 메서드 | 효과 | 신규 어드민 EP |
|------|--------------|------|----------------|
| 취소(잔여 환불) | cancelOrder/cancelRemaining | 잔여 freeze 해제, refund=CANCEL, COMPLETED | (기존) /cancel |
| 강제 정산 | forceSettle | 파트너 KRW 선지급 기록, 잔여 USDT 복원, FORCE_SETTLE | (신규) /force-settle |
| USDT 전환 | convertToDirectWithdrawal | 잔여→신규 USDT 출금, USDT_WITHDRAW | (신규) /convert |
| 전환요청 승인 | approveUsdtConvert | REQUESTED→승인, 신규 출금 생성 | (신규) /convert/approve |
| 전환요청 거부 | rejectUsdtConvert | 필드 초기화, 매칭 대기 복원 | (신규) /convert/reject |

표시 필드: usdt_convert_address / usdt_convert_requested_at / usdt_convert_status, refund_type / refunded_amount / refund_memo / refunded_at.
모든 액션: 사유 입력 + 확인 모달 + admin_audit_logs(자금 영향).

### 2.6-7. 신규/확장 엔드포인트 (출금 주문)

| 메서드 | 경로 | 설명 |
|--------|------|------|
| GET | `/api/admin/p2p/withdraw-orders` | 목록(기존 orders/withdraw 확장 — 회원/전환/잔여 요약) |
| GET | `/api/admin/p2p/withdraw-orders/{id}` | 상세(DTO화 — 현재 raw 엔티티) + 매칭 건별(역방향) |
| POST | `/api/admin/p2p/withdraw-orders/{id}/cancel` | (기존) 취소 |
| POST | `/api/admin/p2p/withdraw-orders/{id}/force-settle` | (신규) 강제 정산 |
| POST | `/api/admin/p2p/withdraw-orders/{id}/convert` | (신규) USDT 직접 전환 |
| POST | `/api/admin/p2p/withdraw-orders/{id}/convert/approve` | (신규) 전환요청 승인 |
| POST | `/api/admin/p2p/withdraw-orders/{id}/convert/reject` | (신규) 전환요청 거부 |

### 2.6-8. 출금 주문 어드민 갭 (현재)

1. 상세가 **raw 엔티티** 반환 → DTO화.
2. **잔여 처리(강제정산/USDT전환)·전환 승인/거부가 어드민에 없음** — partner-api/widget에만. 운영자가 직접 못 함.
3. 매칭 건별(역방향) 뷰 없음 — 이 출금이 어느 입금들에 매칭됐는지 못 봄.
4. 원본 withdrawal(동결 USDT) 연결 가시화 없음.
5. USDT 전환 요청(REQUESTED) 대기 큐/알림 없음.
6. 취소/처리 액션 감사 미기록.

### 2.6-9. 출금 풀 (가용 유동성) 뷰 — 신규

> 전체 출금 주문 목록(§2.6-3)과 별개로, **"지금 P2P로 매칭 가능한 유동성이 얼마나 떠 있나"**를 보는 집계 뷰.
> 매칭기가 실제로 보는 풀과 동일 기준으로 보여줘야 운영 판단에 쓸 수 있다(유동성 마르면 입금이 TORQ로 폴백 → 비용↑).

**풀에 포함되는 주문(매칭기 기준)**:
- status IN (PENDING, PARTIALLY_MATCHED) AND 가용 KRW = (krw_amount − matched_amount) > 0
- 가용 ≥ `p2p.min_amount`(더스트 가드, 2026-06-30 배포) — 미만은 풀에서 제외
- 점검 임박 계좌(maintenanceGuard) 제외
- ⚠️ **실질 한도는 USDT 잠금 예산**: 주문 KRW가 아무리 많아도 파트너 MASTER의 가용 USDT(`getAvailableForP2p`, p2p_partner_locks)가 부족하면 매칭 불가 → 풀은 KRW(주문)과 USDT(락 예산) 두 층을 같이 본다.

**상단 집계 카드**

| 지표 | 산식/출처 |
|------|-----------|
| 총 가용 KRW(주문 기준) | Σ (krw_amount − matched_amount), 매칭가능 주문 |
| 총 가용 USDT(락 예산 기준) | Σ partner getAvailableForP2p (네트워크별) |
| 활성 출금 주문 수 | count(매칭가능) |
| 잠긴 USDT(locked) | p2p_partner_locks 합 |
| 더스트/제외 건수 | 가용<min, 점검계좌 제외, USDT부족 제외 |

**분해**
- **네트워크별**(TRON 등): 가용 KRW / 가용 USDT.
- **파트너별**: 가용 KRW(주문) vs 가용 USDT(락 예산) 나란히 — **어긋나면 경고**(주문은 많은데 USDT 부족 = 매칭 안 됨). 잠긴 USDT 동시 표기.

**풀 구성 목록** — 매칭가능 주문을 **available 큰 순**(매칭기 findMatchableWithdrawOrdersForUpdate 정렬과 동일)으로: 파트너/회원, 가용 KRW, 환율, 추정 USDT, 등록 경과일. → "다음 입금이 어떤 순서로 어디에 매칭될지"를 그대로 미리 봄.

**건강도 지표**: 가용 0(유동성 고갈 → TORQ 의존) 경보, 오래된 주문(N일+ PENDING), 점검계좌로 빠진 유동성, USDT 부족으로 못 쓰는 주문.

**엔드포인트(신규)**: `GET /api/admin/p2p/withdraw-pool` { summary, byNetwork[], byPartner[], orders[] }.
**연계**: 대시보드(§3-5)의 "총 가용/잠금 유동성" 카드는 이 풀 집계를 재사용.

---

## 3. 영역별 재설계 + 갭

### 3-1. 출금/입금 주문
- **DTO 표준화**: getWithdrawOrderDetail/getDepositOrderDetail이 raw 엔티티 반환 → `*DetailResponse` DTO 신설.
- **회원 연결**: 주문 행/상세에 회원(member) 링크 추가(현재 partner_user_id 문자열만).
- **응답 대칭화**: 입금/출금 주문 DTO 필드 정합(공통 베이스 + 각자 확장).
- **상세 보강**: 주문 상세에 매칭 레그/정산/은행계좌/타임라인 인라인.

### 3-2. 거래 관리 / 분쟁  → 상세는 §2.5
- 거래 관리(매칭 거래)의 본 스펙은 **§2.5** 참조. 여기선 보강 포인트만:
- **분쟁 큐**: disputeOnly를 §2.5-1 탭으로 흡수(판정 대기 우선).
- **비즈니스 룰 문서화**: 언제 admin 개입 가능(상태 조건), CONFIRM/CANCEL의 자금 효과(크레딧/환불), 증거(dispute_evidence_url) 요구.
- **TORQ 지각완료 경보 + reconcile 액션**(§2.5-6/7).
- **resolve/상세 응답 DTO화**(현재 raw P2pMatch).

### 3-3. 정산
- **실패 리컨실 UI**: failure_reason + 마지막 tx 오류 노출, 재시도/취소 외 "수동 완료" 보정 경로(권한+감사).
- **부분 정산/보상**: 한쪽 레그 실패 시 처리 정책 명문화.
- **온체인/내부(INNER) 구분** 명확 표기.

### 3-4. 수수료 · 정책 설정
- **파트너 오버라이드 가시화**: 글로벌 + 파트너별 effective 요율 조회/수정(현재 글로벌만).
- **매칭 정책 노출**: 신규 `p2p.min_amount`(더스트 방지), `p2p.max_p2p_legs`, 폴백 enable 플래그 등 system_settings 기반 파라미터를 어드민에서 조회/수정.

### 3-5. 대시보드
- KPI에 추세(7d/30d), 정산 성공률, 분쟁율, 수수료 수익, 회원 증가, 파트너별/네트워크별 잠금잔액 분해 추가.

---

## 4. 공통 / 횡단 이슈

1. **raw 엔티티 → DTO 전면 표준화** (모든 상세/액션 응답).
2. **admin_audit_logs 연동** — 모든 상태변경 액션(정지/활성/취소/분쟁판정/정산보정/수수료변경) 기록. AuditAction enum 활용(SPRING_AUDIT_LOG_REFACTOR_GUIDE.md 참조).
3. **페이지네이션/필터/성능** — 집계는 단일 쿼리, 목록은 XPagination 3규칙. 회원/주문/매칭 대량 조회 인덱스 점검(partner_id, partner_user_id, status, created_at).
4. **딥링크 일관성** — 회원↔주문↔매칭↔정산 상호 이동.
5. **권한/이중확인** — 자금 영향 액션(분쟁판정, 정산보정, 주문취소)은 사유 입력 + 확인 모달 + 감사.

---

## 5. 우선순위 로드맵

| Phase | 범위 | 산출 |
|-------|------|------|
| **P0 (회원 중심)** | §2 회원 상세 4개 섹션 + 신규 엔드포인트 + DTO + admin_audit_logs 연동 | 백엔드 엔드포인트 6종 + admin-ui 회원 상세 화면 |
| **P1 (일관성)** | §3-1 주문 DTO 표준화 + 회원연결, §3-2 매칭 resolve DTO화 + 분쟁 큐 | raw 엔티티 제거 |
| **P2 (운영심화)** | §3-3 정산 리컨실 + §3-4 수수료/정책(파트너 오버라이드, min_amount 노출) | |
| **P3 (가시화)** | §3-5 대시보드 추세/지표 | |

---

## 6. 데이터 모델 연결 (회원 기준)

```
p2p_members (회원)
  ├─ partner_id ───────────────→ partners (파트너)
  ├─ (partner_id, partner_user_id) ─→ p2p_withdraw_orders (출금 주문)
  │                                     ├─ bank_account_id → bank_accounts (입금받을 계좌)
  │                                     └─ id ← p2p_matches.withdraw_order_id (매칭 레그)
  │                                              └─ id ← p2p_settlements.match_id (정산)
  └─ id ← bank_accounts.owner_id (owner_type=MEMBER, 다계좌)
            └─ scraping_credential_id → credentials (인증/빠른조회 자격증명)
```

핵심 연결 키:
- 회원 ↔ 주문: `partner_id + partner_user_id` (주문에 member_id FK 없음 — 문자열 매칭. 향후 member_id FK 추가 검토).
- 회원 ↔ 은행/인증: `bank_accounts.owner_type=MEMBER, owner_id=member.id` → `scraping_credential_id` → `credentials`.
- 주문 ↔ 매칭 ↔ 정산: `withdraw_order_id` → `match_id`.

> 개선 제안: `p2p_withdraw_orders`에 `member_id` FK가 없어 회원-주문 조인이 문자열(partner_user_id) 기반이다.
> 대량 조회 성능/정합을 위해 member_id 컬럼 추가(또는 (partner_id, partner_user_id) 복합 인덱스 보장)를 P1에서 검토.

---

## 부록: 현재 어드민 P2P 엔드포인트 인벤토리 (점검 기준선)

| 컨트롤러 | 경로 | 비고 |
|----------|------|------|
| P2pOrderManagementController | /api/admin/p2p/orders/{withdraw,deposit}[/{id}][/cancel] | 상세=raw 엔티티 ⚠️ |
| P2pMatchManagementController | /api/admin/p2p/matches[/{id}][/resolve] | resolve=raw 엔티티 ⚠️ |
| P2pMemberManagementController | /api/admin/p2p/members[/{id}][/suspend,/activate] | 상세 얕음(대표계좌1·부분집계) |
| P2pSettlementManagementController | /api/admin/p2p/settlements[/{id}][/retry,/cancel] | 실패 리컨실 미흡 |
| P2pFeeSettingsController | /api/admin/p2p/fee-settings | 글로벌만 |
| P2pDashboardController | /api/admin/p2p/dashboard/summary | 상위 KPI만 |
