---
title: "작업보고 — 충전식 잔여분 구현(온보딩 부스트 배선·계좌 1원인증 mock·어드민 분리표시/통화정정·사이드바 잔액·UI 정렬) (PR#81)"
category: "workreport"
document_type: "작업보고"
source_status: "generated"
knowledge_group: "03_history"
priority: "High"
purpose: "직전 PR#80(충전식 가격·결제 W1~W11)에서 PG mock 배선까지 끝낸 충전식 시스템의 남은 잔여분을 마감한 작업의 풀깊이 보고다. 무료 신규 사용자의 온보딩 부스트(환영 보너스 턴)를 프론트에 배선하고, 환불 계좌 본인확인을 위한 1원 인증을 실제 금융제휴 전 mock으로 전 구간 구현했으며, 운영자 어드민에 충전금 분리표시·전환 퍼널·어뷰징·재검토 트리거를 추가하고 AI 원가 표시의 통화 버그(USD를 ₩로 오표시)를 환율 환산으로 정정했다. 사이드바에 플랜·잔액을 노출하고 충전 UI 정렬을 개편했다. Codex 검수 1루프(Critical 1·High 3 등)를 반영해 부스트 월캡 누락·인증 경합·코치 미사용자 행 생성 같은 잠재 결함을 닫았다. 결과로 충전식 결제 시스템이 실제 금융 제휴 키만 갈아끼우면 라이브 전환되는 상태에 도달했다."
read_when: ["충전식","결제","온보딩부스트","계좌인증","어드민","사이드바잔액","최신상태복구"]
updated: "2026-06-11"
work_timestamp: "20260611_143500"
context: "달록본레포CC (D:\\dallog\\dallog_git) — 충전식 가격결제 후속 잔여분(PR#81) 작업보고"
source_of_truth: "https://dallog-tools.hansbridge.co.kr/knowledge/"
---

# 작업보고 — 충전식 잔여분 구현 (PR#81)

> **이 문서가 무엇인가 (비개발자용 한 줄 설명)**
> 코치노트 AI의 "충전해서 쓰는 결제 시스템"에서 직전 작업에 못 다 채운 마지막 조각들을 마감한 기록이다. 신규 가입자에게 주는 "환영 보너스 무료 대화"를 실제로 작동하게 연결하고, 환불받을 통장이 정말 본인 것인지 확인하는 "1원 보내기 인증"을 (아직 은행 제휴 전이라) 가짜로 전 과정 만들어 두었으며, 사장님이 운영 화면에서 돈 흐름·전환·부정사용을 한눈에 볼 수 있게 했고, 잘못 표시되던 AI 원가(달러를 원으로 착각하던 버그)를 환율로 바로잡았다.

---

## 0. 한눈에 보기

| 항목 | 내용 |
|---|---|
| 커밋 | `d10bad9` · 2026-06-11 14:35 (KST) |
| PR | **#81** `feat(coach): 충전식 잔여분 …` |
| 작업 단위 | 충전식 결제(PR#80) **잔여분(W5·W8·W10)** 마감 1건 |
| 변경 규모 | 파일 **21개**, **+951 / −67** 라인 |
| 마이그레이션 | 2건 (CC가 MCP로 직접 적용·검증 — 수동 복붙 아님) |
| 검수 | **Codex 1루프** 반영 (Critical 1 · High 3 · C2 mock 가드) |
| 정책 근거 | `[[project_pricing_charge_policy]]` (B-라이트: 무료0 / 1단계 5,900 / 2단계 9,900 / 히든 24,900 · 일일할당 자유책임 · 소진 시 단발 150 · 충전금=선수금) |

---

## 1. 무엇을 / 왜 했는가 (작업 4갈래)

직전 **PR#80**에서 충전식 결제 본체(W1~W11)와 PG **mock** 배선까지 마쳤다. 그 시점에 DB·정책은 깔려 있으나 **프론트에 안 붙은 부분 / 운영 가시성 / 환불 안전장치**가 남아 있었다. 이 PR#81이 그 "잔여분"을 닫는다.

### ① 온보딩 부스트(환영 보너스) 프론트 배선 — W5
- **무엇** — 무료 플랜 신규 사용자에게 *가입 7일 OR 첫 3세션 중 먼저 도달까지, 세션당 5턴*의 별도 보너스(promotional quota)를 실제 작동하도록 클라이언트에 연결했다.
- **왜** — DB RPC(`coach_onboarding_consume`)와 정책은 PR#80에 있었으나, 앱이 그 RPC를 **호출하지 않아 죽은 코드** 상태였다. 신규 사용자가 첫 경험에서 곧장 차감 벽에 막히는 것을 막는 것이 부스트의 목적이다.

### ② 환불 계좌 1원 인증 — mock 전 구간 (W10 보강)
- **무엇** — 환불받을 계좌가 본인 것인지 확인하는 **1원 인증**(입금자명에 4자리 코드 → 사용자가 입력 → 대조)을 시작~확정 전 구간 구현했다. 아직 은행/PG 제휴 전이므로 **mock(임시 모의 통장)**으로 코드를 즉시 화면에 보여주는 방식이다.
- **왜** — 사장님 지시(2026.06.11): *실 금융제휴 전에 전 구간을 mock으로 만들어 두고, 실계약 시 키·신호만 갈아끼운다*. 미인증 계좌로의 환불(타인 계좌 유출·자금세탁 통로)을 원천 차단하기 위함이다.

### ③ 어드민 — 분리표시·전환 퍼널·어뷰징·재검토 트리거 + 통화 정정 — W8
- **무엇** — 운영자 애널리틱스에 **🅰 선수금 / 🅱 매출 / 🅒 쿠폰** 분리표시(회계), 충전 **전환 퍼널**, 다계정 의심 **어뷰징 모니터**, 가격·캡 **재검토 트리거(§9)**를 추가했다. 동시에 AI 원가 표시의 **통화 버그**를 정정했다.
- **왜** — 선수금은 부채, 차감분만 매출이라는 분리회계를 운영자가 눈으로 대사(對査)할 수 있어야 한다. 또한 기존 어드민이 `estimated_cost`(단위 = **USD**)를 그대로 "₩"로 표시하던 버그가 있어 원가가 실제의 약 1/1500로 보였다.

### ④ 사이드바 잔액 + UI 정렬 — §4-2 / 스크린샷 지적
- **무엇** — 사이드바 하단에 **플랜 배지 + 잔여 충전금(쿠폰 별도)**을 노출(클릭 시 구독·충전금 관리로 이동)하고, 프로필 화면에도 구독 진입 링크를 추가. 충전 화면의 프리셋/자유입력/자동옵션 **정렬을 개편**했다.
- **왜** — 사용자가 자기 잔액을 매번 깊은 메뉴까지 들어가야 알 수 있었다. 충전 UI는 자유입력 input과 CTA·체크박스 정렬이 깨져 보인다는 **스크린샷 지적**이 있었다.

---

## 2. 어떻게 해결했는가 (구현 상세)

### 2-1. 온보딩 부스트 배선

**신규 파일 `src/lib/coachChat/onboardingBoost.ts`**
- `getDeviceId()` — `localStorage`에 기기 식별자(UUID) 보관. 기기 단위 부스트 1회 판정·어뷰징 분리 로깅용. `localStorage` 차단 환경에선 `'dev_unknown'`을 반환해 서버가 보수적으로 처리.
- `consumeOnboardingBoost(threadId, requestDay)` — RPC `coach_onboarding_consume` 호출. **거부되어도 throw 하지 않고** `{active:false, reason}`을 반환해 호출부가 일반 차감으로 자연 폴백한다(개인정보 최소화 위해 provider·ip는 클라에서 미전달).

**훅 `src/hooks/useCoachChat.ts`**
- AI 응답 성공 후, **무료 플랜 + normal 턴**일 때만 일반 차감(`consumeTurn`) **앞**에서 부스트를 먼저 시도. `active=true`면 그 턴은 부스트로 처리(일반 차감 생략 — RPC가 방 turn+1·월캡 합산까지 수행), `false`면 기존 차감 경로로 진행.
- 부스트 활성 시 `boostLeft`(현재 세션 잔여 부스트 턴)·낙관적 월 사용량 갱신. 스레드 리셋 시 `setBoostLeft(null)`.

**배지 `src/components/coach/CoachUsageBadge.tsx`**
- `boostLeft` prop 추가 → "**환영 보너스 · 이 대화 N회 무료**" 표시. §4-4 원칙(세션형 잔여만 표시, **가격표 들이밀기 금지**) 준수.

### 2-2. 계좌 1원 인증

| 레이어 | 파일 | 역할 |
|---|---|---|
| 클라 | `src/lib/coachChat/accountVerify.ts` (신규) | `startAccountVerify`(은행·계좌·예금주 → 코드 발급), `confirmAccountVerify`(코드 대조) — 워커 호출 래퍼 |
| 워커 | `workers/payment-webhook/index.js` | `/verify-account/start`·`/verify-account/confirm` 라우트(로그인 본인 검증 후 처리) |
| DB | `coach_account_verify` 테이블 + `coach_account_verify_confirm` RPC | 코드·만료·시도 저장 + **원자 확정** |

- **mock 동작** — 워커가 4자리 코드를 발급, 인증 행 기록 후 **응답으로 코드를 반환**(임시 모의 통장). 화면에 "🏦 임시 모의 통장 — 입금자명: 달록NNNN"으로 표시. *실서비스에서는 이 코드가 통장 입금 내역으로만 전달*된다는 안내 병기.
- **실전환** — 워커 env `ACCOUNT_VERIFY_PROVIDER`를 `openbanking`/`portone`으로 바꾸고 제휴 키만 등록하면 해당 분기에서 실제 1원 입금 호출(클라 코드 수정 0). 키 미설정 시 명확한 에러 반환(501).
- **환불 가드** — `coach_wallet_refund` RPC에 `refund_verified IS NOT TRUE`면 `account_not_verified` 거부 추가. 프론트는 이 사유를 "먼저 환불 계좌 인증(1원 인증)을 완료해 주세요"로 안내.
- **CoachSubscription.tsx 197줄 대개편** — 환불 섹션을 "계좌 저장" 단일 흐름에서 **인증 시작 → 코드 입력 → 인증 확인** 2단계 흐름으로 교체. 인증된 계좌는 "인증된 계좌 ✓" 배지로 표시.

### 2-3. 어드민 분리표시 + 통화 정정

**통화 버그 정정 (선결#1)** — `workers/admin-analytics/index.js`
```js
// coach_request_log.estimated_cost 단위 = USD(단가표 $/1Mtok 기반)
const fxRate = Number(env.FX_KRW_PER_USD || '1524.8'); // 정책 시뮬 환율
const totalCostKrw = Math.round(totalCostUsd * fxRate);  // 기존: USD를 그대로 ₩로 표시 = 버그
```
- 총원가·도구턴 원가·일별 추이 모두 `* fxRate` 환산 적용. 응답에 `fxRate`·`totalCostUsd`도 함께 노출.

**충전식 회계·전환 섹션 (`chargeSection`)**
- 🅰🅱🅒 — `coach_prepaid_reconcile_view`(예치계좌 대사 뷰)에서 미사용 선수금·매출귀속·누적충전/환불·보유자 수, `coach_revenue_view`에서 쿠폰 잔액 합산.
- 전환 퍼널 — `coach_funnel_event` 이벤트별(소진 임박→소진→단발→플랜 CTA→자동충전 ON) 카운트.
- 어뷰징 — `coach_onboarding_abuse_summary`(다계정 의심 기기). **차단 아닌 모니터링**(§5 과방어 금지 — 부스트 제외 근거일 뿐).
- 재검토 트리거(§9) — ⓐ 캡근접 사용자 비중(기준 3%) ⓑ 월 AI원가/매출(기준 35%, 매출 0이면 N/A) ⓒ 적용 환율(경보 1,800원 7일 지속, 수동 모니터). 기준 초과 시 `is-alert`(붉은색).

**프론트** — `AnalyticsTab.tsx`에 4개 섹션 카드, `analytics.ts`에 `charge`·`fxRate` 옵셔널 타입 추가(구 워커 응답 호환), `admin.css`에 `.adm-mini-v.is-alert{ color: var(--danger) }` 추가.

### 2-4. 사이드바 잔액 + UI 정렬

- **`Layout.tsx`** — `peekSubscription()`(side-effect 없는 "엿보기")+`getWallet()`로 플랜·잔액 조회. **행이 없으면 미노출** = 코치 미사용자에게 구독/지갑 행을 만들지 않음(아래 Codex H#3). 플랜 한글 라벨 매핑(`PLAN_BADGE`) 후 사이드바에 배지+충전금(+쿠폰) 노출.
- **`subscription.ts`** — `peekSubscription()` 신규(`maybeSingle`, 없으면 null 반환).
- **`SocialProfilePage.tsx`** — 액션바에 "구독·충전금" 링크 추가.
- **충전 UI 정렬** — 자유입력 input을 **"기타" 버튼 토글식**으로 변경(평소 숨김, 선택 시 노출). 충전 CTA를 독립 버튼으로 분리. 자동옵션을 `.coach-sub-opt`(체크박스+제목 1행 / 설명·설정은 들여쓴 하위 행) 구조로 재배치해 정렬 일관성 확보.

---

## 3. 어떤 문제가 있었는가 / 원인 / 판단근거

| 문제 | 원인 | 해결 | 판단근거 |
|---|---|---|---|
| 부스트가 **월 hard cap을 우회** 가능 | 부스트 RPC가 별도 quota만 보고 월캡 잔여를 검사 안 함 | RPC에 엔타이틀 `FOR UPDATE` + 잔여<1이면 `exhausted` 반환 | Codex **C#1** — 무료 사용자가 부스트로 월 한도 초과 소진 가능 |
| 인증 confirm **경합·부분 처리** | 코드 대조→지갑 기록이 분리 단계 | `coach_account_verify_confirm` 단일 트랜잭션(`FOR UPDATE`)로 통합 | Codex **H#1·H#2** — 이중 성공·attempts 유실·부분 처리 |
| 코치 **미사용자에게 행 생성** | 사이드바 표시가 `ensure`(행 생성) 경로 사용 | `peekSubscription`(side-effect 0) 신설 | Codex **H#3** — 표시 전용 경로가 데이터 행을 만들면 안 됨 |
| mock 코드가 **실서비스에서 노출** 위험 | mock 분기에 끄기 레버 없음 | 결제 `MOCK_ENABLED=false`면 계좌인증 mock도 410 거부(레버 결합) | Codex **C#2** — 실전환 시 코드 응답 노출 모드를 한 번에 잠금 |
| DEFINER 함수 **anon 호출** 가능 | 기본 권한에 anon 포함 | admin·confirm 함수 `REVOKE … FROM anon` + confirm은 service_role 전용 | 점검 보강 — DEFINER 함수는 service_role만 |
| AI 원가가 **실제의 ~1/1500**로 표시 | `estimated_cost`(USD)를 ₩로 직접 표시 | `* fxRate`(기본 1524.8) 환산 | 선결#1 통화 버그 |

---

## 4. Codex 검토 반영 결과

이 작업은 Codex 검수 협업으로 진행했다(커밋 메시지에 C1/C2/H1~3 근거 명시). **검수 1루프** 결과를 아래와 같이 분류·반영했다.

| 분류 | 항목 | 처리 |
|---|---|---|
| **[즉시반영]** | **C#1** 부스트 월캡 우회 | RPC 월 hard cap 가드 추가 (`coach_boost_acctv_fix.sql`) |
| **[즉시반영]** | **C#2** mock 코드 노출 가드 | `MOCK_ENABLED` 레버 결합 — 실전환 시 동시 잠금 |
| **[즉시반영]** | **H#1·H#2** confirm 경합·부분 처리 | 원자 RPC `coach_account_verify_confirm`로 단일 트랜잭션화 |
| **[즉시반영]** | **H#3** 미사용자 행 생성 | `peekSubscription`(side-effect 0) 신설·사이드바 적용 |
| **[즉시반영]** | (점검 보강) DEFINER 함수 anon 권한 | confirm·admin 함수 `REVOKE FROM anon` |

> 미반영(보류·반려) 항목 없음 — 지적 전부 즉시 반영.

---

## 5. 결과

- ✅ 무료 신규 사용자 온보딩 부스트가 **실제로 작동**(배지 노출·월캡 합산·일일할당 미소모).
- ✅ 환불 계좌 1원 인증이 **mock 전 구간 작동** — 실 금융제휴 시 워커 env+키 교체만으로 라이브.
- ✅ 미인증 계좌 환불 **차단**(account_not_verified 가드).
- ✅ 운영자 어드민에서 🅰🅱🅒 분리회계·전환 퍼널·어뷰징·재검토 트리거를 **눈으로 대사** 가능.
- ✅ AI 원가 **통화 버그 정정**(USD→KRW 환산).
- ✅ 사이드바 플랜·잔액 노출 + 충전 UI 정렬 개편.
- ✅ 마이그레이션 2건 CC가 MCP로 직접 적용·검증.

---

## 6. 미해결 / 후속작업

| 구분 | 내용 |
|---|---|
| **실 금융제휴** | 1원 인증·결제 모두 mock. 오픈뱅킹/PG 실계약 시 워커 env(`ACCOUNT_VERIFY_PROVIDER`, `OPENBANKING_*`, `PORTONE_*`)·키 등록 + start 분기 실입금 호출 구현(TODO 주석 위치 명시). [환경변수 등록됨] 항목은 키만 교체. |
| **환율 정책** | `FX_KRW_PER_USD` 기본 1524.8(정책 시뮬값). 재검토 트리거 ⓒ 경보 1,800원·7일 지속은 **수동 모니터**(자동 알림 미구현). |
| **자동 원가캐치** | 단가표(`coach_price_table`) 다수 placeholder → 자동 원가 캐치·적용 시스템 별도 구축 예정(`[[project_auto_cost_catch_system]]`). |
| **퍼널 이벤트 송출** | `coach_funnel_event` 일부 이벤트(단발 클릭/사용·플랜 CTA 등)의 클라 송출 지점 점검 필요(어드민 카운트가 0이면 미배선 신호). |
| **베타 전 가동** | 어드민-애널리틱스는 Phase1 필수(`[[project_admin_analytics_phase1]]`) — 베타 전 service_role 집계 동작 최종 확인. |

---

## 작업 리드타임

- 커밋 시각 기준 **2026-06-11 14:35 (KST)** 1건 완료(PR#81 머지). 작업 단위 = 충전식 결제(PR#80) 잔여분 마감 1건.

---

```
