# 파트너별 원화 기능 토글 구현 지침서 (v1.0, 2026-07-14)

> **서브에이전트 지시서.** DDL은 운영 DB에 **이미 적용 완료** (v2.5 — `CRYPTOMENTS_V2_DDL.sql` 참조).
> 대상 레포: 백엔드 `/Users/dudgh/git/cryptoments`, 프론트 `/Users/dudgh/git/cryptoments-admin`, 위젯 `/Users/dudgh/git/cryptoments/widget-ui`.

## 0. 기능 정의

partners 테이블에 원화 기능 토글 4종 (모두 `BOOLEAN NOT NULL DEFAULT TRUE`, 운영 적용 완료):

| 컬럼 | 의미 |
|---|---|
| `krw_enabled` | **마스터** — false면 아래 3종 전부 무효 |
| `torq_enabled` | TORQ 견적/거래 생성 + 통합 매칭 TORQ 레그 |
| `p2p_matching_enabled` | P2P 입금(구매) 주문 생성 + 매칭 링크 + P2P 레그 |
| `p2p_withdraw_enabled` | P2P 출금 주문 등록/전환 + **매칭 풀 공급** |

**유효값 공식 (전 게이트 공통)**: `유효_X = krw_enabled && X_enabled`

**정책 원칙**
1. **신규 행위만 차단** — 진행중 주문/매칭/거래의 완결·조회·취소·분쟁 처리는 플래그 무관 허용. 스케줄러 잡은 수정하지 않는다.
2. **read 엔드포인트는 게이트 없음** — 목록/상세/이력 조회는 항상 허용.
3. **차단의 주체는 서버 게이트** — UI 숨김/비활성은 보조.
4. 기존 `p2p_enabled` 컬럼은 **deprecated** — 신규 게이트는 신규 플래그만 참조. 레거시 엔드포인트는 유지하되 아래 §4-4 규칙으로 동기화.

**노출 계산 (위젯)**
- 원화 구매 메뉴 = `krw && (p2p_matching || torq)`
- P2P 출금 메뉴/전환 버튼 = `krw && p2p_withdraw`

---

## Phase A — 백엔드 (`/Users/dudgh/git/cryptoments`)

### A-1. common: Partner 엔티티

`common/src/main/java/com/cryptoments/common/entity/Partner.java`
- 기존 `p2pEnabled` 아래에 4개 필드 추가. 기존 스타일 미러링:
```java
/** 원화 기능 마스터 스위치 (v2.5) */
@XColumn("krw_enabled")
@Builder.Default
private Boolean krwEnabled = true;
// torqEnabled, p2pMatchingEnabled, p2pWithdrawEnabled 동일 패턴
```
- 같은 클래스에 유효값 헬퍼 추가 (null-safe, 기본 true):
```java
public boolean isKrwActive() { return !Boolean.FALSE.equals(krwEnabled); }
public boolean isTorqAllowed() { return isKrwActive() && !Boolean.FALSE.equals(torqEnabled); }
public boolean isP2pMatchingAllowed() { return isKrwActive() && !Boolean.FALSE.equals(p2pMatchingEnabled); }
public boolean isP2pWithdrawAllowed() { return isKrwActive() && !Boolean.FALSE.equals(p2pWithdrawEnabled); }
```
- `p2pEnabled`에 `@Deprecated` + Javadoc "(v2.5) p2pMatchingEnabled/p2pWithdrawEnabled로 분리".

### A-2. common: 에러코드

`common/.../exception/ErrorCodes.java` — 기존 상수 스타일로 4개 추가 (코드 번호는 파일 내 미사용 번호대 사용):
- `KRW_NOT_ENABLED` "원화 기능이 비활성화된 파트너입니다."
- `TORQ_NOT_ENABLED` "TORQ 기능이 비활성화된 파트너입니다."
- `P2P_MATCHING_NOT_ENABLED` "P2P 매칭 기능이 비활성화된 파트너입니다."

> ☠️ **`p2p_matching_enabled=0` 의 뜻이 바뀌었다 (2026-09-13).**
> 매칭 엔진에서 이 플래그는 「매칭을 하지 않는다」가 아니라 **「창 안에서 P2P 구간을 쓰지
> 않는다」**({@code RoutingPolicy.useP2p})다. 그러면 창은 P2P 구간 0 + LP 구간만 남고, 주문은
> 만들어져 그 창을 탄다.
>
> 그래서 구매 경로의 게이트를 「경로가 하나도 없을 때만 막는다」로 바꿨다. 종전에는 이 플래그가
> 꺼진 파트너의 결제가 매칭 창을 **아예 타지 못했다**(운영 실측: `p2p_deposit_orders` 0건,
> `matching_sessions` 0건).
>
> 같은 플래그를 읽는 곳이 **네 군데**였고, 한 곳만 바꾸면 「주문은 되는데 링크는 못 만드는」
> 반쪽 상태가 된다. 네 곳을 함께 바꿨다 — 다만 `reserveAndCreateLink` 는 출금 주문을 선점하는
> **P2P 전용** 경로라 그대로 둔다.
- `P2P_WITHDRAW_NOT_ENABLED` "P2P 출금 기능이 비활성화된 파트너입니다."

### A-3. core: 게이트 삽입 (write 경로만)

기존 p2pEnabled 게이트 패턴(`if (!partner.getP2pEnabled()) throw ...`)을 찾아 다음 규칙으로 교체/추가한다. **grep으로 전수 확인**: `grep -rn "p2pEnabled\|getP2pEnabled" core/ partner-api/ open-api/`

| 위치 (대표) | 게이트 |
|---|---|
| P2P 입금(구매) 주문 생성 — `P2pDepositService.createAndMatch`, `MatchingLinkService.createLink`, `P2pDepositLinkService.createLink` | ☠️ **2026-09-13 변경** — `p2p_matching_enabled` 로 막지 않는다. 「경로가 하나도 없을 때」만 `KRW_NOT_ENABLED` (`!isP2pMatchingAllowed() && !isTorqAllowed()`) |
| P2P 출금 주문 선점 후 링크 — `MatchingLinkService.reserveAndCreateLink` | `isP2pMatchingAllowed()` 위반 시 `P2P_MATCHING_NOT_ENABLED` — **P2P 전용이라 그대로 유지** |
| P2P 출금 주문 등록 / 일반 출금→P2P 전환 — `P2pWithdrawService`, `PartnerWithdrawalService`의 P2P 전환 경로 | `isP2pWithdrawAllowed()` → `P2P_WITHDRAW_NOT_ENABLED` |
| TORQ 견적/거래 생성 — `TorqService.getQuote/createTrade` (또는 진입 컨트롤러). eKYC 체크와 같은 위치 | `isTorqAllowed()` → `TORQ_NOT_ENABLED` |
| 통합 매칭 TORQ 레그 — `fillRemainderWithTorq` 호출 지점 | 구매자 파트너 `isTorqAllowed()` false면 TORQ 레그 스킵하고 파트너 레그로 진행 (예외 던지지 않음) |
| 매칭 라우팅 — `checkRoute` 등 P2P/TORQ 분기 | `p2p_matching` off && `torq` on → TORQ 전액 플로우로만 라우팅. 둘 다 off → `KRW_NOT_ENABLED` |
| 폰페이 세션 생성 (`PhonepayService`) — 기존 `partner_phonepay_settings.isEnabled` 체크 위치 | `isKrwActive()` 추가 (마스터만; 개별 플래그는 기존 설정 테이블 유지) |

게이트 순서: `KRW_NOT_ENABLED`(마스터) 먼저, 그다음 개별 플래그 에러.

### A-4. 매칭 풀 공급 제외 ⚠️ 최고 주의 구간

출금 주문 후보 조회 쿼리(`common/.../mapper/P2pMatchingMapper.java` 및 후보 셀렉트가 있는 매퍼 전부 — grep `p2p_withdraw_orders`로 전수 확인)에 출금측 파트너 조인 조건 추가:

```sql
JOIN partners wp ON wp.id = o.partner_id
AND wp.krw_enabled = 1 AND wp.p2p_withdraw_enabled = 1
```

**절대 규칙 (2026-06-11 전 서비스 다운 전례)**: `@Select("<script>...")` 내부는 XML로 파싱된다.
- `<`, `<=`, `<>` 직접 사용 금지 — `&lt;`/`&lt;=`/`!=` 또는 `<![CDATA[ ]]>`만 사용
- 수정 후 해당 파일 내 **모든** 비교 연산자 재점검
- ORDER BY/LIMIT/COUNT 직접 작성 금지 (XResultInterceptor 자동 처리) — 기존 쿼리 구조 보존

### A-5. open-api: 위젯 설정 노출 + 방어 게이트

- `open-api/.../controller/widget/InfoController.java`의 `GET /widgets/api/widget-config` (`WidgetConfigResponse`): 필드 추가
  - `krwDepositEnabled` = `krw && (p2p_matching || torq)` (구매 메뉴 노출용)
  - `p2pWithdrawEnabled` = `krw && p2p_withdraw`
  - `torqEnabled`, `p2pMatchingEnabled` (유효값) — 위젯이 플로우 분기에 사용
  - `partnerRepository.findOne(partnerId)`로 채움 (기존 TODO 주석 지점). DTO 멤버에 한 줄 주석 필수 (컨벤션)
- 위젯 write 컨트롤러(TORQ 거래 생성, P2P match, 링크 결제 진입)에 A-3 게이트가 서비스 레이어에 없다면 컨트롤러에서 추가. **위젯 경로(`/widgets/api/*`)는 반드시 `WidgetSessionData`** — 세션 타입 변경 금지.

### A-6. admin-api: 토글 API

`admin-api/.../controller/PartnerManagementController.java` + `PartnerManagementService`:
- `PATCH /{id}/krw-flags`, body:
```json
{ "krwEnabled": true, "torqEnabled": true, "p2pMatchingEnabled": true, "p2pWithdrawEnabled": true }
```
  - 4필드 모두 optional — null인 필드는 변경하지 않음 (부분 업데이트). ⚠️ Axim `modify()`는 selective update라 null 필드 미변경 — 이 용도에 정확히 부합. `update()` 쓰지 말 것.
  - 기존 `toggleP2pEnabled`(L478 부근) 패턴 미러링
- `AuditAction`에 `UPDATE_PARTNER_KRW_FLAGS` 상수 추가 (targetType 규칙은 기존 enum 참조), audit metadata에 변경 전/후 값 기록
- `PartnerDetailResponse`에 4필드 노출 (+`p2pEnabled` 기존 유지)
- 기존 `PATCH /{id}/p2p-enabled`(레거시): 동작 유지하되 **p2p_matching_enabled와 p2p_withdraw_enabled도 같은 값으로 동기화** (3컬럼 동시 세팅)

### A-7. partner-api: 조회 노출

`partner-api/.../controller/PartnerSettingsController.java` + `PartnerSettingsService`:
- `GET /api/partner/settings/krw` 신설 (read-only) — 4개 원본 플래그 + 유효값 4종 반환. DTO 멤버 주석 필수.
- 기존 `GET /p2p` 응답에 손대지 않는다 (호환).
- partner-api의 P2P/TORQ write 엔드포인트는 A-3 서비스 게이트로 커버되는지 확인, 미커버 시 컨트롤러 게이트 추가.

### A-8. 완료 기준 (Phase A)

1. `./gradlew :common:compileJava :core:compileJava :admin-api:compileJava :partner-api:compileJava :open-api:compileJava` 전부 성공
2. `grep -rn "getP2pEnabled" core/ partner-api/ open-api/` 결과가 레거시 동기화 지점(A-6)과 partner-api 기존 설정 API 외에 게이트 용도로 남아있지 않음
3. 매퍼 `<script>` 수정 파일에 raw `<` 비교 연산자 0건
4. 신규 write 게이트 각각에 대해: 플래그 false인 파트너로 호출 시 전용 에러코드 반환 경로가 코드상 명확

---

## Phase B — 프론트

### B-1. admin-ui (`/Users/dudgh/git/cryptoments-admin/admin-ui`)

- `src/api/services/partner.service.ts`: `updateKrwFlags(id, flags)` → `PATCH /admin/partners/{id}/krw-flags`
- `src/api/types/partner.ts`: PartnerDetail에 4필드
- `src/views/partners/PartnerDetailView.vue`: 기존 P2P `<Switch>` 블록(L379 부근)과 `handleP2pToggle` 패턴을 미러링해 **"원화 기능" 카드** 추가 — 마스터 1 + 하위 3 토글. 마스터 off면 하위 3개 disabled 표시. 토글 즉시 저장 + toast + refetch. 기존 P2P 토글은 그대로 두되 라벨에 "(구버전 — 매칭·출금 동시 변경)" 부기.

### B-2. partner-ui (`/Users/dudgh/git/cryptoments-admin/partner-ui`)

- 설정 API 연동: `GET /api/partner/settings/krw` 호출 서비스 추가
- `views/partner/settings/P2pSettingsSection.vue` 패턴으로 원화 기능 상태 read-only 표시 (StatusBadge)
- **write 차단 UX**: 유효 플래그 false일 때 — P2P 출금 전환 버튼, 매칭 링크 생성 버튼 disabled + 상단 배너 "원화(P2P 출금/매칭/TORQ) 기능이 비활성 상태입니다. 관리자에게 문의하세요." **메뉴는 숨기지 않는다** (이력·진행중 건 조회 유지).
- 플래그는 로그인 후 1회 로드해 스토어(pinia)에 보관.

### B-3. widget-ui (`/Users/dudgh/git/cryptoments/widget-ui`)

- widget-config 응답의 신규 필드 사용:
  - 원화 구매 진입 카드/메뉴: `krwDepositEnabled` false → 숨김
  - P2P 출금(전환) 화면/버튼: `p2pWithdrawEnabled` false → 숨김
  - 구매 플로우 내부: `p2pMatchingEnabled` false && `torqEnabled` true → TORQ 전액 플로우로 직행
- **진행중 주문 상태 화면은 플래그 무관 접근 유지** (주문 상세 라우트 가드 걸지 말 것)

### B-4. 완료 기준 (Phase B)

1. admin-ui: `npx vue-tsc --noEmit` 통과, 파트너 상세에서 4토글 동작
2. partner-ui: 타입체크 통과, 비활성 시 배너+버튼 disabled
3. widget-ui: 빌드 통과, 플래그 false 조합별 메뉴 숨김

---

## 코딩 규칙 (요약 — 상세는 CLAUDE.md)

- Java 17, Lombok `@Getter @Setter @Builder(toBuilder=true) @NoArgsConstructor @AllArgsConstructor`, `@Data` 금지
- DTO 멤버 변수 JavaDoc/한줄 주석 필수
- MyBatis: `@XRepository` interface 메서드(등호 매칭) / `@Mapper + @Select`(복합) — XML 매퍼 파일 금지
- `IXRepository.save()`는 PK(Long) 반환 — 엔티티 아님
- 예외는 `ErrorCodes` 상수 참조하는 도메인 예외
- open-api 세션: `/widgets/api/*` = WidgetSessionData, `/api/v1/*` = OpenApiSessionData (위반 시 401)
