richbukae 결제→배송 플로우 완성 Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: SALES_OS DB 구축 + Resend 이메일 검증 + E2E 결제→다운로드 플로우 완전 동작 확인
Architecture: 현재 코드는 거의 완성 상태. confirm API → DB 저장(non-blocking) + 이메일 발송(non-blocking) + 다운로드 토큰 반환. 미완성 부분은 (1) SALES_OS Turso DB 테이블 미생성, (2) Resend 실제 발송 미검증, (3) E2E 플로우 미테스트. 세 가지를 완성하면 CEO Toss 프로덕션 키 수령 즉시 실서비스 가능.
Tech Stack: Next.js 15, Turso (LibSQL), Resend SDK, Toss Payments v1 SDK (테스트 키), curl E2E 테스트
관련 태스크: fdaa3141
현황 스냅샷 (2026-02-25 기준)
| 컴포넌트 | 상태 | 비고 |
|---|---|---|
| checkout/page.tsx | ✅ 완성 | TossPayment 컴포넌트, 이메일 입력 |
| TossPayment.tsx | ✅ 완성 | Toss SDK v1, requestPayment |
| /api/payment/confirm | ✅ 완성 | Toss API 호출, 토큰 생성 |
| /success 페이지 | ✅ 완성 | 다운로드 링크 3개 표시 |
| /api/download | ✅ 완성 | 토큰 검증, R2 URL 리다이렉트 |
| SALES_OS DB | ❌ 테이블 미생성 | orders, crm_contacts 없음 |
| Resend 이메일 | ❓ 발송 미검증 | lib/resend.ts 구현 확인 필요 |
| E2E 테스트 | ❌ 없음 | 전체 플로우 테스트 필요 |
CEO 블로킹 (이 플랜에서 제외 — Plan 2에서 처리)
- TOSS_SECRET_KEY (프로덕션) → 현재 테스트 키 fallback으로 동작
- PRODUCT_PDF/SKILLS/NOTION_URL → 현재 503 응답, 기능 테스트는 가능
- RESEND_API_KEY → 없으면 이메일 발송 실패 (로그만)
Task 1: SALES_OS Turso DB 생성 및 테이블 마이그레이션
Files:
- Create:
projects/richbukae-store/scripts/migrate-sales-os.sh - Read:
projects/richbukae-store/src/app/api/payment/confirm/route.ts:60-90(orders INSERT 구조 참조)
Step 1: SALES_OS DB 존재 여부 확인
turso db list 2>&1 | grep -i sales
Expected: "sales-os" 또는 없음
Step 2: DB가 없으면 생성
turso db create sales-os --location nrt
turso db show sales-os
Expected: URL과 토큰 출력
Step 3: orders 테이블 생성
turso db shell sales-os "
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT NOT NULL UNIQUE,
email TEXT NOT NULL DEFAULT '',
product TEXT NOT NULL DEFAULT 'unknown',
amount INTEGER NOT NULL,
currency TEXT NOT NULL DEFAULT 'KRW',
payment_key TEXT NOT NULL,
payment_method TEXT,
status TEXT NOT NULL DEFAULT 'completed',
download_token TEXT,
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
"
Expected: 에러 없음
Step 4: crm_contacts 테이블 생성
turso db shell sales-os "
CREATE TABLE IF NOT EXISTS crm_contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT NOT NULL UNIQUE,
source TEXT NOT NULL DEFAULT 'richbukae',
total_purchases INTEGER NOT NULL DEFAULT 0,
total_spent INTEGER NOT NULL DEFAULT 0,
first_purchase_at INTEGER,
last_purchase_at INTEGER,
created_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
"
Step 5: 테이블 생성 확인
turso db shell sales-os "SELECT name FROM sqlite_master WHERE type='table';"
Expected: orders, crm_contacts 출력
Step 6: 토큰 발급 및 Vercel env 설정
# 토큰 발급
turso db tokens create sales-os
# Vercel env 설정 (richbukae-store 프로젝트 기준)
cd projects/richbukae-store
vercel env add SALES_OS_DB_URL production
# 값: libsql://sales-os-migkjy.aws-ap-northeast-1.turso.io (실제 URL 입력)
vercel env add SALES_OS_DB_TOKEN production
# 값: 발급된 토큰 입력
Step 7: 커밋
cd /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder
git add projects/richbukae-store/scripts/ 2>/dev/null || true
git commit -m "feat(richbukae): create sales-os Turso DB with orders + crm_contacts tables"
Task 2: Resend 이메일 배송 검증
Files:
- Read:
projects/richbukae-store/src/lib/resend.ts - Read:
projects/richbukae-store/src/app/emails/(이메일 템플릿 확인)
Step 1: resend.ts 구현 확인
cat projects/richbukae-store/src/lib/resend.ts
Expected: sendDeliveryEmail 함수 존재, RESEND_API_KEY 사용
Step 2: RESEND_API_KEY Vercel 설정 확인
cd projects/richbukae-store && vercel env ls | grep RESEND
Expected: RESEND_API_KEY 존재 여부 확인
Step 3: RESEND_API_KEY 없으면 루트 .env에서 복사
grep "RESEND" /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/.env
# 있으면:
cd projects/richbukae-store
vercel env add RESEND_API_KEY production
Step 4: 이메일 발송 curl 테스트 (confirm API 직접 호출)
# Toss 테스트 결제는 실제 paymentKey가 필요하므로 Resend만 단독 테스트
# resend.ts에 sendDeliveryEmail 직접 테스트 스크립트 작성
cat > /tmp/test-email.mjs << 'EOF'
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
const { data, error } = await resend.emails.send({
from: 'noreply@richbukae.com',
to: ['test@example.com'],
subject: '[테스트] richbukae 배송 이메일',
html: '<h1>테스트 이메일</h1><p>다운로드 링크: https://richbukae.com/api/download?type=pdf&token=test</p>',
});
console.log('Result:', { data, error });
EOF
RESEND_API_KEY=$(grep "^RESEND" /Users/nbs22/\(Claude\)/\(claude\).projects/business-builder/.env | cut -d= -f2) node /tmp/test-email.mjs
Expected: 이메일 ID 반환, 에러 없음
Step 5: resend.ts from 이메일 도메인 확인
grep -n "from" projects/richbukae-store/src/lib/resend.ts
Expected: noreply@richbukae.com 또는 onboarding@resend.dev (테스트용)
⚠️ richbukae.com 도메인이 Resend에 등록되지 않으면 발송 실패. 미등록이면
onboarding@resend.dev로 임시 변경.
Step 6: 커밋 (수정사항 있으면)
git add projects/richbukae-store/src/lib/resend.ts
git commit -m "fix(richbukae): update Resend sender domain for delivery emails"
Task 3: 다운로드 토큰 E2E 테스트
Files:
- Read:
projects/richbukae-store/src/lib/download.ts - Read:
projects/richbukae-store/src/app/api/download/route.ts
Step 1: 토큰 생성 검증 스크립트 작성
cat > /tmp/test-download-token.mjs << 'EOF'
import crypto from 'crypto';
const DOWNLOAD_SECRET = 'richbukae-download-secret-2026';
const TOKEN_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
function generateToken(orderId) {
const expiry = Date.now() + TOKEN_EXPIRY_MS;
const payload = `${orderId}:${expiry}`;
const hmac = crypto.createHmac('sha256', DOWNLOAD_SECRET).update(payload).digest('hex');
return Buffer.from(`${payload}:${hmac}`).toString('base64url');
}
function verifyToken(token) {
try {
const decoded = Buffer.from(token, 'base64url').toString('utf-8');
const parts = decoded.split(':');
if (parts.length !== 3) return { valid: false };
const [orderId, expiryStr, providedHmac] = parts;
if (Date.now() > parseInt(expiryStr)) return { valid: false };
const payload = `${orderId}:${expiryStr}`;
const expected = crypto.createHmac('sha256', DOWNLOAD_SECRET).update(payload).digest('hex');
return { valid: providedHmac === expected, orderId };
} catch { return { valid: false }; }
}
const token = generateToken('test-order-123');
console.log('Token:', token);
const result = verifyToken(token);
console.log('Verify:', result);
console.assert(result.valid === true, 'Token should be valid');
console.assert(result.orderId === 'test-order-123', 'OrderId should match');
console.log('✅ Token generation/verification OK');
EOF
node /tmp/test-download-token.mjs
Expected: ✅ Token generation/verification OK
Step 2: 다운로드 API 로컬 테스트 (dev 서버 필요)
cd projects/richbukae-store && npm run dev &
sleep 5
# 유효하지 않은 토큰 → 403 예상
curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000/api/download?type=pdf&token=invalid"
# Expected: 403
# 유효한 토큰 생성 후 테스트
TOKEN=$(node -e "
import crypto from 'crypto';
const s='richbukae-download-secret-2026';
const exp=Date.now()+7*24*60*60*1000;
const p='order-test:'+exp;
const h=crypto.createHmac('sha256',s).update(p).digest('hex');
console.log(Buffer.from(p+':'+h).toString('base64url'));
" --input-type=module)
# PRODUCT_PDF_URL 미설정이면 503, 설정되면 302 예상
curl -s -o /dev/null -w "%{http_code}" "http://localhost:3000/api/download?type=pdf&token=$TOKEN"
# Expected: 503 (URL 미설정) or 302 (설정된 경우)
Step 3: dev 서버 종료
kill $(lsof -ti:3000) 2>/dev/null || true
Task 4: confirm API 통합 테스트 (Toss 테스트 결제 시뮬레이션)
Files:
- Read:
projects/richbukae-store/src/app/api/payment/confirm/route.ts
⚠️ 실제 Toss paymentKey는 결제 UI를 통해서만 발급됨. 여기서는 Toss API mock 응답으로 로직 검증.
Step 1: confirm API 로직 단독 검증
# TOSS_SECRET_KEY 없이 테스트 (fallback 키 사용)
# 로컬 서버 기동
cd projects/richbukae-store && npm run dev &
sleep 5
# 잘못된 paymentKey → Toss API 실패 응답 예상
curl -s -X POST "http://localhost:3000/api/payment/confirm" \
-H "Content-Type: application/json" \
-d '{"paymentKey":"invalid","orderId":"test-order-001","amount":67000,"email":"test@example.com"}' \
| python3 -m json.tool
# Expected: {"error": "..."} with error message from Toss
Expected: 에러 JSON 반환, 200 아님
Step 2: 필수 파라미터 누락 테스트
curl -s -X POST "http://localhost:3000/api/payment/confirm" \
-H "Content-Type: application/json" \
-d '{"orderId":"test-order-001"}' \
| python3 -m json.tool
# Expected: {"error": "paymentKey, orderId, amount are required"}, status 400
Step 3: SALES_OS DB 연결 확인 (env 설정 후)
# SALES_OS_DB_URL 설정 후 더미 DB 삽입 테스트
SALES_OS_DB_URL="libsql://sales-os-..." SALES_OS_DB_TOKEN="..." node -e "
import { createClient } from '@libsql/client';
const db = createClient({ url: process.env.SALES_OS_DB_URL, authToken: process.env.SALES_OS_DB_TOKEN });
const r = await db.execute('SELECT COUNT(*) as cnt FROM orders');
console.log('orders count:', r.rows[0].cnt);
" --input-type=module
Expected: orders count: 0 (빈 테이블)
Step 4: dev 서버 종료 + 커밋
kill $(lsof -ti:3000) 2>/dev/null || true
git add -A
git commit -m "test(richbukae): verify payment→download flow with test key"
Task 5: 빌드 검증 + Vercel 배포
Step 1: 프로덕션 빌드 확인
cd projects/richbukae-store && npm run build 2>&1 | tail -20
Expected: ✓ Compiled successfully
Step 2: Vercel Preview 배포
cd projects/richbukae-store && vercel 2>&1 | tail -10
Expected: Preview URL 출력
Step 3: Preview URL에서 E2E 스모크 테스트
PREVIEW_URL="https://richbukae-store-xxx.vercel.app"
# 체크아웃 페이지 로드
curl -s -o /dev/null -w "%{http_code}" "$PREVIEW_URL/checkout"
# Expected: 200
# 결제 확인 API
curl -s -X POST "$PREVIEW_URL/api/payment/confirm" \
-H "Content-Type: application/json" \
-d '{"paymentKey":"test","orderId":"smoke-test","amount":1000}' \
| python3 -m json.tool
# Expected: 에러 응답 (Toss 테스트 paymentKey 미사용이므로) — 500/400 아닌 Toss 에러
Step 4: 커밋 (최종)
git add -A
git commit -m "feat(richbukae): payment-delivery flow complete — ready for production keys"
완료 체크리스트
- SALES_OS DB (sales-os) Turso에 생성됨
- orders, crm_contacts 테이블 생성됨
- SALES_OS_DB_URL, SALES_OS_DB_TOKEN Vercel env 설정됨
- Resend RESEND_API_KEY 설정됨
- 이메일 발송 테스트 성공
- 다운로드 토큰 생성/검증 테스트 PASS
- confirm API 에러 처리 검증됨
- 빌드 PASS
- Vercel Preview 배포됨
CEO 블로킹 해소 후 즉시 처리 항목 (Plan 2)
- TOSS_SECRET_KEY (프로덕션) → Vercel env add
- NEXT_PUBLIC_TOSS_CLIENT_KEY (프로덕션) → Vercel env add
- PRODUCT_PDF_URL, PRODUCT_SKILLS_URL, PRODUCT_NOTION_URL → Vercel env add
- Vercel --prod 배포 (CEO 승인 후)