# Axim Auto-Activation + deposit_address_pool 중복 방어 가이드

## 1. 문제

### 1-A. Axim 설정 저장 시 실제 연동 미실행

partner-ui에서 `PUT /api/partner/integration/axim`으로 `isEnabled=true` 저장 시,
DB 플래그만 변경되고 실제 Axim API 연동(webhook 등록, 지갑 등록)이 실행되지 않음.

`POST /axim/activate` 엔드포인트가 존재하지만 UI에서 호출하지 않음.

### 1-B. deposit_address_pool 중복 INSERT

```
Duplicate entry '7-2-0xd441746...' for key 'deposit_address_pool.uk_partner_network_address'
```

`createPoolWallet()` 중복 호출 시 방어 로직 없음.

---

## 2. 수정 대상

| 파일 | 모듈 | 변경 내용 |
|------|------|----------|
| `PartnerIntegrationService.java` | partner-api | `updateAximSettings()`에 자동 activate/deactivate 로직 |
| `WalletService.java` | core | `createPoolWallet()`에 중복 방어 |

---

## 3. PartnerIntegrationService — updateAximSettings() 수정

### 핵심 변경

`isEnabled`가 `false→true`로 변경되면 `activateAxim()` 로직 실행,
`true→false`로 변경되면 `deactivateAxim()` 로직 실행.

### Before (현재)

```java
public PartnerAximSettings updateAximSettings(Long partnerId, UpdateAximSettingsRequest request) {
    // ... DB에 apiKey/secret/siteId/isEnabled 저장만
    // isEnabled=true 해도 Axim API 호출 없음
}
```

### After

```java
public PartnerAximSettings updateAximSettings(Long partnerId, UpdateAximSettingsRequest request) {
    log.info("[Axim 설정 저장] partnerId={}, apiKey={}, secretPresent={}, siteId={}, isEnabled={}",
            partnerId, request.getApiKey(),
            (request.getApiSecret() != null && !request.getApiSecret().isBlank()),
            request.getSiteId(), request.getIsEnabled());

    PartnerAximSettings settings = aximSettingsRepository.findByPartnerId(partnerId);

    if (settings == null) {
        // ── INSERT (신규) ──
        log.info("[Axim 설정 저장] 기존 설정 없음 → INSERT");
        settings = PartnerAximSettings.builder()
                .partnerId(partnerId)
                .apiKey(request.getApiKey())
                .apiSecretEnc(request.getApiSecret())
                .siteId(request.getSiteId())
                .isEnabled(false)  // ★ 신규는 항상 false로 INSERT (활성화는 별도)
                .build();
        Long id = aximSettingsRepository.save(settings);
        settings.setId(id);
        log.info("[Axim 설정 저장] INSERT 완료: id={}", id);

        // 신규 INSERT + isEnabled=true 요청 → 즉시 활성화
        if (Boolean.TRUE.equals(request.getIsEnabled())) {
            // apiKey/secret 최신값 반영 후 활성화
            return activateAxim(partnerId);
        }
        return settings;
    }

    // ── UPDATE (기존) ──
    log.info("[Axim 설정 저장] 기존 설정 존재: id={}, 현재 apiKey={}, isEnabled={}",
            settings.getId(), settings.getApiKey(), settings.getIsEnabled());

    // apiKey/secret/siteId 업데이트 (isEnabled 제외 — 별도 처리)
    if (request.getApiKey() != null) settings.setApiKey(request.getApiKey());
    if (request.getApiSecret() != null) settings.setApiSecretEnc(request.getApiSecret());
    if (request.getSiteId() != null) settings.setSiteId(request.getSiteId());
    aximSettingsRepository.modify(settings);
    log.info("[Axim 설정 저장] UPDATE 완료 (credentials)");

    // ★ isEnabled 전환 감지 → 자동 activate/deactivate
    if (request.getIsEnabled() != null) {
        boolean wasEnabled = Boolean.TRUE.equals(settings.getIsEnabled());
        boolean wantEnabled = request.getIsEnabled();

        if (!wasEnabled && wantEnabled) {
            // false → true: 활성화 (API 검증 + webhook + 지갑 등록)
            log.info("[Axim 설정 저장] isEnabled 전환: false → true → activateAxim 호출");
            return activateAxim(partnerId);
        } else if (wasEnabled && !wantEnabled) {
            // true → false: 비활성화
            log.info("[Axim 설정 저장] isEnabled 전환: true → false → deactivateAxim 호출");
            return deactivateAxim(partnerId);
        }
        // 동일 값이면 무시 (true→true, false→false)
    }

    return settings;
}
```

### 주요 변경 포인트

1. **신규 INSERT 시 `isEnabled`는 항상 `false`** — 활성화는 `activateAxim()` 통해서만
2. **`isEnabled` 전환 감지**: `false→true` = `activateAxim()`, `true→false` = `deactivateAxim()`
3. **credentials 먼저 UPDATE → 그다음 activate** — `activateAxim()`이 DB에서 최신 apiKey/secret 읽음
4. **activate 실패 시 예외 전파** — UI에서 에러 메시지 표시 가능

---

## 4. WalletService — createPoolWallet() 중복 방어

### Before (현재)

```java
public WalletAddress createPoolWallet(Long partnerId, Long networkId, List<Long> currencyIds) {
    WalletDeriveResponse derived = deriveWallet(networkId, "POOL", partnerId, null);
    // ...
    for (Long currencyId : targetCurrencies) {
        depositAddressPoolRepository.save(...);  // 중복 시 예외 발생
    }
}
```

### After

```java
public WalletAddress createPoolWallet(Long partnerId, Long networkId, List<Long> currencyIds) {
    // ★ 중복 방어: 이미 해당 partner+network에 POOL 주소가 있으면 기존 반환
    List<DepositAddressPool> existing = depositAddressPoolRepository
            .findByPartnerIdAndNetworkId(partnerId, networkId);
    if (existing != null && !existing.isEmpty()) {
        Long existingWalletId = existing.get(0).getWalletAddressId();
        WalletAddress existingWallet = walletAddressRepository.findOne(existingWalletId);
        log.info("POOL wallet already exists: partnerId={}, networkId={}, address={}, skip creation",
                partnerId, networkId, existingWallet != null ? existingWallet.getAddress() : "?");
        return existingWallet;
    }

    WalletDeriveResponse derived = deriveWallet(networkId, "POOL", partnerId, null);
    WalletAddress poolWallet = walletAddressRepository.findOne(derived.getWalletAddressId());
    if (poolWallet == null) {
        throw new NotFoundException(ErrorCodes.WALLET_ADDRESS_NOT_FOUND);
    }

    initWalletBalances(poolWallet.getId(), networkId);
    int approvals = registerTokenApprovals(poolWallet.getId(), networkId);

    List<Long> targetCurrencies = (currencyIds != null && !currencyIds.isEmpty())
            ? currencyIds
            : getActiveCurrencyIds(networkId);

    for (Long currencyId : targetCurrencies) {
        depositAddressPoolRepository.save(DepositAddressPool.builder()
                .partnerId(partnerId)
                .networkId(networkId)
                .currencyId(currencyId)
                .walletAddressId(poolWallet.getId())
                .address(poolWallet.getAddress())
                .decimalDigits(4)
                .isActive(true)
                .build());
    }

    log.info("POOL wallet created: partnerId={}, networkId={}, address={}, currencies={}, approvals={}",
            partnerId, networkId, poolWallet.getAddress(), targetCurrencies.size(), approvals);
    return poolWallet;
}
```

### DepositAddressPoolRepository 메서드 확인

`findByPartnerIdAndNetworkId(Long partnerId, Long networkId)` — 이미 존재하는 메서드인지 확인 필요.
없으면 Repository에 추가:

```java
@XRepository
public interface DepositAddressPoolRepository extends IXRepository<Long, DepositAddressPool> {
    // ... 기존 메서드 ...
    List<DepositAddressPool> findByPartnerIdAndNetworkId(Long partnerId, Long networkId);  // ★ 추가
}
```

---

## 5. 체크리스트

### Axim 자동 활성화
- [ ] `updateAximSettings()` — 신규 INSERT 시 `isEnabled=false` 고정
- [ ] `updateAximSettings()` — credentials UPDATE 후 `isEnabled` 전환 감지
- [ ] `isEnabled: false→true` → `activateAxim()` 자동 호출
- [ ] `isEnabled: true→false` → `deactivateAxim()` 자동 호출
- [ ] activate 실패 시 에러 응답 확인 (409 ConflictException)

### deposit_address_pool 중복 방어
- [ ] `createPoolWallet()` — 기존 pool 존재 시 early return
- [ ] `DepositAddressPoolRepository` — `findByPartnerIdAndNetworkId()` 존재 확인 (없으면 추가)
- [ ] 빌드 확인: `./gradlew :core:compileJava :partner-api:compileJava`

### 테스트
- [ ] partner-ui에서 Axim 설정 저장 (apiKey + secret) → DB 저장만
- [ ] partner-ui에서 활성화 토글 ON → 로그에 `activateAxim` 5단계 출력 확인
- [ ] 같은 partner+network에 `createPoolWallet()` 2회 호출 → 두 번째는 skip 확인
