# Deposit / AximPayment price_krw·price_usd 누락 수정 지침

> **작성**: 2026-04-02 (v2 — 파트너 환율 정책 반영)
> **심각도**: HIGH — 모든 거래의 가격 정보가 NULL로 기록되어 정산·통계 산출 불가
> **영향**: deposits 11건 전부, axim_payments 4건 전부 price_krw/price_usd = NULL
> **운영 DB 보정**: ✅ 완료 (2026-04-02 currency_price_history 기반 보정)

---

## 1. 문제 현황

### 운영 DB 확인 결과

```
deposits (11건 전부):     price_krw = NULL, price_usd = NULL
axim_payments (4건 전부): price_krw = NULL, price_usd = NULL
```

### 근본 원인

| 서비스 | 레코드 생성 위치 | PriceService 주입 | price 설정 |
|--------|-----------------|-------------------|-----------|
| **DepositService** | `onTxDetected()` line 202 | ❌ 미주입 | ❌ builder에 없음 |
| **WebhookProcessingService** | `processDeposit()` line 262 | ❌ 미주입 | ❌ builder에 없음 |
| **AximService** | `requestPayment()` line 80 | ❌ 미주입 | ❌ builder에 없음 |

**PriceService는 존재하고 `currency_prices` 테이블에 시세 데이터도 정상 수집 중이지만, 거래 생성 서비스에서 주입·사용하지 않고 있음.**

---

## 2. 파트너 환율 정책

### 정책 구조

```
partner_exchange_rate_policies 테이블:
  rate_type = 'INHERIT'  → 시스템 실시간 환율 (currency_prices의 USDT/KRW)
  rate_type = 'FIXED'    → 파트너 고정 환율 (fixed_rate 값)
```

**현재 운영 데이터:**

| partner_id | rate_type | fixed_rate | 실제 적용 환율 |
|---|---|---|---|
| 1 | FIXED | 1500 | 1500 KRW/USD |
| 나머지 | (정책 없음) | — | 시스템 환율 ~1511 KRW/USD |

### Price 계산 공식

```
price_usd = currency_prices.price_usd  (시장가, 파트너 무관)
price_krw = price_usd × 파트너 USD/KRW 환율  (파트너 정책 적용)
```

**예시:**
- USDT 입금, FIXED 1500: price_usd=1.0, price_krw=1500
- USDT 입금, INHERIT (시스템 1511): price_usd=1.0, price_krw=1511
- XRP 입금, FIXED 1500: price_usd=0.315, price_krw=472.5 (0.315 × 1500)

### PriceService 기존 메서드 활용

```java
// 이미 존재하는 메서드:
priceService.getCurrentPrice(currencyId)   // → CurrencyPrice (price_usd, price_krw)
priceService.getExchangeRate(partnerId)    // → BigDecimal (FIXED면 고정값, INHERIT면 시스템)
```

---

## 3. 수정 대상 (3개 파일)

### 3-1. `core/src/.../deposit/DepositService.java` — 입금 감지 시 price 설정

**① PriceService 주입 추가**

```java
// import 추가
import com.cryptoments.core.price.PriceService;
import com.cryptoments.common.entity.CurrencyPrice;

// 필드 추가 (line 58~68 사이)
private final PriceService priceService;

// 생성자에 파라미터 추가
public DepositService(WalletService walletService,
                      SettlementService settlementService,
                      NotificationService notificationService,
                      PriceService priceService,  // ← 추가
                      DepositRepository depositRepository,
                      // ... 나머지 동일
                     ) {
    // ...
    this.priceService = priceService;
    // ...
}
```

**② `onTxDetected()` builder에 price 추가 (line 202~218)**

builder 앞에 price 계산 로직 추가:

```java
// ── 시세 조회 (파트너 환율 정책 적용) ──
BigDecimal priceKrw = null;
BigDecimal priceUsd = null;
try {
    CurrencyPrice currentPrice = priceService.getCurrentPrice(currencyId);
    priceUsd = currentPrice.getPriceUsd();

    // 파트너 환율 정책: FIXED면 고정값, INHERIT면 시스템 실시간
    if (partnerId != null) {
        BigDecimal usdKrwRate = priceService.getExchangeRate(partnerId);
        priceKrw = priceUsd.multiply(usdKrwRate);
    } else {
        priceKrw = currentPrice.getPriceKrw();  // 파트너 없으면 시스템 시세
    }
} catch (Exception e) {
    log.warn("입금 시세 조회 실패: currencyId={}, 시세 없이 진행", currencyId);
}

Deposit deposit = Deposit.builder()
        .depositCode(depositCode)
        .partnerId(partnerId)
        .partnerUserId(partnerUserId)
        .networkId(networkId)
        .currencyId(currencyId)
        .depositType(depositType)
        .depositMethod(depositMethod)
        .amount(amount)
        .feeAmount(feeAmount)
        .priceKrw(priceKrw)       // ← 추가
        .priceUsd(priceUsd)       // ← 추가
        .txHash(txHash)
        .fromAddress(fromAddress)
        .toAddress(toAddress)
        .blockNumber(blockNumber)
        .walletAddressId(walletAddressId)
        .status(DepositStatus.DETECTED)
        .build();
```

> **주의**: 시세 조회 실패해도 입금 처리는 계속 진행해야 함 (try-catch 필수). 시세 NULL은 나중에 배치로 보정 가능하지만, 입금 자체가 실패하면 안 됨.

---

### 3-2. `open-api/src/.../webhook/service/WebhookProcessingService.java` — Webhook 입금 처리 시 price 설정

이 파일도 Deposit을 직접 생성하는 경로가 있음. DepositService.onTxDetected()를 호출하는 방식이면 중복 수정 불필요하지만, **직접 builder를 사용하는 경우** 동일하게 수정 필요.

**확인 방법**: `Deposit.builder()` 호출이 있는지 검색.

- 있으면 → 3-1과 동일하게 PriceService 주입 + price 계산 로직 + builder에 priceKrw/priceUsd 추가
- DepositService.onTxDetected()를 호출하는 방식이면 → 수정 불필요 (3-1에서 이미 처리)

---

### 3-3. `core/src/.../axim/AximService.java` — Axim 결제 요청 시 price 설정

**① PriceService 주입 추가**

```java
// import 추가
import com.cryptoments.core.price.PriceService;
import com.cryptoments.common.entity.CurrencyPrice;

// 필드 추가
private final PriceService priceService;

// 생성자에 파라미터 추가
public AximService(AximPaymentRepository aximPaymentRepository,
                   ExternalWalletRepository externalWalletRepository,
                   PartnerAximSettingsRepository aximSettingsRepository,
                   PriceService priceService,  // ← 추가
                   ObjectMapper objectMapper) {
    // ...
    this.priceService = priceService;
    // ...
}
```

**② `requestPayment()` builder에 price 추가 (line 80~91)**

builder 앞에 추가:

```java
// ── 시세 조회 (파트너 환율 정책 적용) ──
BigDecimal priceKrw = null;
BigDecimal priceUsd = null;
try {
    CurrencyPrice currentPrice = priceService.getCurrentPrice(currencyId);
    priceUsd = currentPrice.getPriceUsd();

    BigDecimal usdKrwRate = priceService.getExchangeRate(partnerId);
    priceKrw = priceUsd.multiply(usdKrwRate);
} catch (Exception e) {
    log.warn("Axim 결제 시세 조회 실패: currencyId={}", currencyId);
}

AximPayment payment = AximPayment.builder()
        .paymentCode(paymentCode)
        .partnerId(partnerId)
        .partnerUserId(partnerUserId)
        .externalWalletId(externalWalletId)
        .partnerReference(partnerReference)
        .amount(amount)
        .currencyId(currencyId)
        .networkId(networkId)
        .priceKrw(priceKrw)       // ← 추가
        .priceUsd(priceUsd)       // ← 추가
        .status(AximPaymentStatus.REQUESTED)
        .expiredAt(LocalDateTime.now().plusMinutes(PAYMENT_EXPIRE_MINUTES))
        .build();
```

---

## 3. 기존 데이터 보정 (운영 DB)

기존 NULL 데이터를 보정하려면, 각 거래의 `created_at` 시점에 가장 가까운 `currency_price_history` 스냅샷으로 채워야 함.

```sql
-- 1. deposits price 보정
UPDATE deposits d
JOIN (
    SELECT d2.id as deposit_id,
           (SELECT cph.price_krw
            FROM currency_price_history cph
            WHERE cph.currency_id = d2.currency_id
              AND cph.snapshot_at <= d2.created_at
            ORDER BY cph.snapshot_at DESC LIMIT 1) as hist_price_krw,
           (SELECT cph.price_usd
            FROM currency_price_history cph
            WHERE cph.currency_id = d2.currency_id
              AND cph.snapshot_at <= d2.created_at
            ORDER BY cph.snapshot_at DESC LIMIT 1) as hist_price_usd
    FROM deposits d2
    WHERE d2.price_krw IS NULL
) sub ON d.id = sub.deposit_id
SET d.price_krw = sub.hist_price_krw,
    d.price_usd = sub.hist_price_usd;

-- 2. axim_payments price 보정
UPDATE axim_payments ap
JOIN (
    SELECT ap2.id as payment_id,
           (SELECT cph.price_krw
            FROM currency_price_history cph
            WHERE cph.currency_id = ap2.currency_id
              AND cph.snapshot_at <= ap2.created_at
            ORDER BY cph.snapshot_at DESC LIMIT 1) as hist_price_krw,
           (SELECT cph.price_usd
            FROM currency_price_history cph
            WHERE cph.currency_id = ap2.currency_id
              AND cph.snapshot_at <= ap2.created_at
            ORDER BY cph.snapshot_at DESC LIMIT 1) as hist_price_usd
    FROM axim_payments ap2
    WHERE ap2.price_krw IS NULL
) sub ON ap.id = sub.payment_id
SET ap.price_krw = sub.hist_price_krw,
    ap.price_usd = sub.hist_price_usd;
```

> **주의**: `currency_price_history`에 해당 시점 데이터가 없으면 NULL 유지. 수동으로 현재 시세를 넣을 수 있지만 스테이블코인(USDT)이므로 큰 차이 없음.

---

## 4. 검증 쿼리

수정 배포 후 신규 거래에 price가 정상 기록되는지 확인:

```sql
-- 배포 후 신규 deposit price 확인
SELECT id, deposit_code, amount, price_krw, price_usd, created_at
FROM deposits
ORDER BY id DESC LIMIT 5;

-- 배포 후 신규 axim_payment price 확인
SELECT id, amount, price_krw, price_usd, created_at
FROM axim_payments
ORDER BY id DESC LIMIT 5;
```

---

## 5. 수정 체크리스트

| # | 작업 | 파일 | 상태 |
|---|------|------|------|
| 1 | DepositService에 PriceService 주입 + onTxDetected() builder에 price 추가 | `core/.../deposit/DepositService.java` | ⬜ |
| 2 | WebhookProcessingService에 직접 Deposit 생성하는 경우 동일 수정 | `open-api/.../webhook/WebhookProcessingService.java` | ⬜ (확인 필요) |
| 3 | AximService에 PriceService 주입 + requestPayment() builder에 price 추가 | `core/.../axim/AximService.java` | ⬜ |
| 4 | open-api 빌드 확인 | `./gradlew :open-api:compileJava` | ⬜ |
| 5 | core 빌드 확인 | `./gradlew :core:compileJava` | ⬜ |
| 6 | 배포 후 운영 DB 기존 데이터 보정 SQL 실행 | 운영 DB (bastion 경유) | ⬜ |
| 7 | 신규 거래에 price 정상 기록 확인 | 운영 DB 검증 쿼리 | ⬜ |
