# P2P 출금회원 설정 화면 — 백엔드 기능 설계 지침서

> 작성: 2026-07-10 (Cowork) · 상태: 설계 확정, 구현 대기
> 대상: IntelliJ(Spring Boot open-api/core/common), `cryptoments-admin/p2p-ui`(Vue)
> 관련: `P2P_MEMBER_TELEGRAM_GUIDE.md`, `P2P_TORQ_UNIFIED_MATCHING_DESIGN.md`, `P2P_MATCHING_ARCHITECTURE.md`

## 0. 목표

`p2p-ui` 회원 페이지에 **설정 화면**을 신설하고, 화면의 5개 항목을 회원이 명시적으로
제어할 수 있게 백엔드 기능을 먼저 만든다.

| # | 설정 항목 | 현황 | 이번 설계 | Phase |
|---|-----------|------|-----------|-------|
| 1 | 계좌 관리(등록/수정/삭제) | 등록·삭제·재인증 O, **수정 미노출** | PUT 수정 엔드포인트 노출 | A |
| 2 | 거래 상태(중지/시작) | **미구현** (매칭 게이트 없음) | `trading_paused` + 매칭쿼리 게이트 | A |
| 3 | 스크래핑 등록(CODEF 빠른조회) | 등록·재인증 O | 상태 노출 + 기존 경로 재사용 | A |
| 4 | 입금방식(자동/수동) | link enum만, **회원설정·수동확인 미구현** | 4A 설정저장 / 4B 수동확인 게이트+텔레그램 버튼 | A+B |
| 5 | 텔레그램 연동 | DDL만, 코드 미구현 | 별도 지침서로 설계 완료 | A |

**확정 사항(2026-07-10)**
- 입금방식 **수동 = 수동 입금확인 모드**(부재중 등 특별사유용). 매칭은 자동, 자동확인(CODEF) 대신 회원이 입금을 직접 확인. 텔레그램 "입금 확인 요청" 인라인 버튼으로 처리(§6, 텔레그램 §7B).
- 거래중지 **= 신규 매칭만 차단**(진행 중 매칭·정산은 계속, 로그인·조회 정상). 관리자 `SUSPENDED`(로그인 차단)와 별개.

## 1. DDL 변경 요약

### 1-1. `p2p_members` — 회원 설정 컬럼

```sql
ALTER TABLE p2p_members
    ADD COLUMN trading_paused  TINYINT(1)  NOT NULL DEFAULT 0
        COMMENT '회원 본인 거래중지(신규 매칭 차단). 0=영업, 1=중지' AFTER status,
    ADD COLUMN deposit_method  VARCHAR(10) NOT NULL DEFAULT 'AUTO'
        COMMENT '입금방식 기본값 AUTO/MANUAL(건별승인)' AFTER trading_paused;
-- telegram_chat_id / telegram_notify_enabled / telegram_connected_at 은
-- P2P_MEMBER_TELEGRAM_GUIDE.md 에서 이미 추가 (canonical DDL 반영 완료)
```

- `trading_paused`: 관리자용 `status`(ACTIVE/SUSPENDED)와 **분리**. status=로그인 게이트, trading_paused=매칭 게이트.
- `deposit_method`: 회원 레벨 **기본** 입금방식. (링크 단위 `p2p_deposit_links.match_mode`는 별개로 유지 — §4 참조)

### 1-2. `p2p_matches` — 추가 컬럼 없음

정정(2026-07-10): 수동(MANUAL)은 **매칭 전 승인이 아니라 입금 확인만 수동**이므로,
매칭 상태는 기존 `BANK_PENDING`(입금 확인 대기)을 그대로 재사용한다. → `PENDING_APPROVAL`
신규 상태·승인 컬럼 **불필요**. 리마인더는 "MANUAL 회원의 BANK_PENDING 매칭 중 N분 경과"를
조회해 처리(§6). (상세는 `P2P_MEMBER_TELEGRAM_GUIDE.md` §7B)

## 2. 통합 Settings API

신규 컨트롤러 `P2pMemberSettingsController`(base `/p2p/page/settings`, 세션 `P2pPageSessionData`).
계좌/텔레그램 등 기존 기능은 기존 `P2pWithdrawPageController`에 그대로 두고, 설정 화면은
아래 **집계 조회 1개 + 항목별 변경 엔드포인트**로 구성한다.

| 메서드 | 경로 | 항목 | 비고 |
|--------|------|------|------|
| GET | `/p2p/page/settings` | 전체 | 계좌[]·거래상태·입금방식·텔레그램·승인대기수 집계 |
| PUT | `/p2p/page/settings/trading-status` | 2 | `{ "status": "ACTIVE"\|"PAUSED" }` |
| PUT | `/p2p/page/settings/deposit-method` | 4A | `{ "method": "AUTO"\|"MANUAL" }` |
| PUT | `/p2p/page/account` | 1 | 계좌 수정(예금주 등) — 신규 |
| GET | `/p2p/page/settings/pending-approvals` | 4B | 승인 대기 매칭 목록 |
| POST | `/p2p/page/matches/{matchId}/approve` | 4B | 건별 승인 |
| POST | `/p2p/page/matches/{matchId}/reject` | 4B | 건별 거절 |

기존 재사용: `POST /p2p/page/account`(등록/스크래핑), `DELETE /p2p/page/account`, `POST /p2p/page/account/re-auth`,
텔레그램 4종(`/p2p/page/telegram*` — 텔레그램 지침서).

`GET /p2p/page/settings` 응답 예:
```json
{
  "tradingStatus": "ACTIVE",
  "depositMethod": "MANUAL",
  "accounts": [
    { "id": 12, "bankCode": "048", "accountNumber": "...", "accountHolder": "홍길동",
      "scrapingStatus": "ACTIVE", "consentExpiresAt": "2027-06-26T00:00:00",
      "reauthRequired": false }
  ],
  "telegram": { "connected": true, "notifyEnabled": true },
  "pendingApprovalCount": 2
}
```
> 신규 서비스 `P2pMemberSettingsService`(core/p2p)가 위 조회/변경을 담당. 회원 조회는
> `P2pMemberService.getByToken`/세션 memberId 로 소유권 한정.

## 3. §1 계좌 관리 (등록/수정/삭제)

현행 유지 + **수정 노출**만 추가.

- 등록: `POST /p2p/page/account` (credentialJson 있으면 스크래핑 동시 등록). 다중 계좌 허용.
- 삭제: `DELETE /p2p/page/account?accountId` (진행 중 출금 있으면 `BANK_ACCOUNT_IN_USE`).
- **수정(신규)**: `PUT /p2p/page/account` — `BankAccountService.update`(L117, 이미 존재)를 노출.
  - 수정 허용 필드: **예금주명(account_holder)** 등 비식별 필드.
  - 은행코드/계좌번호 변경은 사실상 다른 계좌(UNIQUE `bank_code+account_number` + 스크래핑 자격증명 바인딩) → **삭제 후 재등록**으로 안내(수정 대상 아님).
  - 소유권 검증: `verifyAccountOwnership`(기존 패턴) 재사용.
- 화면 노출: 계좌별 `scraping_status`, `scraping_consent_expires_at`을 §2 settings 응답에 포함해 "인증 유효/만료 임박" 배지 표시.

## 4. §2 거래 상태 (중지/시작)

### 4-1. 저장 + 토글
- `p2p_members.trading_paused` (0/1). 엔티티 `P2pMember`에 `Boolean tradingPaused` 추가.
- `PUT /p2p/page/settings/trading-status {status}` → PAUSED=1 / ACTIVE=0. `P2pMemberService.setTradingPaused(memberId, paused)`.

### 4-2. 매칭 게이트 (핵심)
매칭 후보 쿼리 3곳(`P2pMatchingMapper`)에 회원 `trading_paused=0` 조건을 추가한다.
출금 주문에는 회원 식별키(partner_id, partner_user_id)가 있으므로 `p2p_members`를 JOIN.

```sql
-- findMatchableWithdrawOrdersForUpdate / findAvailableWithdrawOrders / findExactMatchesForUpdate 공통
SELECT wo.* FROM p2p_withdraw_orders wo
JOIN p2p_members m
  ON m.partner_id = wo.partner_id AND m.partner_user_id = wo.partner_user_id
WHERE wo.status IN ('PENDING','PARTIALLY_MATCHED')
  AND (wo.krw_amount - wo.matched_amount) > 0        -- ※ <script> 내부는 &gt; 로
  AND wo.bank_account_id IS NOT NULL
  AND m.trading_paused = 0                            -- ← 신규 게이트
ORDER BY ...
```
> ⚠️ `<script>` 매퍼 규칙: `>`는 반드시 `&gt;`, `<`/`<=`는 `&lt;`/CDATA, 같지 않음은 `!=`.
> (2026-06-11 SAXParseException 전서비스 다운 재발 방지 — 매퍼 상단 주석에 이미 명시)
>
> `uk_partner_user (partner_id, partner_user_id)` 인덱스가 있어 JOIN 비용은 무시할 만함.
> 출금 주문은 항상 회원(getOrCreate) 기준으로 생성되므로 JOIN 누락 위험 없음.

### 4-3. 의미 정리 (진행 건 불변)
- **PENDING**: 후보에서 제외 → 신규 매칭 없음(그대로 대기, 자동 취소/만료 없음 — 2026-06-11 정책 유지).
- **PARTIALLY_MATCHED**: 기매칭분은 계속 진행, 잔여분만 신규 매칭 중단.
- **FULLY_MATCHED / SETTLING**: 영향 없음(정산까지 완료).
- 재개(ACTIVE) 시 `trading_paused=0` → 다음 매칭 사이클부터 자동으로 후보 복귀(데이터 마이그레이션 불필요).
- 신규 출금 주문 생성/전환은 **막지 않는다**(확정 사항: "신규 매칭만 차단").

## 5. §3 계좌 스크래핑 등록 (CODEF 빠른조회)

신규 로직 불필요 — 기존 경로 재사용, 화면에 상태만 노출.
- 등록: `POST /p2p/page/account` (credentialJson 포함) → `P2pScrapingService.registerOrReauthAccount`.
- 재인증: `POST /p2p/page/account/re-auth`.
- 설정 화면: 계좌별 `scraping_status`(CREATED/ACTIVE/EXPIRED/REVOKED), `consent_expires_at`로
  "빠른조회 인증 필요 / 유효 / 만료임박(≤N일)" 표시. `reauthRequired = status!=ACTIVE || 만료임박`.
- 참고(별개 트랙): 신협(048) `C_FAST_ONLY` 패턴 실호출 처리·A_TYPE0 외 패턴 분기는 미완
  (메모리 `shinhyup-c-fast-only`). 본 설정화면 범위 밖 — 상태 노출만 담당.

## 6. §4 입금방식 (자동 AUTO / 수동 MANUAL = 수동 입금확인)

**정정(2026-07-10)**: MANUAL은 매칭 전 건별 승인이 **아니다**. 회원이 **부재중 등 특별 사유로
자동확인(CODEF 스크래핑) 대신 입금을 직접 확인**하는 회원 레벨 모드다.
매칭은 그대로 자동 진행되고, **`BANK_PENDING → BANK_CONFIRMED`(입금 확인)만 회원 수동**으로 한다.
기존 건별 확인 경로(`confirmBankTransfer`, `POST /p2p/page/orders/{code}/confirm-deposit/{matchId}`)를 재사용한다.

### Phase 4A — 설정 저장 (화면과 함께 출시, 저위험)
- `p2p_members.deposit_method` (기본 AUTO). 엔티티 필드 + `PUT /p2p/page/settings/deposit-method`.
- 이 단계만으로는 엔진 동작 변화 없음(전원 자동확인). 화면 토글 + 저장.

### Phase 4B — 수동 확인 게이트 + 텔레그램 버튼 (후속)
```
구매자 이체완료(transfer-done) → 매칭 BANK_PENDING
  회원 AUTO   → P2pScrapingVerifyJob(CODEF) 자동 확인 → BANK_CONFIRMED → 정산  (현행)
  회원 MANUAL → ① ScrapingVerifyJob 자동확인/자동분쟁 SKIP (MANUAL 게이트)
              ② 텔레그램 C1 "입금 확인 요청"(인라인 버튼) 발송
              ③ 회원이 통장 확인 후 [✅ 입금 확인] 버튼 탭 → 봇 콜백 → confirmBankTransfer → 정산
                 (또는 위젯 confirm-deposit 로도 동일 확정 가능)
              ④ 미확인 시 C2 리마인더 → C3 에스컬레이션
```
- **매칭·잔여·재라우팅 변경 없음**(구매자 흐름 그대로). 바뀌는 건 "확인 주체" 뿐.
- 상세(게이트 코드, 인라인 버튼, 봇 콜백, 내부 확인 엔드포인트, 보안): **`P2P_MEMBER_TELEGRAM_GUIDE.md` §7B**.
- **타임·분쟁 룰 확정본(판매자 확인 10분·6h 라운드·관리자 중재·미제출 불리): `P2P_MANUAL_CONFIRM_DISPUTE_RULES.md`**.

> 4A(설정 저장)만으로 화면은 완성된다. 4B 미배포 구간에서 MANUAL 선택 시에는 자동확인이 그대로
> 동작(안전) — "수동 확인은 준비 중" 안내만 표기하면 된다.

## 6B. 확장 — CODEF 미지원 은행(인터넷은행) 수동 지원

수동 확인이 생기면 **자동확인(CODEF 빠른조회)이 불가능한 은행도 사용 가능**해진다.
현재 `banks`는 "빠른조회 지원 17개" 카탈로그이고, 계좌 등록은 이 카탈로그에 있는 은행만 허용
(`BankAccountService.create` → 없으면 `BANK_NOT_FOUND`). 자동확인은 `fast_inquiry_pattern`이
있어야만 동작한다. → 인터넷은행 등은 **카탈로그에 `pattern=NULL`로 추가 + 확인은 수동**으로 열면 된다.

### 6B-1. 은행 카탈로그 확장 (데이터, 스키마 무변경)

현재 `banks` = 빠른조회 지원 **18개**(002/003/004/007/011/020/023/031/032/034/035/037/039/045/048/071/081/088).
인터넷은행 3사가 누락 → **`fast_inquiry_pattern = NULL`(자동확인 미지원=수동전용), `active = 1`** 로 seed.

```sql
-- 인터넷은행 (수동확인 전용). vendor_organization/supported_vendors는 NOT NULL → placeholder/빈배열.
INSERT INTO banks (code, name, vendor_organization, fast_inquiry_pattern,
                   consent_max_days, primary_vendor, supported_vendors, active)
VALUES
 ('090','카카오뱅크','0090', NULL, 365, 'MANUAL', JSON_ARRAY(), 1),
 ('089','케이뱅크',  '0089', NULL, 365, 'MANUAL', JSON_ARRAY(), 1),
 ('092','토스뱅크',  '0092', NULL, 365, 'MANUAL', JSON_ARRAY(), 1);

-- 자정 전후 이체/점검 제한 (KST, 자정 wrap 허용 — 기존 데이터 관례). 매칭 가드가 이 창을 회피.
INSERT INTO bank_maintenance_windows (bank_code, day_of_week, start_kst, end_kst, reason, active)
VALUES
 ('090','DAILY','23:55','00:05','정기점검(23:57~00:03)', 1),
 ('089','DAILY','23:40','00:30','자정 전후 이체 제한',   1),
 ('092','DAILY','23:30','00:30','타행이체 제한(연계기관)',1);
```
- 코드(090 카카오/089 케이/092 토스)는 KFTC 표준. **점검시간은 변동 가능 → 운영 반영 전 재확인**(2026-07 웹 확인 기준).
- 스키마 변경 없음(컬럼 이미 nullable). **운영 반영은 데이터 INSERT** (승인 후).
- 수동전용 은행은 스크래핑 호출이 없어 점검창의 "verify 사전차단" 목적은 약하나, **매칭 직후 이체 불가 구간 회피**를 위해 넣어두는 것을 권장.

### 6B-2. 실효 확인 모드 규칙 (핵심)
매칭의 확인 방식은 **회원 토글 + 은행 능력**의 OR로 결정한다.

```
effectiveManual(match) = (member.deposit_method == MANUAL)
                      OR (bank.fast_inquiry_pattern IS NULL)   // 은행이 자동확인 불가
```
- 자동확인 가능 은행 + 회원 AUTO → **자동**(현행).
- 자동확인 불가 은행(인터넷은행 등) → **항상 수동**(회원 토글 무관).
- `P2pScrapingVerifyJob`의 MANUAL 게이트(§텔레그램 §7B-2)를 이 규칙으로 확장:
  자동확인 대상에서 pattern=NULL 계좌를 제외하고, 해당 매칭은 C1(텔레그램 입금확인 요청)으로 보낸다.

### 6B-3. 계좌 등록 UX
- 인터넷은행 선택 시: credentialJson 없이 등록(단순 `createOrGet`). "이 은행은 **수동 확인 전용**입니다" 안내.
- **가드(결정 필요)**: 수동전용 계좌는 확인 채널이 없으면 매칭 후 갇힘 → **텔레그램 연결(또는 위젯 확인)을 필수/강권**.
  권장: 수동전용 계좌 등록 시 텔레그램 연결 안내 + (선택) 미연결 시 매칭 후보 제외.

### 6B-4. 증빙/분쟁 차이 (주의)
- 자동확인 은행: CODEF 스크래핑으로 금액·입금자명 대조 + 거래확인증 증빙 + 이름 불일치 자동분쟁.
- **수동전용 은행: 스크래핑 증빙 없음** → 회원의 수동 확인이 유일 근거. 자동 분쟁(이름 대조) 미적용,
  분쟁 발생 시 **관리자 판정 비중↑**. (운영 리스크 — 한도/노출 정책은 운영 결정)

### 6B-5. banks API 응답 확장
`GET /p2p/page/banks` 항목에 `autoConfirmSupported`(= `fast_inquiry_pattern != null`) 추가 →
프론트가 "자동확인 은행 / 수동확인 전용(인터넷은행)"으로 그룹핑. (UI 핸드오프 반영)

## 7. §5 텔레그램 연동

`P2P_MEMBER_TELEGRAM_GUIDE.md`에서 설계 완료(전용 봇 `@cryptoments_p2p_bot`).
설정 화면 통합만 정리:
- 연결: `GET /p2p/page/telegram/connect-token` → 딥링크 버튼.
- 상태/토글/해제: `GET/PUT /p2p/page/telegram/notify`, `DELETE /p2p/page/telegram`.
- 수동 입금확인 요청/리마인더(C1/C2, 인라인 버튼) 및 봇 콜백 처리 = 텔레그램 지침서 §7B.

## 8. 구현 순서 / 분담

**Phase A (설정 화면 1차 — 계좌수정·거래중지·스크래핑노출·입금방식저장·텔레그램)**
1. common: `P2pMember` 필드(`tradingPaused`, `depositMethod`, telegram 3종) + 매핑
2. common: `P2pMatchingMapper` 3개 쿼리에 `p2p_members` JOIN + `trading_paused=0` 게이트 (⚠️`&gt;` 규칙)
3. core: `P2pMemberService.setTradingPaused / setDepositMethod`, `P2pMemberSettingsService`(집계 조회)
4. open-api: `P2pMemberSettingsController`(GET settings, PUT trading-status, PUT deposit-method) + `PUT /p2p/page/account`(수정)
5. 텔레그램: `P2P_MEMBER_TELEGRAM_GUIDE.md` Phase 구현
6. DDL: `p2p_members` ALTER(§1-1) 운영 반영(승인 후)
7. p2p-ui: 설정 화면(계좌 목록·수정, 거래중지 토글, 스크래핑 상태/재인증, 입금방식 토글, 텔레그램 연결)

**Phase B (입금방식 수동 = 수동 입금확인 게이트 + 텔레그램 버튼)**
1. scheduler: `P2pScrapingVerifyJob`에 MANUAL 게이트(자동확인 SKIP)
2. core: C1 트리거(`submitTransferDone` afterCommit, 회원 MANUAL 시) + 리마인더 대상 조회
3. Spring: `TelegramBotClient` reply_markup 확장 + C1/C2 인라인버튼 발송
4. open-api: 내부 확인 엔드포인트 `POST /internal/p2p/telegram/action`(X-Bot-Secret)
5. Node `telegram-bot-p2p`: `callback_query` 핸들러 → 내부 엔드포인트 호출 → editMessageText
6. env: `P2P_BOT_INTERNAL_SECRET`(Spring+Node 공유)
7. p2p-ui: 입금방식 안내(수동 시 "텔레그램으로 확인" 문구)
   (상세 전부 `P2P_MEMBER_TELEGRAM_GUIDE.md` §7B)

## 9. 오픈 이슈 / 결정 필요

1. **수동 C2 리마인더 주기/횟수** (예: 5분 후 1회 + 이후 10분 간격 최대 3회).
2. **C3 장기 미확인 처리** — 이체기한 경과 시 관리자 알림 / 자동분쟁 중 택.
3. **수동 확인 버튼 보안** — 일회탭+이중확인(현행 결정) vs URL 열어 PIN 재인증. (§7B-6)
4. **거래중지 중 신규 출금주문 표기** — 막지는 않되 화면에 "거래중지 상태로 대기만 함" 안내 문구 필요.
