# 파트너 콘솔 Telegram 연결 해지(Disconnect) 구현 지침

> 작성: 2026-06-22 · 대상: partner-api (Spring Boot) + partner-ui (Vue 3)
> 정책 확정: **완전 해지 = `partner_telegram_configs` 행 삭제** (소프트 해지 아님)

## 1. 배경 / 현재 상태

현재 Telegram 탭에는 **연결 해지 기능이 없다.**

| 기능 | 동작 | 한계 |
|------|------|------|
| `알림 수신` ON/OFF 토글 | `PUT /telegram` 으로 `is_active` 만 변경 | chat_id 가 DB에 그대로 남음. "일시 중단"일 뿐 해지가 아님 |
| 테스트 전송 | `POST /telegram/test` | — |

요구사항: 연결된 상태에서 **"연결 해지"** 버튼으로 연동을 완전히 끊고, UI는 다시 QR/딥링크 연결 화면으로 복귀해야 한다.

해지 방식은 **완전 삭제**로 확정됨 — `partner_telegram_configs` 의 해당 파트너 행을 삭제한다. 다시 연결하면 봇 `/start` 핸들러가 행을 새로 upsert 하므로 별도 복구 로직이 필요 없다.

> 참고: 폰페이 비활성화(`deactivatePhonepay`)는 행을 유지하되 `apiKey`/`callbackUrl`을 null 처리한다. Telegram은 정책상 **행 자체를 삭제**한다는 점이 다름.

---

## 2. 변경 범위 요약

| 레이어 | 파일 | 변경 |
|--------|------|------|
| Backend Controller | `partner-api/.../controller/PartnerIntegrationController.java` | `DELETE /telegram` 엔드포인트 추가 |
| Backend Service | `partner-api/.../service/PartnerIntegrationService.java` | `disconnectTelegram(Long)` 메서드 추가 |
| Frontend Service | `partner-ui/src/api/services/integration.service.ts` | `disconnectTelegram()` 추가 |
| Frontend View | `partner-ui/src/views/partner/settings/TelegramView.vue` | 연결됨 화면에 "연결 해지" 버튼 + 핸들러 추가 |

DDL 변경 없음. DTO 신규 없음 (응답은 기존 `MessageResponse` 재사용).

---

## 3. Backend 구현

### 3-1. Service — `PartnerIntegrationService.java`

`testTelegram(...)` 메서드 바로 아래에 추가한다.

```java
    /**
     * Telegram 연결 해지.
     * 파트너의 Telegram 설정 행을 완전히 삭제한다.
     * 멱등 처리 — 설정이 없으면 이미 해지된 것으로 보고 정상 응답한다.
     */
    public MessageResponse disconnectTelegram(Long partnerId) {
        PartnerTelegramConfig config = telegramConfigRepository.findByPartnerId(partnerId);
        if (config == null) {
            log.info("[Telegram 해지] 이미 해지됨(설정 없음): partnerId={}", partnerId);
            return MessageResponse.of("Telegram 연결이 해지되었습니다.");
        }
        telegramConfigRepository.remove(config.getId());
        log.info("[Telegram 해지] 완료: partnerId={}, configId={}, chatId={}",
                partnerId, config.getId(), config.getChatId());
        return MessageResponse.of("Telegram 연결이 해지되었습니다.");
    }
```

> `IXRepository.remove(Long id)` 는 partner-api 전반에서 행 삭제에 사용 중 (`reservationRepository.remove`, `paymentLinkRepository.remove` 등). 동일 패턴을 따른다.
>
> 멱등 처리한 이유: 동시에 두 탭에서 해지를 누르거나, 폴링 도중 해지하는 경우에도 404 대신 정상 응답하여 UX를 단순화한다. 만약 "없으면 404" 정책을 원하면 `throw new NotFoundException(...)` 로 교체.

### 3-2. Controller — `PartnerIntegrationController.java`

import 추가:

```java
import org.springframework.web.bind.annotation.DeleteMapping;
```

`testTelegram()` 메서드 바로 아래에 추가한다.

```java
    /**
     * Telegram 연결 해지.
     * 파트너의 Telegram 연동을 완전히 끊고 설정을 삭제한다.
     *
     * @return 해지 결과
     * @response 200 해지 완료
     * @group 연동 설정
     * @auth true
     */
    @DeleteMapping(name = "Telegram 연결 해지", value = "/telegram")
    public MessageResponse disconnectTelegram() {
        Long partnerId = getSession().getPartnerId();
        return partnerIntegrationService.disconnectTelegram(partnerId);
    }
```

> 해지는 비가역 작업이지만, API 키 재발급(`@RequiresOtp`)과 달리 chat_id 복구 비용이 낮으므로(봇 재연결만 하면 됨) OTP는 선택사항이다. 보안 정책상 OTP를 강제하려면 `@RequiresOtp(description = "Telegram 연결 해지")` 를 추가.

### 3-3. 빌드 확인

```bash
./gradlew :partner-api:compileJava
```

---

## 4. Frontend 구현

### 4-1. API Service — `integration.service.ts`

`testTelegram` 아래에 추가:

```typescript
  disconnectTelegram: () =>
    api.delete<MessageResponse>('/api/partner/integration/telegram'),
```

> `api.delete` 가 client 래퍼에 정의되어 있는지 확인할 것 (`@/api/client`). 없다면 동일 시그니처로 추가하거나, 백엔드를 `POST /telegram/disconnect` 로 바꿔 `api.post` 를 사용한다. (REST 의미상 DELETE 권장)

### 4-2. View — `TelegramView.vue`

**(a) 해지 핸들러 추가** — `<script setup>` 의 `testSend` 함수 아래에 추가:

```typescript
const disconnecting = ref(false)

async function disconnect() {
  if (!confirm('텔레그램 연결을 해지하시겠습니까?\n해지 후에는 다시 QR/링크로 연결해야 알림을 받을 수 있습니다.')) {
    return
  }
  disconnecting.value = true
  try {
    await integrationService.disconnectTelegram()
    // 상태 초기화 → 미연결 화면으로 복귀
    chatId.value = ''
    isActive.value = false
    testResult.value = ''
    await generateToken()
    startPolling()
  } catch {
    /* 실패 시 현재 상태 유지 */
  } finally {
    disconnecting.value = false
  }
}
```

`disconnecting` ref 는 상단 ref 그룹(`const testing = ref(false)` 근처)에 함께 선언해도 된다.

**(b) 버튼 추가** — 연결됨(`<template v-else>`) 영역의 "테스트 전송" 줄(165~170행)을 아래로 교체:

```vue
          <div class="flex items-center gap-3">
            <Button variant="outline" size="sm" :disabled="testing || !isActive" @click="testSend">
              {{ testing ? '전송 중...' : '테스트 전송' }}
            </Button>
            <span v-if="testResult" class="text-sm text-green-600">{{ testResult }}</span>
            <Button
              variant="ghost"
              size="sm"
              class="ml-auto text-red-600 hover:text-red-700 hover:bg-red-50"
              :disabled="disconnecting"
              @click="disconnect"
            >
              {{ disconnecting ? '해지 중...' : '연결 해지' }}
            </Button>
          </div>
```

`ml-auto` 로 해지 버튼을 우측 정렬하여 파괴적 액션을 시각적으로 분리한다.

---

## 5. 동작 흐름 (해지 후)

```
[연결됨 화면]
  사용자가 "연결 해지" 클릭
    → confirm 확인
    → DELETE /api/partner/integration/telegram
    → 서버: partner_telegram_configs 행 삭제
    → 프론트: chatId/isActive 초기화 → isConnected=false
    → generateToken() 호출 (새 QR/딥링크)
    → startPolling() 재개
[미연결 화면 — QR/딥링크 표시, 재연결 대기]
```

봇 측(node-service telegram-bot) 변경 불필요. 재연결 시 사용자가 봇에서 `/start {partnerCode}` 를 다시 실행하면 `chat_id` 가 새로 등록된다.

---

## 6. 테스트 체크리스트

- [ ] 연결됨 상태에서 "연결 해지" → confirm 취소 시 아무 변화 없음
- [ ] 연결 해지 확인 → DB `partner_telegram_configs` 행 삭제 확인
- [ ] 해지 직후 UI가 QR/딥링크(미연결) 화면으로 전환되는지
- [ ] 봇에서 재연결(`/start`) → chat_id 재등록 → 다시 연결됨 화면으로 폴링 감지되는지
- [ ] 해지된 상태(설정 없음)에서 다시 DELETE 호출 → 200 정상 응답(멱등) 확인
- [ ] 해지 후 테스트 전송 시도 불가(미연결 화면에는 버튼 없음) 확인

운영 DB 확인 쿼리:

```bash
ssh cryptoments-bastion "ssh db-01 'mysql -u cryptoments -p\"Crypt0m3nts!2026\" cryptoments_db -e \"SELECT id, partner_id, chat_id, is_active FROM partner_telegram_configs WHERE partner_id = <PARTNER_ID>;\"'"
```

---

## 7. 배포

CLAUDE.md 배포 규칙 준수 — 수동 배포 금지.

1. **cryptoments-backend**: partner-api 변경 → `git push origin main` → CI/CD 자동 빌드+배포
2. **cryptoments-admin**: partner-ui 변경 → `git push origin main` → CI/CD 자동 빌드+배포

DDL 변경 없음 → DB 작업 불필요.
