> ⛔ **폐기됨 (2026-08-11).** 설계가 "입금 트리 워터폴"에서 "체인 합 모델"로 변경되어 이 문서는 더 이상 유효하지 않다.
> 현행 지침서: [`P2P_FEE_REDESIGN_FINAL_GUIDE.md`](./P2P_FEE_REDESIGN_FINAL_GUIDE.md)
> 이력 참고용으로만 보존한다.

# P2P 수수료 체계 통합 지침서 (DDL v2.8) — 폐기

작성일: 2026-08-11
대상: Cowork 서브에이전트 (구현)
선행 조건: DDL v2.8 적용 + §3.6 시스템 몫 결정 완료

> 출금 매칭 수수료 신설(`P2P_WITHDRAW_MATCH_FEE_GUIDE.md`)은 **보류**이며 DDL v2.9로 재지정한다.
> 이 문서가 먼저 적용된다.

---

## 1. 배경과 결정

P2P 수수료는 지금까지 P2P **전용** 배분 로직을 따로 갖고 있었다. 그 결과 세 가지 문제가 실측으로 확인됐다.

| 문제 | 실측 근거 |
|---|---|
| 입금 파트너 몫이 어디에도 기록되지 않음 | 2026-07-03 집계가 `WITHDRAW_BONUS` 2.2281 / `SYSTEM` 20.0531 두 행뿐. 파트너 몫이 `SYSTEM`에 혼재 |
| 코인이 시스템 계정을 경유 | 실현 5번이 파트너36 MASTER(524) → SETTLEMENT(525) 온체인 전송. 주간 재분배(실현 6번)는 `from/to_wallet` NULL, `tx_hash` NULL |
| 잔차라서 오염 | 배치 1(6/26~7/5) 실측 — 매장 81.59%, 보너스 8.41%. 이론값 80% / 10%와 불일치 |

**결론: P2P 전용 배분 로직을 폐기하고, 검증된 일반 입금 수수료 트리 메커니즘(`SettlementService.aggregateDailyFees`)으로 통합한다.**

### 확정 사항

| 항목 | 결정 |
|---|---|
| P2P 구매자 수수료 | **1%** (실제 부과. 구매자 수령 99%) |
| 배분 방식 | 일반 입금 트리 메커니즘 재사용 — 1% = SYSTEM 0.2% + 총판 0.8% |
| 요율 단위 | **퍼센트로 통일** (`1.0` = 1%). P2P 계열만 소수 비율이었음 |
| 수수료 산출 시점 | **매칭 단위** — 주문 선확정 + 비례 배분 폐기 |
| TORQ / PARTNER 레그 | 0% 유지 |
| 판매자 보너스 | **폐지** |
| 주간 재분배 | **폐지** — 정산 즉시 각자 몫 확정 |

---

## 2. 목표 모델

### 2.1 자금 흐름 (1,000 USDT 매칭)

```
출금 파트너 MASTER ──온체인 1,000 USDT──▶ 구매 파트너 MASTER
                                            │
                                  원장 CREDIT 1,000
                                       FEE      10   (= 1%)
                                            │
                                  순증 990 USDT
                                            │
                        D+1 트리 순회로 10 USDT 분배
                          ├ SYSTEM  2 USDT (parent_fee_rate 0.2%)
                          └ 총판    8 USDT (1.0% − 0.2%)
```

### 2.2 트리 분배 규칙 (`aggregateDailyFees`와 동일)

입금 파트너부터 최상위까지 올라가며 `remainingRate`를 소진한다.

| 단계 | 몫 |
|---|---|
| 입금 파트너가 `MERCHANT` | **0** — 전액 상위로 전달 (2026-07-13 정책, `SettlementService.java:570-578`) |
| 입금 파트너가 `DISTRIBUTOR` 직영 | `p2pFeeRate − min_fee_rate` |
| 중간 총판 | `remainingRate − parent_fee_rate` |
| 최상위 총판 | `remainingRate − systemRate` |
| SYSTEM | `min(최상위 parent_fee_rate, remainingRate)` |

파트너 36 실측 검산: `DISTRIBUTOR` 최상위, `parent_fee_rate = 0.2`, `min_fee_rate = 0.2`
→ 본인 마진 `1.0 − 0.2 = 0.8`, SYSTEM `0.2`. **합 1.0 ✓**

---

## 3. DDL (v2.8)

### 3.1 헤더 개정

```
-- ║  v2.8 (2026-08-11): P2P 수수료 체계 통합 — P2P 전용 배분 폐기,       ║
-- ║         일반 입금 트리 메커니즘으로 통합. 요율 단위 퍼센트 통일,      ║
-- ║         매칭 단위 수수료 산출(p2p_matches.fee_rate/fee_amount_krw).   ║
-- ║         p2p_share_rate/p2p_withdraw_bonus_rate/p2p_order_shares       ║
-- ║         deprecated. 테이블 수 변화 없음(54개)                         ║
```

### 3.2 요율 단위 변환 ⚠️ 순서 엄수

**변환 전 반드시 백업 테이블을 만들 것.**

```sql
CREATE TABLE bak_p2p_rate_unit_20260811 AS
  SELECT id, p2p_fee_rate, p2p_share_rate, p2p_withdraw_bonus_rate FROM partners;
CREATE TABLE bak_p2p_dpo_rate_20260811 AS
  SELECT id, fee_rate, fee_amount FROM p2p_deposit_orders;

-- ① partners.p2p_fee_rate : 소수 비율 → 퍼센트 (0.02 → 2.0)
UPDATE partners SET p2p_fee_rate = p2p_fee_rate * 100 WHERE p2p_fee_rate IS NOT NULL;

ALTER TABLE partners
  MODIFY COLUMN p2p_fee_rate DECIMAL(10,6) DEFAULT NULL
    COMMENT 'P2P 구매자 수수료율 오버라이드 — 퍼센트 단위 (1.0 = 1%). NULL=글로벌 p2p.fee_rate. 0=면제. deposit_fee_rate와 동일 단위 (v2.8)';

-- ② 기존 P2P 전용 배분 컬럼 — deprecated (DROP 하지 않고 주석으로 표기)
ALTER TABLE partners
  MODIFY COLUMN p2p_share_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '(v2.8 deprecated — 입금 트리 parent_fee_rate로 통합. 미사용) P2P 쉐어율',
  MODIFY COLUMN p2p_withdraw_bonus_rate DECIMAL(10,6) DEFAULT NULL
    COMMENT '(v2.8 deprecated — 판매자 보너스 폐지. 미사용) 출금자 매칭 보너스율';

-- ③ p2p_deposit_orders.fee_rate : 단위 변환 + 의미 변경
UPDATE p2p_deposit_orders SET fee_rate = fee_rate * 100;

ALTER TABLE p2p_deposit_orders
  MODIFY COLUMN fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '주문 실효 수수료율 — 퍼센트 단위, 매칭 확정 후 blended (= fee_amount / krw_amount × 100). 레그별 실제 요율은 p2p_matches.fee_rate (v2.8)',
  MODIFY COLUMN fee_amount BIGINT NOT NULL DEFAULT 0
    COMMENT '주문 총 수수료 (KRW) — 레그별 fee_amount_krw 합. 매칭 확정 후 기록 (v2.8)';

-- ④ p2p_matches : 레그별 수수료 (신설)
ALTER TABLE p2p_matches
  ADD COLUMN fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '이 레그의 구매자 수수료율 스냅샷 — 퍼센트 단위. P2P=파트너 effective, TORQ/PARTNER=0 (v2.8)'
    AFTER exchange_rate,
  ADD COLUMN fee_amount_krw BIGINT NOT NULL DEFAULT 0
    COMMENT '이 레그의 구매자 수수료 확정액 (KRW) = krw_amount × fee_rate / 100 (floor) (v2.8)'
    AFTER fee_rate,
  MODIFY COLUMN withdraw_bonus_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '(v2.8 deprecated — 판매자 보너스 폐지. 신규 매칭은 항상 0) 출금파트너 보너스율 스냅샷';

-- ⑤ p2p_order_shares : deprecated
ALTER TABLE p2p_order_shares
  COMMENT '(v2.8 deprecated — 입금 트리로 통합. 신규 기록 없음) P2P 주문 쉐어 스냅샷';

-- ⑥ settlement_daily_fees.share_rate 의미 통일
ALTER TABLE settlement_daily_fees
  MODIFY COLUMN share_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '이 참여자의 쉐어율 — 퍼센트 단위 (0.8 = 0.8%). v2.8부터 DEPOSIT/P2P 모두 트리에서 나온 설정 요율(myMargin). 사후 역산 금지',
  MODIFY COLUMN fee_role VARCHAR(20) NOT NULL DEFAULT 'BUYER_SHARE'
    COMMENT 'BUYER_SHARE(파트너 트리 몫) / SYSTEM(시스템 몫) / WITHDRAW_BONUS(v2.8 deprecated)';

-- ⑦ system_settings
UPDATE system_settings SET setting_value = '1.0',
  description = 'P2P 구매자 수수료 글로벌 기본율 — 퍼센트 단위 (1.0 = 1%). deposit_fee_rate와 동일 단위 (v2.8)'
 WHERE setting_key = 'p2p.fee_rate';

UPDATE system_settings SET setting_value = '0',
  description = '(v2.8 deprecated — 시스템 몫은 최상위 parent_fee_rate에서 산출) P2P 시스템 수익율'
 WHERE setting_key = 'p2p.system_rate';

UPDATE system_settings SET setting_value = '0',
  description = '(v2.8 deprecated — 판매자 보너스 폐지) P2P 출금자 매칭 보너스율'
 WHERE setting_key = 'p2p.withdraw_bonus_rate';

-- ⑧ 주석 버그 수정 — 실제 데이터는 0.2가 0.2% (기존 주석 "0.002 = 0.2%"는 오류)
ALTER TABLE partners
  MODIFY COLUMN parent_fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '상위가 이 파트너 트리에서 얻는 수수료율 — 퍼센트 단위 (0.2 = 0.2%). 최상위 파트너의 값이 곧 시스템 계약 수익률 (v2.8 주석 정정)';
```

### 3.3 단위 통일 결과

v2.8 이후 **모든 요율 컬럼이 퍼센트 단위**가 된다.

| 계열 | 컬럼 | 단위 |
|---|---|---|
| 입금 트리 | `deposit_fee_rate` / `parent_fee_rate` / `min_fee_rate` / `max_fee_cap` | 퍼센트 (기존 유지) |
| TORQ | `torq_rebate_*.rate` / `torq.rebate_rate` | 퍼센트 (기존 유지) |
| 집계 | `settlement_daily_fees.share_rate` | 퍼센트 (기존 유지) |
| **P2P** | `p2p_fee_rate` / `p2p_deposit_orders.fee_rate` / `p2p_matches.fee_rate` / `p2p.fee_rate` | **퍼센트 (v2.8 변환)** |

### 3.4 폐기 목록

| 대상 | 처리 |
|---|---|
| `partners.p2p_share_rate` | 컬럼 유지, deprecated 주석, 코드에서 미참조 |
| `partners.p2p_withdraw_bonus_rate` | 동일 |
| `p2p_matches.withdraw_bonus_rate` | 동일 (기존 행 보존, 신규는 0) |
| `p2p_order_shares` | 테이블 유지, 신규 기록 중단 |
| `p2p.system_rate` / `p2p.withdraw_bonus_rate` | 값 0, deprecated |
| `P2pRevenueShareService` / `P2pRevenueShareSettlementJob` | **코드 삭제** |
| `p2p_revenue_share_settlements` / `_items` | 테이블 + 기존 배치 1건 **이력 보존** |
| `P2pSettlementService.computeProportionalFeeKrw` | **메서드 삭제** |
| `SettlementService.aggregateDailyP2pFees` | **메서드 삭제** — `aggregateDailyFees`로 통합 |
| `SettlementMapper` P2P 전용 쉐어/보너스 쿼리 | **삭제** |
| `P2pFeeResolver.collectShareChain` / `resolveWithdrawBonusRate` | **삭제** |

### 3.5 데이터 마이그레이션

```sql
-- 기존 매칭에 레그별 수수료 백필 (통계 연속성용, 선택)
UPDATE p2p_matches pm
  JOIN p2p_deposit_orders dpo ON dpo.id = pm.deposit_order_id
   SET pm.fee_rate = CASE WHEN pm.leg_type = 'P2P' THEN dpo.fee_rate ELSE 0 END,
       pm.fee_amount_krw = CASE WHEN pm.leg_type = 'P2P'
                                THEN FLOOR(pm.krw_amount * dpo.fee_rate / 100) ELSE 0 END
 WHERE pm.fee_rate = 0;
```

> ⚠️ 이 백필은 §3.2 ③(단위 변환) **이후에** 실행할 것. 순서가 바뀌면 100배 오차.

미재분배 잔액 처리 — 전환 시점에 `settlement_daily_fees` 중 `fee_source='P2P' AND fee_role='SYSTEM' AND redistribution_id IS NULL` 인 행(현재 2건, 0.6135 USDT)은 **그대로 SYSTEM 몫으로 확정**한다. 주간 잡을 마지막으로 돌리지 않는다 — 금액이 10 USD 실현 임계 미만이라 어차피 실현되지 않는다.

### 3.6 ⚠️ 결정 필요 — `parent_fee_rate = 0` 파트너의 시스템 몫

실측: ACTIVE 최상위 `DISTRIBUTOR` 35곳 중 **24곳이 `parent_fee_rate = 0`**. 트리 메커니즘에서 시스템 몫은 최상위 `parent_fee_rate`에서 나오므로, 이 파트너들의 P2P는 **전액 총판 몫이 되고 시스템 수익이 0**이 된다.

파트너 1이 실제 해당한다 — `parent_fee_rate = 0`, `p2p_fee_rate` 설정됨, P2P 매칭 이력 있음(매칭 728).

선택지:

| 안 | 내용 | 영향 |
|---|---|---|
| A | 해당 파트너의 `parent_fee_rate`를 0.2로 설정 | 데이터 수정만. **일반 입금 수수료 분배도 함께 바뀜** |
| B | P2P 전용 시스템 최소 요율 `p2p.min_system_rate` 신설 | P2P만 영향. 트리 순회 후 SYSTEM이 하한 미만이면 총판 몫에서 차감 |
| C | 현행 수용 — 시스템 몫 0 허용 | 변경 없음. 해당 파트너 P2P는 시스템 수익 없음 |

**이 결정 없이 구현에 착수하지 말 것.**

---

## 4. 구현 지침 (파일별)

### 4.1 `core/settlement/SettlementService.java` — 트리 통합

`aggregateDailyFees`(L476~)를 **재원 파라미터화**한다. 로직 본체는 건드리지 말 것.

```java
/** 일반 입금 수수료 집계 — 재원: deposits 원장, 요율: partners.deposit_fee_rate */
public int aggregateDailyFees(LocalDate date) {
    return aggregateDailyFeesByTree(date, SettlementFeeSource.DEPOSIT);
}

/** P2P 수수료 집계 — 재원: P2P_SETTLEMENT 원장 FEE, 요율: partners.p2p_fee_rate (effective) */
public int aggregateDailyP2pFeesByTree(LocalDate date) {
    return aggregateDailyFeesByTree(date, SettlementFeeSource.P2P);
}
```

- 재원별로 다른 것은 **두 가지뿐**: ① 집계 소스 쿼리 ② `depositFeeRate` 자리에 들어갈 요율
- `P2P`일 때 요율 = `partner.p2pFeeRate != null ? partner.p2pFeeRate : 글로벌 p2p.fee_rate` (둘 다 퍼센트)
- 트리 순회 / `myMargin` 계산 / `proportionalShare` / `minFeeRate` 검증은 **완전히 공유**
- `feeSource`만 다르게 세팅. `feeRole`은 기존과 동일 (`SYSTEM` / `BUYER_SHARE`)
- **기존 `aggregateDailyP2pFees` 는 삭제**

집계 소스 쿼리(신규, `SettlementMapper`): 대상일 `SETTLED` P2P 레그의 `fee_amount_krw` 를 파트너×통화×네트워크로 합산하고 USDT 환산.

### 4.2 `core/p2p/P2pFeeResolver.java`

```java
/** 파트너 effective P2P 수수료율 — 퍼센트 단위 (1.0 = 1%) */
public BigDecimal resolveEffectiveFeeRate(Partner partner) { ... }
```

- 글로벌 기본값 상수를 `"0.02"` → `"1.0"` 으로 변경
- `collectShareChain` / `resolveWithdrawBonusRate` / 관련 상수 **삭제**
- JavaDoc에 **퍼센트 단위**임을 명시

### 4.3 `core/p2p/P2pDepositService.java` — 순서 반전 ⚠️

현재 `:110-115`에서 수수료를 확정하고 `:164`에서 매칭한다. **뒤집는다.**

```java
// 1) 주문 생성 — fee_rate / fee_amount 는 0 으로 시작
// 2) matchingService.tryMatchDeposit(order)   ← 레그 확정
// 3) 레그별 fee 합산 → 주문 갱신
long totalFee = matchRepo.findByDepositOrderId(order.getId()).stream()
        .filter(m -> m.getStatus() != P2pMatchStatus.CANCELLED
                  && m.getStatus() != P2pMatchStatus.FAILED)
        .mapToLong(P2pMatch::getFeeAmountKrw).sum();
BigDecimal blended = order.getKrwAmount() > 0
        ? BigDecimal.valueOf(totalFee).multiply(HUNDRED)
              .divide(BigDecimal.valueOf(order.getKrwAmount()), 6, RoundingMode.HALF_UP)
        : BigDecimal.ZERO;
depositOrderRepo.modify(order.toBuilder().feeAmount(totalFee).feeRate(blended).build());
```

- `p2p_order_shares` 기록 블록(`:118-159`) **전체 삭제**
- 매칭 실패로 레그가 0개면 `fee_amount = 0`, `fee_rate = 0`

### 4.4 `core/p2p/P2pMatchingService.java` — 레그별 수수료 산출

`createMatch`(789~)에서 레그 타입에 따라 채운다.

```java
BigDecimal legFeeRate = (legType == P2pLegType.P2P)
        ? feeResolver.resolveEffectiveFeeRate(depositPartner)   // 퍼센트
        : BigDecimal.ZERO;                                       // TORQ / PARTNER
long legFeeKrw = BigDecimal.valueOf(krwAmount)
        .multiply(legFeeRate)
        .divide(HUNDRED, 0, RoundingMode.DOWN)
        .longValue();
```

- `withdrawBonusRate` 는 **항상 `BigDecimal.ZERO`** (보너스 폐지)
- L800-806 보너스 캡 블록 **삭제**
- TORQ 레그 생성부(`:485-500`)도 `feeRate(ZERO) / feeAmountKrw(0)` 명시

`checkRoute`(`:1059-1113`)의 `getStringSetting("p2p.fee_rate")`를 `feeResolver.resolveEffectiveFeeRate(partner)` 로 교체 — 기존 버그(파트너 오버라이드 무시) 동시 수정.

### 4.5 `core/p2p/P2pSettlementService.java` — 비례 배분 폐기

```java
// 기존: long feeKrwThis = computeProportionalFeeKrw(dpo, match);
long feeKrwThis = match.getFeeAmountKrw();          // 레그에 확정액이 있음
BigDecimal feeUsdt = BigDecimal.valueOf(feeKrwThis)
        .divide(match.getExchangeRate(), 18, RoundingMode.DOWN);
```

- `computeProportionalFeeKrw`(`:713-738`) **메서드 삭제**
- `legType == PARTNER → 0` 분기(`:446`)는 `fee_amount_krw = 0` 으로 자연 처리되므로 삭제 가능
- `creditTorqLeg`(`:516-557`)는 **변경 없음** (FEE 0 유지)

### 4.6 `core/p2p/P2pRevenueShareService.java` + Job — 삭제

- `P2pRevenueShareService`, `P2pRevenueShareSettlementJob`, `P2pRevenueShareMapper` **삭제**
- `settlementService.creditRealizedDirect` 가 다른 호출자 없이 이 서비스 전용이면 함께 삭제
- 엔티티/Repository(`P2pRevenueShareSettlement`, `P2pRevenueShareItem`)는 **이력 조회용으로 유지**

### 4.7 `admin-api/.../P2pFeeSettingsService.java`

- `FEE_MIN` / `FEE_MAX` 를 퍼센트 기준으로 (`1.0` ~ `3.0`)
- `findChainViolations` / 쉐어 체인 검증 **삭제** — 트리 정합성은 기존 입금 수수료 검증이 담당
- 파트너 P2P 요율 검증: `min_fee_rate ≤ p2p_fee_rate ≤ max_fee_cap` (입금 수수료와 동일 밴드 규칙)
- 보너스 관련 API / DTO 필드 **삭제**

### 4.8 `open-api` 위젯 DTO — 레그별 수수료 노출

`P2pWidgetMatchResponse.computeExpectedUsdt`(`:120-130`)의 **`gross × (1 − feeRate)` 를 폐기**한다. 현재 TORQ 레그(이미 net)에 요율을 또 곱해 표시가 틀리고 있다.

```java
// 레그별로 net 을 구해 합산
BigDecimal net = matches.stream()
        .filter(활성)
        .map(m -> m.getUsdtAmount().subtract(
                BigDecimal.valueOf(m.getFeeAmountKrw())
                        .divide(m.getExchangeRate(), 18, RoundingMode.DOWN)))
        .reduce(BigDecimal.ZERO, BigDecimal::add);
```

레그 상세 DTO(`P2pWidgetMatchDetailResponse`)에 `legType` / `feeRate` / `feeAmountKrw` 추가.

### 4.9 `widget-ui`

| 파일:라인 | 조치 |
|---|---|
| `p2p.vue:871-888` 확인 화면 | 매칭 전이라 레그 미상 — **보수적으로 최대 요율 표시**. 오차가 항상 사용자에게 유리한 방향이라 분쟁 없음 |
| `p2p.vue:1024-1028` 이체 화면 요율 | 주문 blended `fee_rate` 그대로 사용 (v2.8부터 실징수액 기준이라 정확) |
| `p2p.vue:991-996` 성공 화면 | 서버 `expectedUsdt` 를 그대로 쓰도록 변경 — 프론트 재계산 제거 |
| `p2p.vue:529-532` verifying 화면 | 현재 gross 표시 → 성공 화면과 동일 net 으로 통일 |
| `p2p.vue:388-425` 레그 카드 | `legType` 배지 추가 |
| `torq.vue:337-340` + `ko.js:189` / `en.js:189` | 하드코딩 `'수수료 (2%)'` → 파라미터화 |
| `p2p.vue:1237-1266` 데모 하드코딩 | 퍼센트 단위로 갱신 |

### 4.10 `cryptoments-admin`

| 파일 | 조치 |
|---|---|
| `admin-ui/.../P2pFeeSettingsView.vue:206-218` | 입력 단위 퍼센트로. 보너스/쉐어 입력 제거 |
| 같은 파일 `:271` | `"수수료율은 주문 생성 시점에 스냅샷"` → `"매칭 확정 시점에 레그별로 산출"` |
| `admin-ui/.../PartnerP2pFeeCard.vue` | 쉐어율/보너스율 필드 제거 |
| `admin-ui/.../P2pDepositOrderDetailView.vue:227-229` | 주문 blended + 레그별 요율 함께 표시 |
| `partner-ui/.../P2pSettingsSection.vue:77-78` | 퍼센트 단위 표기 확인 |

---

## 5. 검증 및 완료 기준

### 5.1 단위 전수 확인 ⚠️ 최우선

`p2p_fee_rate` / `p2p.fee_rate` / `fee_rate` 를 소수 비율로 다루는 코드를 **전부 찾아 목록화**하고 퍼센트로 교체했는지 보고할 것.

```bash
grep -rn "p2p.fee_rate\|getP2pFeeRate\|getFeeRate()" --include=*.java core/ open-api/ admin-api/ partner-api/ scheduler/ common/
grep -rn "0.02\|multiply(.*100\|divide(.*100" --include=*.java core/src/main/java/com/cryptoments/core/p2p/
grep -rn "feeRate" --include=*.vue --include=*.js ../cryptoments-admin/ widget-ui/
```

**판정 기준**: 퍼센트 값을 금액에 곱할 때는 반드시 `/100`이 있어야 한다. 없으면 100배 과다.

### 5.2 검산 (필수)

파트너 36 기준, 1,000 USDT 매칭 · P2P 1%:

| 항목 | 기대값 |
|---|---|
| `p2p_matches.fee_rate` | `1.000000` |
| `p2p_matches.fee_amount_krw` | `krw_amount × 1 / 100` (floor) |
| 원장 CREDIT | 1,000 |
| 원장 FEE | 10 |
| 순증 | 990 |
| `settlement_daily_fees` SYSTEM | `share_rate = 0.2`, `share_amount = 2` |
| `settlement_daily_fees` BUYER_SHARE (파트너 36) | `share_rate = 0.8`, `share_amount = 8` |
| 두 행 합 | 10 ✓ |

### 5.3 회귀 불변

- 일반 입금 수수료(`fee_source = DEPOSIT`) 집계 결과가 **변경 전과 완전히 동일**
- TORQ 레그 크레딧 금액 불변 (FEE 0 유지)
- PARTNER 레그 FIAT 정산 경로 불변

### 5.4 양방향 DDL 정합성

DDL에 있는데 Entity에 없는 컬럼 / Entity에 있는데 DDL에 없는 컬럼 — 두 방향 모두 확인 보고.

### 5.5 빌드

```bash
./gradlew :common:compileJava :core:compileJava :admin-api:compileJava :partner-api:compileJava :open-api:compileJava :scheduler:compileJava
```

> 샌드박스는 Java 11이므로 **Desktop Commander로 로컬 Mac에서** 실행할 것.

### 5.6 MyBatis `<script>` 주의 ⚠️

신규 매퍼 SQL의 `<script>` 내부에서 `<`, `<=`, `<>` 직접 사용 금지. 기동 시점 `SAXParseException` 으로 전 서비스가 내려간다 (2026-06-11 운영 장애). `&lt;` / `!=` / `<![CDATA[ ]]>` 사용. `ORDER BY` / `LIMIT` / `COUNT` 직접 작성 금지.

### 5.7 완료 기준

1. §3.6 시스템 몫 결정 반영
2. §5.1 단위 전수 확인 보고 완료
3. §5.2 검산 재현 (로컬 DB 또는 단위 테스트)
4. §5.3 회귀 불변 확인
5. §5.4 양방향 정합성 이상 없음
6. §5.5 6개 모듈 컴파일 성공

---

## 6. 함정 정리

| # | 함정 | 대응 |
|---|---|---|
| 1 | 단위 100배 오차 | §5.1 전수 확인. 퍼센트는 반드시 `/100` |
| 2 | 백필을 단위 변환 **전에** 실행 | §3.5 순서 엄수 |
| 3 | `minFeeRate` 불일치 시 정산 **통째 스킵** | `SettlementService.java:497-504`. 전환 전 전 파트너 `computeMinFeeRate` 검증 |
| 4 | `MERCHANT` 입금 파트너는 몫이 0 | 2026-07-13 정책. P2P를 쓰는 매장이 생기면 수익 0 — 사전 고지 필요 |
| 5 | `parent_fee_rate = 0` → 시스템 몫 0 | §3.6 결정 |
| 6 | 주문 `fee_rate` 의미 변경 (스냅샷 → blended) | 어드민/파트너 화면 문구 수정 |
| 7 | `settlement_daily_fees.share_rate` 역산값 혼재 | v2.8부터 설정 요율만. 과거 P2P 행은 역산값이라 비교 금지 |
| 8 | 위젯 확인 화면은 여전히 레그 미상 | 보수적 최대 요율 표시 |

---

## 7. 배포

1. §3.6 결정 → DDL v2.8 적용 (**사람이 실행**, 로컬 → 운영 순)
2. 코드 구현 → 로컬 빌드 검증 → 커밋 → `git push origin main`
3. GitLab Pipelines에서 `spring:deploy-production` ▶ + `widget-ui:deploy` ▶ + admin/partner UI 배포 잡 ▶ 수동 실행
4. 배포 직후 §5.2 검산을 운영 실거래 1건으로 재확인

> `git push` 는 Cowork 샌드박스에 SSH 키가 없으므로 Desktop Commander로 로컬 Mac에서 실행한다.
