# Guide #80 — open-api v1 호환 API 추가

> **원칙**: v1 API 스펙(endpoint, DTO)을 최대한 그대로 가져간다.
> **v1 소스 참조**: `/Users/dudgh/git/coin-payments/core-service-api/`, `widget-api-service/`, `common-lib/`
> **v2 대상**: `open-api` 모듈

---

## 현황 요약

### v2 open-api 기존 (18 endpoints)

| 경로 | 컨트롤러 | 비고 |
|------|----------|------|
| `GET /ping` | PingController | ✅ |
| `GET /widgets/api/chains, tokens, exchange-rates` | InfoController (4) | ✅ |
| `GET /widgets/api/balance, transactions` | BalanceController (2) | ✅ |
| `POST /widgets/api/deposit-address` | DepositController (1) | ✅ |
| `POST /widgets/api/withdrawal, fee, limits` | WithdrawalController (3) | ✅ |
| `GET/POST/DELETE /widgets/api/axim/*` | AximController (5) | ✅ |
| `POST /webhooks/axim/{partnerId}` | AximWebhookController (1) | ✅ |
| `POST /api/v2/webhooks/blockchain-monitor` | WebhookController (1) | ✅ |

### v1에서 가져올 API (3개 카테고리)

| 카테고리 | v1 모듈 | 엔드포인트 수 |
|----------|---------|-------------|
| **A. Partner Server API** (`/api/v1/*`) | core-service-api | 22 |
| **B. Widget 인증** (`/widgets/auth/*`) | widget-api-service | 2 |
| **C. Widget 추가** (`/widgets/api/*`, `/widgets/payment/*`) | widget-api-service | 13 |

---

## Part A. Partner Server API — v1 core-service-api 이관

> 파트너 서버가 서버-to-서버로 호출하는 API. HMAC 인증 기반.

### A-1. AuthController

**v1 경로**: `POST /api/v1/auth/authenticate`
**v2 경로**: 동일 (`POST /api/v1/auth/authenticate`)

**v1 DTO (그대로 사용)**:

```java
// open-api/dto/request/AuthRequest.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class AuthRequest {
    /** API 키 */
    private String apiKey;
    /** 타임스탬프 (Unix timestamp) */
    private String timestamp;
    /** 액세스 토큰 */
    private String accessToken;
}

// open-api/dto/response/AuthResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class AuthResponse {
    /** 인증 성공 여부 */
    private boolean success;
    /** 파트너 ID */
    private Long partnerId;
    /** 파트너 이름 */
    private String partnerName;
    /** 파트너 상태 */
    private String partnerStatus;
    /** 인증 일시 */
    private LocalDateTime authenticatedAt;
    /** 토큰 만료 일시 */
    private LocalDateTime expiresAt;
    /** 오류 메시지 */
    private String errorMessage;

    public static AuthResponse success(Partner partner, LocalDateTime expiresAt) {
        return AuthResponse.builder()
                .success(true)
                .partnerId(partner.getId())
                .partnerName(partner.getPartnerName())
                .partnerStatus(partner.getStatus().name())
                .authenticatedAt(LocalDateTime.now())
                .expiresAt(expiresAt)
                .build();
    }

    public static AuthResponse failure(String errorMessage) {
        return AuthResponse.builder()
                .success(false)
                .errorMessage(errorMessage)
                .authenticatedAt(LocalDateTime.now())
                .build();
    }
}
```

**v2 Controller**:

```java
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {

    @PostMapping("/authenticate")
    public AuthResponse authenticate(@RequestBody AuthRequest request) {
        // v2: Partner entity의 apiKey로 조회 → HMAC 검증 → 세션 토큰 발급
        // v1 로직 참조: core-service-api AuthController
    }
}
```

**v2 인증 방식**: v1은 `@ApiService` 어노테이션으로 Partner를 주입했지만, v2는 Axim의 `XBaseAccessTokenHandler` 기반 세션 토큰을 사용. `AuthController`에서 발급한 토큰을 이후 API 호출 시 `Authorization` 헤더로 전달.

---

### A-2. PartnerInfoController

**v1 경로**: `/api/v1/partner/*`
**v2 경로**: 동일

| v1 | v2 | DTO |
|---|---|---|
| `GET /api/v1/partner/chains` | 동일 | `List<ChainActivationResponse>` |
| `GET /api/v1/partner/wallets` | 동일 | `List<WalletResponse>` |
| `GET /api/v1/partner/balances` | 동일 | `List<WalletResponse>` |

**v1 DTO (그대로)**:

```java
// ChainActivationResponse.java — v1 common-lib에서 가져옴
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class ChainActivationResponse {
    /** 체인 타입 */
    private String chainType;
    /** 체인 이름 */
    private String chainName;
    /** 활성화 여부 */
    private Boolean isActive;
    /** 활성화 일시 */
    private LocalDateTime activatedAt;
}
```

```java
// WalletResponse.java — v1 common-lib
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WalletResponse {
    /** 지갑 ID */
    private Long id;
    /** 지갑 주소 */
    private String address;
    /** 체인 타입 */
    private String chainType;
    /** 지갑 타입 */
    private String walletType;
    /** 지갑 상태 */
    private String status;
    /** 잔액 (토큰별) */
    private Map<String, Object> balances;
    /** 생성일 */
    private LocalDateTime createdAt;
}
```

**v2 구현 핵심**:
- `ChainCurrencyResolver.toChainType(networkId)`를 사용하여 v2 networkId → v1 chainType 변환
- `walletAddressRepository`, `walletBalanceRepository`에서 데이터 조회 후 v1 DTO로 변환

---

### A-3. UserController

**v1 경로**: `/api/v1/users/*`
**v2 경로**: 동일

| v1 | v2 | DTO |
|---|---|---|
| `POST /api/v1/users/deposit-wallet` | 동일 | Req: `CreateDepositWalletRequest` → Res: `DepositWalletResponse` |
| `POST /api/v1/users/withdrawal` | 동일 | Req: `UserWithdrawalRequest` → Res: `WithdrawalResponseV1` |
| `GET /api/v1/users/{id}/transactions` | 동일 | `List<TransactionHistoryResponse>` |
| `GET /api/v1/users/{id}/deposits/pending` | 동일 | `List<TransactionHistoryResponse>` |
| `GET /api/v1/users/{id}/axim/connect-info` | 동일 | `AximConnectionInfoResponse` |

**v1 DTO (그대로)**:

```java
// CreateDepositWalletRequest.java (v1 CreateWalletRequest 기반, 이름 변경)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class CreateDepositWalletRequest {
    /** 파트너 사용자 ID */
    @NotBlank
    private String partnerUserId;
    /** 체인 타입 */
    @NotNull
    private String chainType;
    /** 통화 타입 */
    @NotNull
    private String currencyType;
}
```

```java
// DepositWalletResponse.java (v1 WalletInfo 기반, 간소화)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class DepositWalletResponse {
    /** 지갑 ID */
    private Long id;
    /** 지갑 주소 */
    private String address;
    /** 체인 타입 */
    private String chainType;
    /** 통화 타입 */
    private String currencyType;
    /** 지갑 상태 */
    private String status;
    /** 생성일 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
}
```

```java
// UserWithdrawalRequest.java (v1 WithdrawalRequest 기반)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class UserWithdrawalRequest {
    /** 파트너 사용자 ID */
    private String partnerUserId;
    /** 출금 금액 */
    @NotNull @DecimalMin("0.000001")
    private BigDecimal amount;
    /** 통화 타입 */
    @NotNull
    private String currencyType;
    /** 체인 타입 */
    @NotNull
    private String chainType;
    /** 출금 대상 주소 */
    @NotBlank
    private String toAddress;
    /** 금액 단위 (TOKEN/KRW) */
    private String amountUnit; // 기본 TOKEN
    /** KRW 금액 (amountUnit=KRW일 때) */
    private BigDecimal krwAmount;
}
```

```java
// WithdrawalResponseV1.java (v1 그대로)
@Getter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WithdrawalResponseV1 {
    /** 트랜잭션 ID */
    private Long transactionId;
    /** 트랜잭션 해시 */
    private String transactionHash;
    /** 트랜잭션 상태 */
    private String status;
    /** 출금 토큰 수량 */
    private BigDecimal tokenAmount;
    /** 출금 KRW 금액 */
    private BigDecimal krwAmount;
    /** 토큰 KRW 가격 */
    private BigDecimal tokenKrwPrice;
    /** 생성 일시 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createdAt;
}
```

```java
// TransactionHistoryResponse.java (v1 그대로)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class TransactionHistoryResponse {
    /** 거래 ID */
    private Long transactionId;
    /** 파트너 거래 ID */
    private String partnerTransactionId;
    /** 거래 타입 (DEPOSIT/WITHDRAWAL) */
    private String transactionType;
    /** 체인 타입 */
    private String chainType;
    /** 통화 타입 */
    private String currencyType;
    /** 출발 주소 */
    private String fromAddress;
    /** 도착 주소 */
    private String toAddress;
    /** 거래 금액 */
    private BigDecimal amount;
    /** KRW 가격 */
    private BigDecimal priceKrw;
    /** USD 가격 */
    private BigDecimal priceUsd;
    /** TX 해시 */
    private String txHash;
    /** 블록 번호 */
    private Long blockNumber;
    /** 상태 */
    private String status;
    /** 파트너 사용자 ID */
    private String partnerUserId;
    /** 지갑 주소 */
    private String walletAddress;
    /** 생성일 */
    private LocalDateTime createdAt;
    /** 확인일 */
    private LocalDateTime confirmedAt;
}
```

**v2 구현 핵심**:
- `ChainCurrencyResolver`로 `chainType/currencyType` ↔ `networkId/currencyId` 변환
- `core.WalletService.createHotWallet()`로 HD 파생 위임
- `core.WithdrawalService.requestWithdrawal()`로 출금 위임
- deposits/withdrawals 테이블에서 partnerUserId로 조회 후 v1 DTO 변환

---

### A-4. TransactionController (입금 확정)

**v1 경로**: `/api/v1/transactions/*`
**v2 경로**: 동일

| v1 | v2 | 비고 |
|---|---|---|
| `GET /deposits/unconfirmed` | 동일 | CONFIRMED 상태 입금 목록 (아직 SETTLED 아닌 것) |
| `POST /deposits/{transactionId}/confirm` | 동일 | 입금 확정 (CONFIRMED → SETTLED) |
| `POST /deposits/confirm-batch` | 동일 | 배치 확정 |

> **v2 입금 확정 개념**: v1에서는 CONFIRMED(블록체인 확인) → SETTLED(파트너 확정)의 2단계. 파트너가 confirm API를 호출해야 정산에 반영. v2에서도 이 흐름을 유지.

**v1 DTO**:

```java
// ConfirmDepositsRequest.java (v1 TransactionController 내부 클래스)
@Getter @Setter
@NoArgsConstructor @AllArgsConstructor
public class ConfirmDepositsRequest {
    /** 확정할 트랜잭션 ID 목록 */
    private List<Long> transactionIds;
}
```

**v2 구현 핵심**:
- `depositRepository.findByPartnerIdAndStatus(partnerId, DepositStatus.CONFIRMED)` → 미확정 목록
- confirm: `deposit.setStatus(SETTLED)` + `settlementService.processDeposit(deposit)` → 정산 반영

---

### A-5. CurrencyPriceController (시세 API)

**v1 경로**: `/api/v1/currency-prices/*`
**v2 경로**: 동일

| v1 | v2 | 비고 |
|---|---|---|
| `GET /` | 동일 | 전체 토큰 시세 |
| `GET /{currencyType}/krw` | 동일 | 특정 토큰 KRW |
| `GET /{currencyType}/usd` | 동일 | 특정 토큰 USD |
| `GET /usdt-krw` | 동일 | USDT/KRW 시세 |
| `GET /{currencyType}` | 동일 | 특정 토큰 (기본 KRW) |
| `GET /convert/usdt-to-krw` | 동일 | USDT→KRW 변환 |
| `GET /convert/krw-to-usdt` | 동일 | KRW→USDT 변환 |
| `GET /convert/{currencyType}/to-krw` | 동일 | 토큰→KRW |
| `GET /convert/{currencyType}/to-usd` | 동일 | 토큰→USD |

**v1 DTO**:

```java
// CurrencyPriceResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class CurrencyPriceResponse {
    /** 통화 타입 */
    private String currencyType;
    /** KRW 가격 */
    private BigDecimal priceKrw;
    /** USD 가격 */
    private BigDecimal priceUsd;
    /** 마지막 업데이트 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime lastUpdated;
}

// CurrencyConversionResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class CurrencyConversionResponse {
    /** 원본 통화 */
    private String fromCurrency;
    /** 변환 통화 */
    private String toCurrency;
    /** 원본 금액 */
    private BigDecimal fromAmount;
    /** 변환 금액 */
    private BigDecimal toAmount;
    /** 적용 환율 */
    private BigDecimal exchangeRate;
    /** 변환 시각 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime convertedAt;
}
```

**v2 구현 핵심**:
- `core.PriceService`에서 시세 조회 (빗썸 API)
- `price_snapshots` 테이블에서 캐싱된 시세 사용

---

### A-6. ChainController (지원 체인 — Public)

**v1 경로**: `GET /api/v1/chains`
**v2 경로**: 동일

**인증 불필요** (public endpoint)

```java
// SupportedChainResponse.java (v1 그대로)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class SupportedChainResponse {
    /** 체인 타입 */
    private String chainType;
    /** 체인 이름 */
    private String name;
    /** 체인 심볼 */
    private String symbol;
    /** 지원 토큰 목록 */
    private List<SupportedTokenInfo> tokens;

    @Getter @Setter @Builder
    @NoArgsConstructor @AllArgsConstructor
    public static class SupportedTokenInfo {
        /** 통화 타입 */
        private String currencyType;
        /** 심볼 */
        private String symbol;
        /** 컨트랙트 주소 */
        private String contractAddress;
        /** 소수점 */
        private Integer decimals;
    }
}
```

---

### A-7. CallbackTestController

**v1 경로**: `POST /api/v1/test/callback`
**v2 경로**: 동일

```java
// CallbackTestRequest.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class CallbackTestRequest {
    /** 콜백 URL */
    @NotBlank
    private String callbackUrl;
    /** 테스트 타입 (DEPOSIT/WITHDRAWAL) */
    private String type;
}

// CallbackTestResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class CallbackTestResponse {
    /** 성공 여부 */
    private boolean success;
    /** 응답 코드 */
    private Integer responseCode;
    /** 응답 메시지 */
    private String responseMessage;
    /** 발송 시각 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime sentAt;
}
```

---

## Part B. Widget 인증

### B-1. WidgetAuthController

**v1 경로**: `/widgets/auth/*`
**v2 경로**: 동일

| v1 | v2 |
|---|---|
| `POST /widgets/auth/token` | 동일 |
| `POST /widgets/auth/refresh` | 동일 |

**v1 DTO**:

```java
// WidgetTokenRequest.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WidgetTokenRequest {
    /** 파트너 사용자 ID */
    private String partnerUserId;
    /** 권한 목록 */
    private Set<String> permissions;
}

// WidgetTokenResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WidgetTokenResponse {
    /** 액세스 토큰 */
    private String accessToken;
    /** 리프레시 토큰 */
    private String refreshToken;
    /** 토큰 타입 */
    private String tokenType;
    /** 만료 시간 (초) */
    private Long expiresIn;
    /** 권한 */
    private Set<String> permissions;
    /** 스코프 */
    private String scope;
}
```

**v2 구현**:
- v2는 Axim `XBaseAccessTokenHandler` 기반. `POST /widgets/auth/token`에서 HMAC 서명 검증 후 세션 토큰 발급.
- 기존 v2 `OpenApiSessionData`와 통합 가능.

---

## Part C. Widget 추가 API

### C-1. Widget Config

**v1 경로**: `GET /widgets/api/widget-config`
**v2 경로**: 동일

```java
// WidgetConfigResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WidgetConfigResponse {
    /** 프리셋 금액 */
    private BigDecimal presetAmount;
    /** 최소 금액 */
    private BigDecimal minAmount;
    /** 최대 금액 */
    private BigDecimal maxAmount;
    /** 일일 한도 */
    private BigDecimal dailyLimit;
    /** 잔액 표시 여부 */
    private Boolean showBalance;
    /** 잔액 소스 */
    private String balanceSource;
    /** 파트너 환율 사용 */
    private Boolean usePartnerExchangeRate;
    /** 금액 수정 허용 */
    private Boolean allowAmountEdit;
}
```

---

### C-2. Deposit Reservations (입금 예약)

**v1 경로**: `/widgets/api/deposit-reservations`
**v2 경로**: 동일

| v1 | v2 |
|---|---|
| `POST /deposit-reservations` | 동일 — 예약 생성 |
| `GET /deposit-reservations` | 동일 — 조회 |
| `PUT /deposit-reservations` | 동일 — 수정 |
| `DELETE /deposit-reservations` | 동일 — 취소 (물리 삭제) |
| `POST /deposit-reservations/complete` | 동일 — 완료 처리 |

**v1 DTO**:

```java
// DepositReservationRequest.java (v1 common-lib)
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class DepositReservationRequest {
    /** 사용자 ID */
    @NotBlank
    private String userId;
    /** 통화 타입 */
    @NotBlank
    private String currencyType;
    /** 체인 타입 */
    @NotBlank
    private String chainType;
    /** KRW 금액 */
    private BigDecimal amountKrw;
    /** 토큰 금액 */
    private BigDecimal amountCrypto;
    /** 환율 */
    private BigDecimal exchangeRate;
    /** 결제 링크 ID */
    private String linkId;
}

// DepositReservationResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class DepositReservationResponse {
    /** 예약 ID */
    private Long id;
    /** 파트너 ID */
    private Long partnerId;
    /** 사용자 ID */
    private String userId;
    /** 통화 타입 */
    private String currencyType;
    /** 체인 타입 */
    private String chainType;
    /** KRW 금액 */
    private BigDecimal amountKrw;
    /** 토큰 금액 */
    private BigDecimal amountCrypto;
    /** 환율 */
    private BigDecimal exchangeRate;
    /** 상태 */
    private String status;
    /** 만료 시각 */
    private LocalDateTime expiresAt;
    /** 생성일 */
    private LocalDateTime createdAt;
    /** 실제 토큰 금액 */
    private BigDecimal actualAmountCrypto;
    /** 실제 KRW 금액 */
    private BigDecimal actualAmountKrw;
    /** TX 해시 */
    private String transactionHash;
    /** 입금 완료 시각 */
    private LocalDateTime depositCompletedAt;
}

// DepositCompletionRequest.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class DepositCompletionRequest {
    /** 예약 ID */
    @NotNull
    private Long reservationId;
    /** 실제 토큰 금액 */
    @NotNull
    private BigDecimal actualAmountCrypto;
    /** 실제 KRW 금액 */
    @NotNull
    private BigDecimal actualAmountKrw;
    /** TX 해시 */
    @NotBlank
    private String transactionHash;
    /** 트랜잭션 ID */
    private Long transactionId;
}
```

**v2 구현**: Guide #76의 `deposit_reservations` 테이블 사용. `ChainCurrencyResolver`로 v1 타입 변환.

---

### C-3. Axim 추가 엔드포인트

| v1 | v2 | 비고 |
|---|---|---|
| `GET /widgets/api/axim/partner-info` | 동일 | Axim 파트너 정보 |
| `GET /widgets/api/axim/best-networks` | 동일 | 연결 지갑 최적 네트워크 |

---

### C-4. Payment Link (결제 링크 처리)

**v1 경로**: `/widgets/payment/links/*`
**v2 경로**: 동일

| v1 | v2 | 비고 |
|---|---|---|
| `GET /{linkId}` | 동일 | 결제 링크 정보 (public) |
| `POST /{linkId}/activate` | 동일 | 결제 링크 활성화 |
| `GET /{linkId}/status` | 동일 | 상태 조회 |
| `POST /{linkId}/complete` | 동일 | 완료 (내부) |
| `POST /{linkId}/expire` | 동일 | 만료 (내부) |

**v1 DTO**:

```java
// PaymentLinkInfoResponse.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PaymentLinkInfoResponse {
    /** 링크 ID */
    private String linkId;
    /** 제목 */
    private String title;
    /** 토큰 금액 */
    private BigDecimal amountCrypto;
    /** 통화 타입 */
    private String currencyType;
    /** 체인 타입 */
    private String chainType;
    /** KRW 가격 */
    private BigDecimal priceKrw;
    /** KRW 환산 금액 */
    private BigDecimal calculatedKrwAmount;
    /** 상태 */
    private String status;
    /** 사용 가능 여부 */
    private Boolean isUsable;
    /** 액세스 토큰 */
    private String accessToken;
    // Axim 관련
    /** Axim 활성화 */
    private Boolean aximEnabled;
    /** Axim 연결 여부 */
    private Boolean aximConnected;
    /** Axim 사이트 ID */
    private String aximSiteId;
}

// PaymentLinkActivateRequest.java
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class PaymentLinkActivateRequest {
    /** 사용자 식별자 */
    @NotBlank
    private String userIdentifier;
    /** IP 주소 */
    private String ipAddress;
    /** User Agent */
    private String userAgent;
}
```

---

## 구현 파일 구조

```
open-api/src/main/java/com/cryptoments/openapi/
├── controller/
│   ├── (기존) PingController.java
│   ├── (기존) InfoController.java
│   ├── (기존) BalanceController.java
│   ├── (기존) DepositController.java
│   ├── (기존) WithdrawalController.java
│   ├── (기존) AximController.java
│   ├── (기존) AximWebhookController.java
│   ├── (신규) AuthController.java               ← A-1
│   ├── (신규) PartnerInfoController.java         ← A-2
│   ├── (신규) UserController.java                ← A-3
│   ├── (신규) TransactionController.java         ← A-4
│   ├── (신규) CurrencyPriceController.java       ← A-5
│   ├── (신규) ChainController.java               ← A-6
│   ├── (신규) CallbackTestController.java        ← A-7
│   ├── (신규) WidgetAuthController.java          ← B-1
│   ├── (신규) WidgetConfigController.java        ← C-1 (또는 InfoController에 추가)
│   ├── (신규) DepositReservationController.java  ← C-2
│   └── (신규) PaymentLinkController.java         ← C-4
├── dto/
│   ├── request/
│   │   ├── AuthRequest.java
│   │   ├── CreateDepositWalletRequest.java
│   │   ├── UserWithdrawalRequest.java
│   │   ├── ConfirmDepositsRequest.java
│   │   ├── CallbackTestRequest.java
│   │   ├── WidgetTokenRequest.java
│   │   ├── DepositReservationRequest.java
│   │   ├── DepositCompletionRequest.java
│   │   └── PaymentLinkActivateRequest.java
│   └── response/
│       ├── AuthResponse.java
│       ├── ChainActivationResponse.java
│       ├── WalletResponse.java (v1 partner용)
│       ├── DepositWalletResponse.java
│       ├── WithdrawalResponseV1.java
│       ├── TransactionHistoryResponse.java
│       ├── CurrencyPriceResponse.java
│       ├── CurrencyConversionResponse.java
│       ├── SupportedChainResponse.java
│       ├── CallbackTestResponse.java
│       ├── WidgetTokenResponse.java
│       ├── WidgetConfigResponse.java
│       ├── DepositReservationResponse.java
│       └── PaymentLinkInfoResponse.java
└── service/
    ├── (기존) WebhookPayloadBuilder.java
    ├── (기존) ChainCurrencyResolver.java
    ├── (신규) OpenApiAuthService.java
    ├── (신규) OpenApiPartnerService.java
    ├── (신규) OpenApiUserService.java
    ├── (신규) OpenApiTransactionService.java
    ├── (신규) OpenApiPriceService.java
    ├── (신규) OpenApiCallbackTestService.java
    ├── (신규) DepositReservationService.java
    └── (신규) PaymentLinkWidgetService.java
```

---

## 구현 우선순위

| 순서 | 항목 | 엔드포인트 수 | 난이도 | 비고 |
|------|------|-------------|--------|------|
| 1 | **A-1** Auth | 1 | ⭐⭐ | 다른 API의 전제 조건 |
| 2 | **A-6** Chains (Public) | 1 | ⭐ | 인증 불필요 |
| 3 | **A-5** Currency Prices | 9 | ⭐⭐ | core.PriceService 활용 |
| 4 | **A-2** Partner Info | 3 | ⭐ | 조회만 |
| 5 | **A-3** User (deposit-wallet, withdrawal) | 5 | ⭐⭐⭐ | core 서비스 위임 |
| 6 | **A-4** Transaction (입금 확정) | 3 | ⭐⭐ | CONFIRMED→SETTLED |
| 7 | **A-7** Callback Test | 1 | ⭐ | WebhookPayloadBuilder 활용 |
| 8 | **B-1** Widget Auth | 2 | ⭐⭐ | 토큰 발급/갱신 |
| 9 | **C-1** Widget Config | 1 | ⭐ | 설정 조회 |
| 10 | **C-2** Deposit Reservations | 5 | ⭐⭐ | Guide #76 연동 |
| 11 | **C-3** Axim 추가 | 2 | ⭐ | 기존 AximController 확장 |
| 12 | **C-4** Payment Links | 5 | ⭐⭐⭐ | widget 결제 페이지 핵심 |

**총 신규**: 38 endpoints, ~22 DTO, ~8 Service

---

## 핵심 주의사항

1. **v1 호환 필수**: `chainType`/`currencyType` 문자열 기반 — `ChainCurrencyResolver`로 v2 ID 변환
2. **인증 분리**: `/api/v1/*`는 HMAC 인증, `/widgets/*`는 세션 토큰 인증
3. **DTO 패키지**: v1 enum (`ChainType`, `CurrencyType`)은 v2에서 String으로 처리 (v2는 DB ID 기반)
4. **core 위임**: 비즈니스 로직은 core 모듈 서비스에 위임, open-api는 v1↔v2 변환 레이어
