> ⛔ **폐기됨 (2026-08-11).** 출금 수수료를 gross-up(1002 전송) 방식으로 설계했으나, 분쟁·취소 정합성을 위해
> "1000 전송 + 수수료 즉시 계상" 방식으로 변경되었다.
> 현행 지침서: [`P2P_FEE_REDESIGN_FINAL_GUIDE.md`](./P2P_FEE_REDESIGN_FINAL_GUIDE.md)
> 이력 참고용으로만 보존한다.

# P2P 출금 매칭 수수료 신설 지침서 (DDL v2.8) — 폐기

작성일: 2026-08-11
대상: Cowork 서브에이전트 (구현)
선행 조건: DDL v2.8 적용 완료

---

## 1. 배경과 결정

기존 P2P 수수료는 **구매자(입금자) 단일 부담**이었다. 여기에 **출금 파트너가 부담하는 매칭 수수료**를 신설한다.

파트너 협의에서 나온 요율 구조는 "입금 0.7% + 환전 0.2%, 시스템 0.2%"이며, 이 중 **환전 0.2%가 출금측 부담분**이다. 실무 정리는 "회원에게 100만원을 지급하기 위해 100만2천원이 소모된다" — 즉 **출금 회원 수령액은 불변이고 출금 파트너가 100.2%를 내놓는다.**

### 확정 사항

| 항목 | 결정 |
|---|---|
| 부담 주체 | 출금(판매자) 파트너. 출금 회원 수령액은 불변 |
| 구현 방식 | 별도 징수 경로 없이 `usdtNeeded × (1 + rate)` — 매칭 수량 산정에서 흡수 |
| 수취처 | 출금 파트너의 **상위 총판 체인** 각자 요율 + **시스템 잔여** |
| 유연성 | 총판 쉐어 0 → 시스템 전액 / 총판 쉐어 = 부담률 → 총판 전액 / 그 사이 임의 분배 |
| 기준 | 거래액 기준 절대 요율 (수수료 대비 비율 아님) |
| 적용 레그 | **P2P 레그만.** TORQ / PARTNER 레그는 0 |
| 기본값 | 0 — 적용 전까지 기존 동작 완전 불변 |
| 범위 | 이번 작업은 출금 매칭 수수료 신설까지. 구매자측 수수료 체계 개편(명시 요율·즉시 지급 통일)은 별건 |

---

## 2. 요율 모델

### 2.1 산식

```
기준액(USDT)      base      = krw_amount / exchange_rate
출금 매칭 수수료   matchFee  = base × withdrawMatchFeeRate
출금 파트너 유출   released  = base + matchFee = base × (1 + withdrawMatchFeeRate)
```

`released` 가 잠금·원장 DEBIT·온체인 전송액 전부에 동일하게 적용된다.

### 2.2 배분

```
WITHDRAW_MATCH_SHARE_j  = matchFee × (share_rate_j / withdrawMatchFeeRate)   ← 출금 파트너의 상위 총판 각자
WITHDRAW_MATCH_SYSTEM   = max(0, matchFee − Σ WITHDRAW_MATCH_SHARE_j)
```

출금 파트너 **본인은 수취하지 않는다**(부담자이므로 자기 몫은 상쇄되어 무의미). 체인은 부모부터 루트까지만 순회한다.

### 2.3 숫자 예시

전제: 거래액 1,000,000원 · 환율 1,400 · 구매자 수수료 0.7% · 출금 매칭 수수료 0.2% · 출금측 상위 총판 쉐어 0.1%

| 항목 | 값 |
|---|---|
| base | 714.285714285714285714 |
| matchFee | 1.428571428571428571 |
| released (= `p2p_matches.usdt_amount`) | 715.714285714285714285 |
| 온체인 전송 / 입금 파트너 CREDIT | 715.714285714285714285 |
| FEE (구매자 0.7% = 5.0 + matchFee 1.4286) | 6.428571428571428571 |
| 입금 파트너 원장 순증 | 709.285714285714285714 (기준 대비 99.3%) |
| 출금 회원 은행 수령 | 1,000,000원 (불변) |
| 배분 — 출금측 상위 총판 (0.1%) | 0.714285714285714285 |
| 배분 — 시스템 잔여 (0.1%) | 0.714285714285714285 |

검산: `715.714285… − 6.428571… = 709.285714…` · `0.714285… + 0.714285… = 1.428571…`

---

## 3. DDL (v2.8)

### 3.1 헤더 개정 (`CRYPTOMENTS_V2_DDL.sql`)

```
-- ║  v2.8 (2026-08-11): P2P 출금 매칭 수수료 — 출금 파트너 부담 요율    ║
-- ║         (partners.p2p_withdraw_match_fee_rate) + 출금측 상위 총판   ║
-- ║         쉐어(partners.p2p_withdraw_share_rate) +                    ║
-- ║         p2p_withdraw_orders.match_fee_rate 스냅샷 +                 ║
-- ║         p2p_withdraw_order_shares 체인 스냅샷 테이블 +              ║
-- ║         p2p_matches.withdraw_match_fee_rate/_usdt +                 ║
-- ║         fee_role WITHDRAW_MATCH_SHARE / WITHDRAW_MATCH_SYSTEM.      ║
-- ║         테이블 55개                                                 ║
```

### 3.2 ALTER 스크립트

```sql
-- ── ① partners: 부담률 + 출금측 쉐어율 ──────────────────────────────
ALTER TABLE partners
  ADD COLUMN p2p_withdraw_match_fee_rate DECIMAL(10,6) DEFAULT NULL
    COMMENT 'P2P 출금 매칭 수수료율 오버라이드 — 이 파트너가 출금측일 때 부담 (소수 비율, 거래액 기준 절대율). NULL=글로벌 p2p.withdraw_match_fee_rate, 0=면제 (v2.8)'
    AFTER p2p_withdraw_bonus_rate,
  ADD COLUMN p2p_withdraw_share_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '이 파트너가 출금측 상위 총판으로서 받을 매칭 수수료 쉐어율 (소수 비율, 거래액 기준 절대율). 출금 파트너 본인 몫 없음(부담자). 체인 합 ≤ effective 출금 매칭 수수료율, 잔여=시스템 (v2.8)'
    AFTER p2p_withdraw_match_fee_rate;

-- ── ② p2p_withdraw_orders: 주문 시점 요율 스냅샷 ────────────────────
ALTER TABLE p2p_withdraw_orders
  ADD COLUMN match_fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT '주문 생성 시점 출금 파트너 effective 매칭 수수료율 스냅샷 (v2.8)'
    AFTER exchange_rate;

-- ── ③ p2p_withdraw_order_shares: 출금측 체인 스냅샷 (신규) ──────────
CREATE TABLE p2p_withdraw_order_shares (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,

    withdraw_order_id BIGINT NOT NULL
        COMMENT 'p2p_withdraw_orders.id',

    partner_id BIGINT NOT NULL
        COMMENT '쉐어 수취 파트너 (출금 파트너의 상위 총판) — partners.id. 출금 파트너 본인은 미기록',

    share_rate DECIMAL(10,6) NOT NULL
        COMMENT '이 파트너의 출금측 쉐어율 스냅샷 (소수 비율, 거래액 기준 절대율). 주문 생성 시점 partners.p2p_withdraw_share_rate 고정',

    created_at DATETIME(6) DEFAULT CURRENT_TIMESTAMP(6),

    UNIQUE KEY uk_worder_partner (withdraw_order_id, partner_id),
    KEY idx_partner (partner_id)
) COMMENT 'P2P 출금 주문 쉐어 스냅샷 — 출금 매칭 수수료의 상위 총판 체인 (쉐어율 0인 파트너는 미기록) (v2.8)';

-- ── ④ p2p_matches: 매칭 스냅샷 + 확정액 ─────────────────────────────
ALTER TABLE p2p_matches
  ADD COLUMN withdraw_match_fee_rate DECIMAL(10,6) NOT NULL DEFAULT 0
    COMMENT 'P2P 레그 매칭 생성 시 출금 파트너 effective 매칭 수수료율 스냅샷. TORQ/PARTNER 레그=0 (v2.8)'
    AFTER withdraw_bonus_rate,
  ADD COLUMN withdraw_match_fee_usdt DECIMAL(36,18) NOT NULL DEFAULT 0
    COMMENT '이 매칭의 출금 매칭 수수료 확정액 (USDT). usdt_amount에 이미 가산되어 있음 — 기준액 = usdt_amount − withdraw_match_fee_usdt (v2.8)'
    AFTER withdraw_match_fee_rate;

-- ── ⑤ settlement_daily_fees: fee_role 주석 확장 ─────────────────────
ALTER TABLE settlement_daily_fees
  MODIFY COLUMN fee_role VARCHAR(20) NOT NULL DEFAULT 'BUYER_SHARE'
    COMMENT 'BUYER_SHARE(구매자측 매장/총판) / WITHDRAW_BONUS(출금자 보너스) / SYSTEM(구매자 수수료 시스템 잔여) / WITHDRAW_MATCH_SHARE(출금측 상위 총판) / WITHDRAW_MATCH_SYSTEM(출금 매칭 수수료 시스템 잔여) (v2.8)';

-- ── ⑥ system_settings: 글로벌 기본값 ────────────────────────────────
INSERT INTO system_settings (setting_key, setting_value, description, value_type) VALUES
('p2p.withdraw_match_fee_rate', '0',
 'P2P 출금 매칭 수수료 글로벌 기본율 (소수 비율, 0.002=0.2%). 출금 파트너 부담 — 출금 회원 수령액 불변, 파트너가 base×(1+rate) 유출. 허용 범위 0~0.01. 0=미부과 (v2.8)',
 'NUMBER')
ON DUPLICATE KEY UPDATE setting_value = setting_value;
```

### 3.3 `p2p_matches.usdt_amount` 의미 변경 ⚠️

**이번 변경의 최대 리스크.** v2.8부터 `usdt_amount ≠ krw_amount / exchange_rate` 가 될 수 있다.

```
usdt_amount = krw_amount / exchange_rate × (1 + withdraw_match_fee_rate)
기준액       = usdt_amount − withdraw_match_fee_usdt
```

`usdt_amount` 를 **실제 잠금·DEBIT·온체인 전송액**으로 정의한 이유는, 하위 경로(잠금 해제, 정산, 전송)가 자동으로 정합을 유지하기 때문이다. 대신 `krw / rate` 를 재계산해 `usdt_amount` 와 비교하거나 대체하는 코드가 있으면 전부 깨진다 — §5.1 전수 확인 항목.

---

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

### 4.1 `common` — enum

**`common/src/main/java/com/cryptoments/common/enums/FeeRole.java`**
- `WITHDRAW_MATCH_SHARE`, `WITHDRAW_MATCH_SYSTEM` 추가.

**`common/.../entity/Partner.java`**
- `p2pWithdrawMatchFeeRate` (BigDecimal, nullable), `p2pWithdrawShareRate` (BigDecimal, NOT NULL) 추가. `@XColumn` 매핑.

**`common/.../entity/P2pWithdrawOrder.java`**
- `matchFeeRate` (BigDecimal) 추가.

**`common/.../entity/P2pMatch.java`**
- `withdrawMatchFeeRate`, `withdrawMatchFeeUsdt` 추가.

**신규 `common/.../entity/P2pWithdrawOrderShare.java` + `repository/P2pWithdrawOrderShareRepository.java`**
- `p2p_order_shares` / `P2pOrderShareRepository` 를 그대로 미러링. `findByWithdrawOrderId(Long)` 제공.

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

기존 `resolveEffectiveFeeRate` / `resolveWithdrawBonusRate` / `collectShareChain` 과 동일한 패턴으로 추가한다.

```java
/** 글로벌 키 */
private static final String SETTING_WITHDRAW_MATCH_FEE_RATE = "p2p.withdraw_match_fee_rate";
/** 기본값 — 0 (미부과) */
private static final BigDecimal DEFAULT_WITHDRAW_MATCH_FEE_RATE = BigDecimal.ZERO;

/** 출금 파트너 effective 매칭 수수료율. 오버라이드 NULL=글로벌, 0은 유효값(면제) */
public BigDecimal resolveWithdrawMatchFeeRate(Partner withdrawPartner) { ... }

/** 출금 파트너의 상위 총판 체인 쉐어 — 본인 제외, 부모부터 루트까지. p2p_withdraw_share_rate > 0 인 파트너만 */
public List<ShareEntry> collectWithdrawShareChain(Partner withdrawPartner) { ... }
```

- `collectWithdrawShareChain` 은 **본인을 포함하지 않는다.** `collectShareChain` 과 다른 유일한 지점이므로 JavaDoc에 명시할 것.
- 순환 방어(visited Set)는 기존과 동일하게 유지.

### 4.3 `core/p2p/P2pWithdrawService.java` — 주문 생성

`P2pDepositService.java:109-159` 의 입금 주문 스냅샷 패턴을 미러링한다.

1. `matchFeeRate = feeResolver.resolveWithdrawMatchFeeRate(partner)` → `p2p_withdraw_orders.match_fee_rate` 저장
2. `matchFeeRate.signum() > 0` 일 때만 `collectWithdrawShareChain(partner)` 호출
3. 체인 합이 `matchFeeRate` 를 초과하면 **체인 전체를 미기록**(`List.of()`) — 기존 `P2pDepositService.java:124-128` 과 동일한 방어
4. 남은 체인을 `p2p_withdraw_order_shares` 에 저장

### 4.4 `core/p2p/P2pMatchingService.java` — 산식 4곳 ⚠️ 핵심

현재 아래 네 곳이 모두 `krw / wo.exchangeRate` 동일 패턴이다. **헬퍼 하나로 통일하고 전부 교체할 것.**

| 라인 | 맥락 |
|---|---|
| 273-274 | 완전 일치 후보 잠금 |
| 300 | 분할 매칭 플랜 산정 |
| 386 | 분할 매칭 실행 잠금 |
| 791-792 | `createMatch` — `usdt_amount` 산출 |

```java
/**
 * 출금 파트너가 실제로 내놓아야 할 USDT — 기준액 + 출금 매칭 수수료.
 * 잠금 / 원장 DEBIT / 온체인 전송이 모두 이 값을 쓴다.
 *
 * @param rounding 잠금 산정은 UP(273/300/386), 매칭 확정은 DOWN(791) — 기존 각 호출부의 모드를 유지할 것
 */
private BigDecimal grossUsdt(long krwAmount, P2pWithdrawOrder wo, RoundingMode rounding) {
    BigDecimal base = BigDecimal.valueOf(krwAmount).divide(wo.getExchangeRate(), 18, rounding);
    BigDecimal rate = wo.getMatchFeeRate() == null ? BigDecimal.ZERO : wo.getMatchFeeRate();
    return base.add(base.multiply(rate)).setScale(18, rounding);
}
```

`createMatch`(789-822)에서는 추가로 스냅샷을 채운다.

```java
BigDecimal matchFeeRate = BigDecimal.ZERO;
BigDecimal matchFeeUsdt = BigDecimal.ZERO;
if (legType == P2pLegType.P2P) {                      // ← 레그 게이트. 기존 보너스와 동일
    matchFeeRate = wo.getMatchFeeRate() == null ? BigDecimal.ZERO : wo.getMatchFeeRate();
    BigDecimal base = BigDecimal.valueOf(krwAmount)
            .divide(wo.getExchangeRate(), 18, RoundingMode.DOWN);
    matchFeeUsdt = base.multiply(matchFeeRate).setScale(18, RoundingMode.DOWN);
    usdtAmount = base.add(matchFeeUsdt);
}
```

- **`withdraw_bonus_rate` 캡 로직(L803)은 건드리지 말 것.** 보너스는 구매자 수수료 재원, 매칭 수수료는 별도 재원이므로 서로 캡을 걸지 않는다.
- TORQ / PARTNER 레그는 두 컬럼 모두 0, `usdtAmount` 는 기존 산식 그대로.

### 4.5 `core/p2p/P2pSettlementService.java` — FEE 합산

`completeSettlement`(L394-503)의 FEE 기록(L463-468)을 확장한다.

```java
BigDecimal buyerFeeUsdt = ...;                                 // 기존 computeProportionalFeeKrw 경로 — 변경 없음
BigDecimal matchFeeUsdt = match.getWithdrawMatchFeeUsdt() == null
        ? BigDecimal.ZERO : match.getWithdrawMatchFeeUsdt();
BigDecimal totalFeeUsdt = buyerFeeUsdt.add(matchFeeUsdt);

settlementService.credit(dpo.getPartnerId(), ..., match.getUsdtAmount(), ...);   // 전액(가산분 포함)
settlementService.recordFee(dpo.getPartnerId(), ..., totalFeeUsdt, ...);         // 합산
```

- `deposits` 행(L1060-1090)의 `fee_amount` 도 `totalFeeUsdt` 로.
- `settleFromLocked` / 출금측 DEBIT은 `match.getUsdtAmount()` 를 그대로 쓰므로 **수정 불필요** — 이미 가산분이 포함돼 있다.
- `settlePartnerLegFiat`(L307-385) 및 TORQ 경로는 변경 없음.

### 4.6 `core/settlement/SettlementService.java` + `common/.../mapper/SettlementMapper.java` — 집계

`aggregateDailyP2pFees`(L633-723)에 **두 번째 재원 블록**을 추가한다. 기존 구매자 수수료 블록은 그대로 둔다.

신규 매퍼 쿼리 2개:

```sql
-- ⓐ 출금 매칭 수수료 총계 (매장×통화×네트워크)
SELECT dpo.partner_id AS depositPartnerId, ...,
       SUM(pm.withdraw_match_fee_usdt) AS totalMatchFee
  FROM p2p_matches pm
  JOIN p2p_deposit_orders dpo ON dpo.id = pm.deposit_order_id
 WHERE pm.status = 'SETTLED' AND pm.leg_type = 'P2P'
   AND pm.withdraw_match_fee_usdt > 0
   AND DATE(pm.settled_at) = #{date}
 GROUP BY ...

-- ⓑ 출금측 총판 쉐어
SELECT ..., pwos.partner_id AS participantPartnerId,
       SUM(pm.withdraw_match_fee_usdt * pwos.share_rate / pm.withdraw_match_fee_rate) AS shareAmount
  FROM p2p_matches pm
  JOIN p2p_withdraw_order_shares pwos ON pwos.withdraw_order_id = pm.withdraw_order_id
  ...
 WHERE pm.withdraw_match_fee_rate > 0
```

집계 결과 기록 규칙:

| 필드 | 값 |
|---|---|
| `fee_source` | `P2P` |
| `fee_role` | `WITHDRAW_MATCH_SHARE` / `WITHDRAW_MATCH_SYSTEM` |
| `source_partner_id` | **입금(구매측) 파트너** — 출금 파트너가 아님 |
| `participant_partner_id` | 출금측 상위 총판 (SYSTEM 행은 `null`, balance 저장 시 `0`) |

> ⚠️ **`source_partner_id` 를 출금 파트너로 쓰지 말 것.** 실현 잡(`SettlementService.java:854-864`)이 `fromWalletId = source_partner MASTER` 로 스윕하는데, 출금 매칭 수수료 코인은 온체인 전송을 타고 **입금 파트너 MASTER** 에 물리적으로 있다. 출금 파트너를 source로 쓰면 코인이 없는 지갑에서 스윕을 시도한다. 기존 `WITHDRAW_BONUS` 도 동일하게 `source=입금 매장 / participant=출금 파트너` 규약이다.

`WITHDRAW_MATCH_SYSTEM = max(0, totalMatchFee − Σ WITHDRAW_MATCH_SHARE)` — 기존 SYSTEM 블록(L701-703)과 같은 클램프를 쓰되 **별도 role로 분리 기록**한다. 기존 `SYSTEM` role에 합산하지 말 것(재원 추적 불가).

### 4.7 `admin-api/.../P2pFeeSettingsService.java` — 검증 + API

1. 글로벌 `p2p.withdraw_match_fee_rate` 읽기/쓰기. 범위 `0 ≤ rate ≤ 0.01`
2. 파트너 `p2pWithdrawMatchFeeRate` 오버라이드. `null`(글로벌 복귀) 또는 `0 ≤ rate ≤ 0.01`
3. 파트너 `p2pWithdrawShareRate` ≥ 0
4. **신규 불변식**: 임의 파트너 P에 대해
   `Σ(P의 상위 총판 p2p_withdraw_share_rate) ≤ effective withdraw_match_fee_rate(P)`
   기존 `findChainViolations` 와 같은 구조의 `findWithdrawChainViolations` 를 만들어 글로벌 변경·파트너 변경 양쪽에서 서브트리 전수 검증
5. `null` 오버라이드 해제는 `modify()` 가 아니라 `update()` 를 써야 실제 NULL이 기록된다 (기존 주석 참조)

응답 DTO에 신규 3필드 추가. 모든 DTO 멤버에 JavaDoc 필수.

### 4.8 `partner-api` — 매칭 가능 잔여 표시

출금 주문의 USDT 소진이 `(1 + rate)` 배로 빨라진다. 파트너 콘솔이 "매칭 가능 잔여"를 `usdt_amount` 기준으로 계산하는 곳이 있으면 동일 헬퍼를 적용해 보정할 것. 미보정 시 화면상 잔여와 실제 매칭 가능액이 어긋난다.

### 4.9 `cryptoments-admin` (admin-ui)

P2P 수수료 설정 화면에 3필드 추가 — 글로벌 출금 매칭 수수료율, 파트너별 부담률 오버라이드, 파트너별 출금측 쉐어율. 기존 P2P 수수료 설정 화면의 입력·검증 UX를 그대로 따를 것.

---

## 5. 검증 및 완료 기준

### 5.1 전수 확인 (구현 전 필수) ⚠️

`usdt_amount = krw_amount / exchange_rate` 를 **가정하거나 재계산하는 코드를 전부 찾아 목록화**하고, 각각이 기준액을 원하는지 유출액을 원하는지 판정해 보고할 것.

```bash
grep -rn "getExchangeRate()" --include=*.java core/ partner-api/ open-api/ admin-api/ scheduler/
grep -rn "getUsdtAmount()" --include=*.java core/ partner-api/ open-api/ admin-api/ scheduler/
```

판정 기준: 잠금·DEBIT·온체인 전송·CREDIT = **유출액(usdt_amount)** / 회원 표시·환율 검증·수수료 비례 계산 = **기준액**.

### 5.2 양방향 DDL 정합성

- DDL에 있는데 Entity에 없는 컬럼
- Entity에 있는데 DDL에 없는 컬럼 (팬텀 컬럼)

두 방향 모두 확인해 보고할 것.

### 5.3 회귀 불변 확인

`p2p.withdraw_match_fee_rate = 0` 이고 모든 파트너 오버라이드가 NULL인 상태에서:

- `usdtNeeded` / `usdt_amount` 가 변경 전과 **비트 단위로 동일**
- `p2p_matches.withdraw_match_fee_usdt = 0`
- `settlement_daily_fees` 에 `WITHDRAW_MATCH_*` 행이 생성되지 않음
- 기존 `BUYER_SHARE` / `WITHDRAW_BONUS` / `SYSTEM` 금액 불변

### 5.4 빌드

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

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

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

신규 매퍼 SQL의 `<script>` 내부에서 `<`, `<=`, `<>` 를 **직접 쓰지 말 것.** 컴파일은 통과하지만 기동 시점 `SAXParseException` 으로 전 서비스가 내려간다 (2026-06-11 운영 장애). `&lt;` / `&lt;=` / `!=` 또는 `<![CDATA[ ]]>` 를 사용한다.

또한 `ORDER BY` / `LIMIT` / `COUNT` 를 직접 작성하지 말 것 — `XResultInterceptor` 가 처리한다.

### 5.6 완료 기준

1. §5.1 전수 확인 결과 보고 완료
2. §5.2 양방향 DDL 정합성 이상 없음
3. §5.3 회귀 불변 확인 완료
4. §5.4 5개 모듈 컴파일 성공
5. §2.3 숫자 예시가 코드 경로에서 그대로 재현됨 (단위 테스트 또는 로컬 DB 시뮬레이션)

---

## 6. 함정 정리

| # | 함정 | 대응 |
|---|---|---|
| 1 | `usdt_amount ≠ krw/rate` 가 된다 | §5.1 전수 확인 |
| 2 | 실현 잡이 `source_partner MASTER` 에서 스윕 | `source_partner_id = 입금 파트너` 고정 (§4.6) |
| 3 | 출금 파트너 본인이 자기 쉐어를 받으면 상쇄 | `collectWithdrawShareChain` 은 본인 제외 |
| 4 | 보너스와 매칭 수수료를 같은 재원으로 오해 | 별도 재원 — 캡·클램프를 서로 걸지 않음 |
| 5 | TORQ / PARTNER 레그에 요율이 새어 들어감 | `legType == P2P` 게이트 |
| 6 | 잠금은 `RoundingMode.UP`, 확정은 `DOWN` | 각 호출부 기존 모드 유지 |
| 7 | 오버라이드 해제가 안 됨 | `update()` 사용 (`modify()` 는 null 스킵) |
| 8 | 출금 주문 소진 속도 변화 미반영 | §4.8 |

---

## 7. 배포

1. DDL v2.8 적용 — **사람이 실행** (로컬 → 운영 순)
2. 코드 구현 → 로컬 빌드 검증 → 커밋 → `git push origin main`
3. GitLab Pipelines에서 `spring:deploy-production` ▶ 수동 실행
4. 배포 후 요율은 **0 유지**. 검증 완료 후 어드민에서 파트너별로 단계 적용

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