Communitygithub.com

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Sepay 웹훅에서 결제 성공 시 Telegram 봇 알림을 Next.js 프로젝트에 설정합니다.

MKT.LANDINGPAGE.SKILLS란 무엇인가요?

이 스킬은 Next.js 프로젝트에서 Sepay 웹훅을 통해 결제가 성공하면 Telegram 봇이 실시간으로 알림을 보내도록 연결합니다. 기존 Sepay 웹훅 핸들러를 감지하고, `lib/telegram.ts`에 재사용 가능한 `sendTelegramNotification()` 헬퍼 함수를 생성하며, `Promise.allSettled`를 사용하여 이메일 자동 응답기(Resend)와 함께 알림을 병렬로 전송합니다. 이를 통해 Telegram 오류가 200 응답을 차단하지 않도록 하여 Sepay의 웹훅 재시도로 인한 중복 알림을 방지합니다. 또한 사용자에게 @BotFather를 통한 Telegram 봇 생성, 토큰 및 chat_id 획득 방법을 안내하고, `.env.local`에 환경 변수를 추가합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lexuanthanhhy/MKT.LANDINGPAGE.SKILLS/tree/main/.claude/skills/biz-telegram-payment-notify

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

Biz Telegram Payment Notify — Sepay가 입금을 수신하면 실시간 알림

이 스킬은 Next.js 프로젝트의 기존 Sepay 웹훅에 Telegram 봇 알림을 연결합니다. 고객이 Sepay에 연결된 은행 계좌로 송금하는 데 성공할 때마다 스킬은 Telegram 봇을 통해 소유자(또는 영업팀 그룹 채팅방)에게 고객 이름, 전화번호, 금액, 제품, 시간 정보가 포함된 실시간 메시지를 보냅니다.

철학: 고객용 이메일 자동 응답(Resend)과 소유자용 Telegram 알림은 서로 다른 두 대상에게 서비스를 제공합니다. 이메일은 고객을 위한 격식 있는 소통입니다. Telegram은 소유자를 위한 '핑(ping)'입니다. — 바깥에 나가 있을 때 Telegram을 열면 새 주문을 바로 확인할 수 있으며, 100개의 다른 이메일 중에서 찾기 위해 Gmail을 열 필요가 없습니다. Telegram 지연 시간은 2초 미만인 반면, 이메일은 30-60초입니다.

Slack/Discord/Zalo 대신 Telegram을 사용하는 이유: Telegram 봇 설정은 5분(토큰 1개 + chat_id 1개)이면 충분하며, 워크스페이스/서버 관리자가 필요 없고, 무제한 무료 메시지, 이메일보다 강력한 푸시 알림, iOS/Android/데스크톱 네이티브 앱을 제공합니다. Slack은 워크스페이스와 초대가 필요합니다. Discord는 서버와 권한 설정이 필요합니다. Zalo OA는 인증된 비즈니스 계정(승인까지 3-7일)과 월간 활성 사용자 요금이 필요합니다. Telegram은 소유자 측 알림을 위한 최소 저항 경로입니다.

결과물: 코드 패치 + 봇 설정 가이드(단계별) + 환경 변수 목록 + 3단계 테스트 계획. 스킬은 Telegram 전송을 자체 테스트하지 않습니다 — 사용자가 먼저 봇 토큰과 chat_id를 붙여넣도록 요구합니다. 이러한 자격 증명은 사용자의 자산이기 때문입니다.


이 스킬을 사용해야 하는 경우

  • 사용자가 Sepay 웹훅 설정을 막 마쳤고, 고객이 송금할 때 Telegram을 통해 실시간 알림을 받고 싶어 합니다.
  • 사용자가 이미 /api/payment-success(또는 이와 동등한)를 통해 Resend 이메일을 처리하고 있으며, 병렬로 Telegram 알림을 추가하고자 합니다. — 새 라우트를 만들거나 로직을 중복하지 않습니다.
  • 사용자가 "주문이 들어오면 알려주세요"라고 말했고, 컨텍스트가 Sepay 통합이 있는 판매 랜딩 페이지입니다.

다음과 같은 경우에는 이 스킬을 사용하지 마십시오:

  • 사용자가 Slack/Discord 알림을 원하는 경우 → 다른 사용 사례입니다.
  • 사용자가 Zalo OA 봇을 원하는 경우 → OA 인증된 비즈니스 + 다른 비용 구조가 필요합니다.
  • 사용자가 Sepay 웹훅을 설정하지 않은 경우(은행 계좌가 Sepay에 연결되지 않았거나, 웹훅을 수신할 엔드포인트가 없는 경우) → 이 스킬은 Sepay를 처음부터 설정하지 않으며, 기존 웹훅에만 연결합니다.
  • 사용자가 소유자에게 이메일만 보내기를 원하는 경우 → biz-email-setup에 이미 Email B(소유자 알림)가 있습니다.

전체 워크플로우 (6단계)

Phase 0: 기존 Sepay 웹훅 라우트 감지 (경로 + 프레임워크 + 핸들러 형태)
       ↓
Phase 0.5: 리드 저장소 감지 (lib/leads-kv.ts from /biz-setup-sepay-payment)
           → 있음: 실제 KV 조회 사용
           → 없음: 2가지 옵션 제공 (원시 모드 또는 /biz-setup-sepay-payment 먼저 실행)
       ↓
Phase 1: 사용자에게 Telegram 봇 생성 안내 (BotFather → 토큰 → chat_id)
       ↓ [게이트 — 사용자가 토큰 + chat_id를 붙여넣을 때까지 대기]
       ↓
Phase 2: 메시지 형식 초안 작성 → 사용자에게 미리보기 표시 → 확인
       ↓
Phase 3: 코드 연결 (lib/telegram.ts 헬퍼 + 웹훅 핸들러에 통합 + 환경 변수)
       ↓
Phase 4: 테스트 계획 (curl 봇 직접 → 시뮬레이션된 Sepay 페이로드 → 프로덕션 확인)

Phase 1과 Phase 4에는 사용자 대기 게이트가 있습니다. 사용자가 수동 작업(토큰 가져오기, 프로덕션 확인)을 수행해야 하기 때문입니다. Phase 0/0.5/2/3은 스킬이 능동적으로 처리합니다.


Phase 0 — 기존 Sepay 웹훅 감지

이 스킬은 새 라우트를 만들지 않고 기존 웹훅에 연결합니다. 먼저 핸들러를 찾습니다.

웹훅 라우트 찾기 — 일반적인 키워드로 grep:

# 프로젝트 루트에서
grep -r "sepay" --include="*.ts" --include="*.js" --include="*.tsx" -l
grep -r "transferAmount\|transferType\|payment-success\|payment-webhook" --include="*.ts" --include="*.js" -l

3가지 일반적인 시나리오:

시나리오기존 경로조치
사용자가 biz-email-setup 파이프라인 사용app/api/payment-success/route.ts (또는 pages/api/payment-success.ts)에 sendCustomerEmail + sendOwnerEmail을 호출하는 Resend가 이미 있음동일한 Promise.allSettled에 sendTelegramNotification 연결
사용자가 Sepay 전용 라우트를 가지고 있음app/api/sepay-webhook/route.ts 또는 app/api/webhook/sepay/route.tsSepay 페이로드를 파싱하고 검증한 후(transferType === 'in' 분기) 연결
어떤 라우트도 찾을 수 없음—중단. 사용자에게 알림: "Sepay를 수신하는 웹훅 라우트를 찾을 수 없습니다. Sepay 웹훅을 먼저 설정(은행을 Sepay에 연결 → 도메인을 가리키는 웹훅 URL 생성)한 후 돌아와서 Telegram을 연결해 주세요. 또는 웹훅이 다른 이름의 엔드포인트에 있다면 경로를 알려주세요."

기존 핸들러 읽기 — 형태 이해:

  • Sepay 요청을 인증합니까? (Authorization 헤더 Apikey 또는 HMAC)
  • id 또는 referenceCode를 기준으로 중복 제거합니까? (Sepay는 non-200 응답 시 재시도 → 중복 알림 방지를 위해 중복 제거 필요)
  • content(주문 ID가 포함된 송금 메시지)를 기준으로 리드를 조회합니까?
  • Sepay에 반환하는 응답 형식: { success: true } 또는 일반 200 OK?

기존 핸들러에 중복 제거가 없는 경우 — 사용자에게 경고하지만 차단하지는 않습니다. 스킬은 Telegram만 연결합니다. 중복 제거는 결제 로직의 문제입니다. 필요한 경우 별도로 중복 제거를 추가하도록 제안합니다.


Phase 0.5 — 리드 저장소 감지 (중요)

Sepay 웹훅은 content(고객이 송금 시 붙여넣은 텍스트) + transferAmount + referenceCode만 보냅니다. Sepay는 고객이 누구인지 알지 못합니다 — content(주문 ID 포함)를 기준으로 로컬 저장소를 조회해야 합니다.

스킬이 3가지를 확인합니다:

# 1. 리드 저장소 헬퍼 존재 확인
test -f lib/leads-kv.ts || test -f lib/leads-kv.js && echo "lead store: ✓"

# 2. Vercel KV 환경 변수 확인
grep -E "KV_REST_API_URL|KV_REST_API_TOKEN" .env.local && echo "KV env: ✓"

# 3. @vercel/kv 설치 확인
grep '"@vercel/kv"' package.json && echo "KV package: ✓"

3가지 시나리오:

시나리오조치
3가지 모두 ✓ — 리드 저장소 준비 완료Phase 1로 진행, 웹훅 핸들러에서 lib/leads-kv.ts의 getLeadByOrderId() 사용
1개 이상 누락 — 설정되지 않음중단, 사용자에게 2가지 옵션 질문 (아래 참조)

누락된 경우, 사용자에게 질문:

리드 저장소(Vercel KV)가 설정되지 않은 것을 발견했습니다. Sepay 웹훅은 고객이 누구인지 알지 못합니다 — 송금 내용과 금액만 있습니다. 다음 중 원하는 옵션을 선택해 주세요:

옵션 1 — 먼저 /biz-setup-sepay-payment를 통해 전체 리드 저장소 설정 (권장):

  • 해당 스킬이 Vercel KV를 설정하고, lib/leads-kv.ts를 스캐폴딩하며, 사용자 등록 시 리드를 저장하도록 양식 제출을 연결합니다.
  • 그런 다음 여기로 돌아와서 고객 이름/전화번호/이메일을 조회하는 Telegram을 연결합니다.
  • Telegram 알림에 표시됩니다: "Nguyễn Văn A — 0901234567 — [email protected] — 499K — AI Agent 코스"

옵션 2 — 원시 모드: 리드를 조회하지 않고 Sepay 페이로드를 Telegram으로 직접 전송:

  • Telegram에만 표시됩니다: "Vietcombank — 499.000đ — content: DH001 0901234567 — 14:32"
  • 소유자는 내용을 통해 고객을 스스로 추측하고 Resend 이메일 로그와 교차 확인해야 합니다.
  • 간단하고 즉시 실행 가능하지만 UX가 떨어집니다.

1 또는 2를 선택해 주세요.

사용자가 1을 선택한 경우 → 이 스킬을 중단하고 /biz-setup-sepay-payment를 제안합니다. 사용자가 설정을 마친 후 이 스킬로 돌아옵니다.

사용자가 2를 선택한 경우 → Phase 1로 진행하고 원시 템플릿을 사용합니다(templates/lib-telegram-raw.ts 참조).


Phase 1 — 사용자에게 Telegram 봇 생성 안내

자세한 지침과 텍스트 스크린샷은 references/telegram-bot-setup.md를 참조하세요. 사용자를 위한 3단계 요약:

1단계 — @BotFather를 통해 새 봇 만들기

1. Telegram 열기 (모바일/데스크톱)
2. 검색: @BotFather → 시작 버튼 클릭
3. 명령어 입력: /newbot
4. BotFather가 봇 이름을 묻습니다 — 표시 이름 입력 (예: "브랜드 X 주문")
5. BotFather가 사용자 이름을 묻습니다 — "_bot"으로 끝나는 사용자 이름 입력 (예: brandx_orders_bot) — 전역적으로 고유해야 함
6. BotFather가 다음 줄이 포함된 메시지를 반환합니다:
   "Use this token to access the HTTP API: 1234567890:AAxxxxxxxxxxxxxxxxxxxxxx"
7. 해당 토큰을 복사하여 붙여넣으세요.

보안 참고: 이 토큰은 봇을 제어할 수 있는 권한입니다. Git에 커밋하지 말고 공개적으로 공유하지 마세요. 실수로 유출된 경우 @BotFather → /token → /revoke로 돌아가 새 토큰을 만드세요.

2단계 — chat_id 가져오기

1:1 채팅 (소유자만 수신) — 솔로프리너에게 권장:

1. Telegram에서 검색: @userinfobot → 시작 버튼 클릭
2. 봇이 "Id: 123456789"가 포함된 메시지를 반환합니다 — 해당 번호를 복사합니다.
3. 그것이 당신의 chat_id입니다.

그룹 채팅 (영업팀이 함께 수신) — 팀에게 권장:

1. 새 Telegram 그룹 만들기 (또는 기존 그룹 사용)
2. 그룹 이름 클릭 → 구성원 추가 → 방금 만든 봇 검색 (@brandx_orders_bot 사용자 이름으로) → 추가
3. 그룹에서 입력: /start@brandx_orders_bot (또는 아무 메시지나)
4. 브라우저를 열고 다음 주소로 접속:
   https://api.telegram.org/bot<TOKEN>/getUpdates
   (<TOKEN>을 1단계의 토큰으로 바꾸기)
5. JSON 응답에서 "chat":{"id":-1001234567890,...}를 찾습니다 — 해당 번호를 복사합니다 (있는 경우 빼기 기호 유지)
6. 그것이 그룹의 chat_id입니다.

채널 (단방향 브로드캐스트, 토론 없음) — chat_id가 -100으로 시작합니다. 가져오는 방법은 그룹과 동일하지만 봇을 채널 관리자로 추가합니다.

3단계 — 스킬에 붙여넣기

사용자가 다음을 붙여넣을 때까지 기다립니다:

TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789

중요 게이트: 사용자가 두 값을 모두 붙여넣을 때까지 Phase 2로 진행하지 마십시오. 사용자가 "다른 곳에 이미 있습니다"라고 말하더라도 여전히 붙여넣기를 기다리십시오. Phase 3에서 .env.local에 주입해야 하기 때문입니다.


Phase 2 — 메시지 형식 초안 작성

기본 형식은 베트남어, 모바일 친화적(Telegram HTML 렌더링):

🎉 <b>신규 주문</b>

👤 <b>{{name}}</b>
📞 {{phone}}
📧 {{email}}
💰 <b>{{amount}}</b> — {{product_name}}
🕐 {{time_vn}}

🔗 거래 코드: <code>{{reference_code}}</code>

변수 주입 후:

🎉 신규 주문

👤 Nguyễn Văn A
📞 0901 234 567
📧 [email protected]
💰 499.000đ — AI Agent 코스
🕐 14:32 14/05/2026

🔗 거래 코드: FT26134567890

사용자에게 미리보기 표시, 질문:

  1. 이 형식이 괜찮습니까? 추가/제거할 필드가 있습니까? (예: UTM 소스, 일일 주문 수, 고객 Zalo 링크 추가)
  2. 이모지가 너무 많습니까? (일부 사용자는 일반 텍스트를 선호)
  3. CRM/시트로 연결되는 인라인 버튼(CTA)을 추가하시겠습니까? (고급 — 스킬이 인라인 키보드를 추가할 수 있음)

형식 규칙:

  • HTML 모드(parse_mode: "HTML") — Telegram은 <b>, <i>, <u>, <code>, <pre>, <a href>를 지원합니다. <br>은 지원하지 않습니다 — \n을 사용하세요.
  • VND 금액 형식: 499.000đ (천 단위 구분 마침표, 끝에 đ 포함) 또는 깔끔하게 499K / 1.99M를 사용합니다. offer.json voice 설정이 있으면 이를 읽습니다.
  • 전화번호 형식: 0901 234 567 (3-3-3 간격) — 모바일에서 더 쉽게 스캔할 수 있습니다.
  • 베트남 시간: HH:mm dd/mm/yyyy — UTC가 아닌 베트남 시간(UTC+7)으로 설정합니다.
  • 이모지 스팸을 피하세요(메시지당 5개 이상의 이모지는 전문성을 떨어뜨립니다).

Phase 3 — 코드 연결

3가지 변경 사항:

3.1 헬퍼 함수 lib/telegram.ts 만들기

templates/lib-telegram.ts (TypeScript) 또는 templates/lib-telegram.js (vanilla JS)를 읽습니다. 내용:

// lib/telegram.ts
type TelegramPayload = {
  name: string;
  phone: string;
  email: string;
  amount: number; // VND, raw number (예: 499000)
  productName: string;
  referenceCode?: string;
};

export async function sendTelegramNotification(payload: TelegramPayload): Promise<void> {
  const token = process.env.TELEGRAM_BOT_TOKEN;
  const chatId = process.env.TELEGRAM_CHAT_ID;

  if (!token || !chatId) {
    console.error('[telegram] Missing TELEGRAM_BOT_TOKEN or TELEGRAM_CHAT_ID');
    return; // throw하지 않음 — Telegram 실패가 결제 응답을 차단하지 않도록 함
  }

  const text = formatMessage(payload);

  try {
    const res = await fetch(`https://api.telegram.org/bot${token}/sendMessage`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: chatId,
        text,
        parse_mode: 'HTML',
        disable_web_page_preview: true,
      }),
    });
    if (!res.ok) {
      const errBody = await res.text();
      console.error(`[telegram] Send failed: ${res.status} ${errBody}`);
    }
  } catch (err) {
    console.error('[telegram] Network error:', err);
    // Swallow — Telegram 실패가 Sepay를 재시도하게 하지 않음
  }
}

function formatMessage(p: TelegramPayload): string {
  const amountStr = p.amount.toLocaleString('vi-VN') + 'đ';
  const phoneStr = p.phone.replace(/(\d{4})(\d{3})(\d{3})/, '$1 $2 $3');
  const now = new Date().toLocaleString('vi-VN', {
    timeZone: 'Asia/Ho_Chi_Minh',
    day: '2-digit', month: '2-digit', year: 'numeric',
    hour: '2-digit', minute: '2-digit',
  });
  return [
    '🎉 <b>신규 주문</b>',
    '',
    `👤 <b>${escapeHtml(p.name)}</b>`,
    `📞 ${phoneStr}`,
    `📧 ${escapeHtml(p.email)}`,
    `💰 <b>${amountStr}</b> — ${escapeHtml(p.productName)}`,
    `🕐 ${now}`,
    p.referenceCode ? `\n🔗 거래 코드: <code>${escapeHtml(p.referenceCode)}</code>` : '',
  ].filter(Boolean).join('\n');
}

function escapeHtml(s: string): string {
  return s.replace(/[<>&]/g, c => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;' }[c]!));
}

오류를 삼키는 이유: Sepay는 non-200 응답을 수신하면 웹훅을 재시도합니다. Telegram 시간 초과/다운으로 웹훅이 중단되면 Sepay가 재시도하고, 각 재시도는 이메일을 다시 보내고 결제를 다시 기록하여 사용자가 단일 주문에 대해 5개의 이메일과 5개의 Telegram 알림을 받게 됩니다. Telegram은 최선 노력 사이드 채널이지 중요 경로가 아닙니다.

3.2 Sepay 웹훅 핸들러에 연결

기존 핸들러를 읽고 결제 성공 처리 위치(Sepay 페이로드 파싱 + transferType === 'in' 검증 후)를 찾습니다. 추가:

// app/api/payment-success/route.ts (또는 동등한 파일)
import { sendTelegramNotification } from '@/lib/telegram';
import { sendCustomerEmail, sendOwnerEmail } from '@/lib/resend'; // 이미 있는 경우

export async function POST(req: Request) {
  const payload = await req.json();

  // [기존] Sepay 인증 + 페이로드 파싱 검증
  if (payload.transferType !== 'in') {
    return Response.json({ success: true }); // 나가는 송금 무시
  }

  // [기존] content(주문 ID 포함) 또는 referenceCode를 기준으로 리드 조회
  const lead = await lookupLeadByTransferContent(payload.content);

  // [신규] 3가지 부수 효과를 병렬로 전개 — Telegram 실패가 이메일을 차단하지 않음
  await Promise.allSettled([
    sendCustomerEmail(lead),
    sendOwnerEmail(lead),
    sendTelegramNotification({
      name: lead.name,
      phone: lead.phone,
      email: lead.email,
      amount: payload.transferAmount,
      productName: lead.productName,
      referenceCode: payload.referenceCode,
    }),
  ]);

  return Response.json({ success: true });
}

Promise.allSettled를 사용하는 이유 (Promise.all 대신):

  • Promise.all은 어떤 프로미스라도 실패하면 reject됩니다 → 핸들러가 throw됨 → Sepay가 500을 수신 → 재시도 → 중복 이메일.
  • Promise.allSettled는 모든 프로미스가 처리될 때까지 기다립니다(resolve 또는 reject). throw되지 않습니다.

Phase 4 — 테스트 계획 (3단계)

레벨 1 — 봇 직접 테스트 (curl)

사용자가 다음을 실행하도록 요청:

curl -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": "<CHAT_ID>", "text": "🧪 <b>테스트 알림</b>\n\n👤 <b>홍길동</b>\n📞 0901 234 567\n📧 [email protected]\n💰 <b>499.000đ</b> — 테스트 제품", "parse_mode": "HTML"}'

기대: 2초 이내에 Telegram 앱에 메시지가 표시됩니다. 실패할 경우:

  • 400 Bad Request: chat_id가 잘못되었거나 봇이 채팅의 구성원이 아닙니다(그룹인 경우).
  • 401 Unauthorized: 토큰이 잘못되었거나 취소되었습니다.
  • 404 Not found: Bot API URL 형식이 잘못되었습니다(토큰 구문 확인).

레벨 2 — 시뮬레이션된 Sepay 페이로드

사용자가 프로젝트를 시작하고 다음을 통해 테스트 웹훅을 수동으로 보내도록 요청(로컬에서)

# 프로젝트 루트에서 (로컬 서버가 실행 중이라고 가정)
curl -X POST http://localhost:3000/api/payment-success \
  -H "Content-Type: application/json" \
  -d '{
    "id": "test_webhook_001",
    "gateway": "VCB",
    "transferType": "in",
    "transferAmount": 499000,
    "content": "lead_test_001",
    "referenceCode": "TEST123",
    "accountNumber": "1234567890",
    "transactionDate": "2026-05-14 07:32:00"
  }'

기대:

  • Resend 이메일(있는 경우)이 고객과 소유자에게 전송됩니다.
  • Telegram 알림이 표시됩니다.
  • curl 응답: { "success": true } (200).
  • 만약 Telegram이 실패하면(예: 잘못된 chat_id) → 응답은 여전히 200이어야 합니다. 500이 아니어야 합니다.

레벨 3 — 프로덕션에서 확인 (선택 사항이지만 권장)

1. 프로덕션에 배포합니다.
2. 실제 고객이 Sepay를 통해 결제하거나 VND 10,000을 직접 송금합니다.
3. 조건:
   - Owner: 10초 이내에 Telegram 알림 수신 (이메일보다 빠름).
   - 고객: Resend 이메일 수신 (영수증/감사).
   - 결제가 Sepay에 200 OK로 기록됨 (관리자 페이지).
   - 웹훅 로그에 재시도 없음 (동일한 결제 ID에 대해 하나의 POST만).
4. 문제 해결:
   - Telegram은 수신했지만 이메일은 없음 → Resend 확인.
   - 이메일은 수신했지만 Telegram은 없음 → 봇 토큰/chat_id 확인 + curl로 직접 테스트.
   - 둘 다 수신했지만 1분 후에 수신 → `Promise.allSettled`가 여전히 올바른지 확인 (50x 오류를 던지고 있지 않은지).

환경 변수 (업데이트할 .env.local)

.env.local에 추가:

TELEGRAM_BOT_TOKEN=1234567890:AAxxxxxxxxxxxxxxxxxxxxxx
TELEGRAM_CHAT_ID=123456789

.gitignore에 이미 추가되어 있는지 확인 (가장 일반적인 패턴):

# .gitignore (이미 있는 경우 위 주석 해제 또는 추가)
.env.local
.env*.local

.env.local이 이미 .gitignore에 있다면 사용자에게 알릴 필요가 있지만 항상 다시 확인하세요: cat .gitignore | grep .env.local

(선택 사항, 권장) .env.example 업데이트:

# .env.example
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=

이를 통해 새 개발자가 놓치지 않도록 합니다.


관련 파일

파일목적생성됨?
lib/telegram.tssendTelegramNotification() + formatMessage()예 (Phase 3)
app/api/payment-success/route.ts웹훅 핸들러 (수정됨)기존 + 수정됨
.env.localTELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID수정됨
.gitignore.env.local 행 확인확인됨

lib/leads-kv.ts — 이 스킬이 생성하지 않음. Phase 0.5에서 확인하고 없으면 사용자에게 /biz-setup-sepay-payment를 안내합니다.


자주 묻는 질문

Q: 동일한 주문에 대해 여러 Telegram 알림을 받았습니다. 왜 그런가요?

A: Sepay가 웹훅을 재시도했을 가능성이 높습니다(핸들러가 200을 반환하지 않았거나 Sepay가 네트워크 시간 초과를 감지했습니다). 확인: (1) 웹훅 핸들러가 항상 { success: true }를 반환하는지 확인합니다(모든 분기 종료). (2) id 또는 referenceCode를 기준으로 중복 제거를 추가합니다. (3) Promise.allSettled를 사용하고 있는지 확인합니다(Promise.all이 아님).

Q: Telegram 메시지가 깨져서 표시됩니다. 텍스트가 표시되지 않습니다.

A: Telegram sendMessage HTML 모드를 사용하는 경우: (1) 여는 태그와 닫는 태그가 일치하는지 확인합니다(<b>텍스트</b>). (2) <br>을 사용하지 마세요 — \n을 사용하세요. (3) 모든 특수 문자(<, >, &)가 이스케이프 처리되었는지 확인합니다.

Q: 에이전트/사용자 정의 로직을 위해 메시지 형식을 어떻게 변경합니까?

A: lib/telegram.ts에서 formatMessage() 수정. Parse mode를 MarkdownV2(더 많은 형식)로 전환하거나 HTML로 유지합니다. 참고: MarkdownV2는 훨씬 더 엄격한 이스케이프 규칙이 있습니다.

Q: 그룹 chat_id를 찾을 수 없습니다. getUpdates가 비어 있습니다.

A: 봇이 먼저 그룹에 추가되었는지 확인합니다. getUpdates는 새 업데이트가 있을 때만 표시합니다. 봇이 추가된 후 그룹에 /start@YourBotUsername 또는 아무 메시지나 보내 getUpdates에 트리거합니다.

Q: Vercel Edge Functions에서 작동합니까?

A: 예. fetch는 Edge runtime에서 지원됩니다. 단, Edge에서는 console.error가 다르게 동작할 수 있으며(수집되지 않음), 수집을 위해 logger를 통해 기록하는 것을 고려하세요.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Best practices for Remotion - Video creation in React

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Scaffold trang `/admin` CRM-style trực quan vào Next.js project (Supabase backend) — KHÔNG còn variant Vercel KV. Layout 1CRM-inspired: sidebar 3 tab (Tổng quan / Khách hàng / Email marketing) + popup nhập mã đăng nhập. **Tab Tổng quan**: 4 KPI cards (Tổng leads / Đã thanh toán / Tỷ lệ chuyển đổi / Doanh thu) có delta arrow vs kỳ trước, line chart leads+paid+revenue theo ngày dùng `recharts`, donut trạng thái paid/pending/expired, period selector pill 7d/30d/90d, list 5 thanh toán gần nhất. **Tab Khách hàng**: bảng leads + search + filter trạng thái + filter ngày + CSV export. **Tab Email marketing**: form soạn chiến dịch (tên + tiêu đề + body chọn định dạng **văn bản thường HOẶC HTML** + placeholder `{{name}}` + audience 4 loại all/paid/pending/last_days + tên người gửi) — có nút **Xem trước** mở modal iframe render đúng email thật (text → auto convert + wrap khung; HTML fragment → wrap khung, HTML đầy đủ giữ nguyên) → gửi bulk qua SMTP (reuse `nodemailer` của `biz-email-setup` — KHÔN

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Deploy landing page / website (React, Next.js, Vite, static HTML) lên Vercel — tự kiểm tra Vercel CLI hoặc Vercel MCP đã cài chưa, nếu chưa thì cài qua npm/pnpm và hướng dẫn user authenticate (vercel login), tự detect framework (Next.js / Vite / CRA / static), auto-generate vercel.json khi cần, chạy `vercel --prod`, trả về live URL + inspect URL + hướng dẫn custom domain. USE WHEN user says 'deploy lên vercel', 'đẩy landing page lên vercel', 'publish website', 'go live', 'deploy nextjs', 'deploy react', 'deploy vite', 'tạo URL live', 'đưa lên production', 'biz-deploy-vercel', 'host landing page', 'vercel deploy', 'ship lên vercel', 'đẩy code lên vercel'. Skill này áp dụng cho cả case dùng Vercel CLI lẫn Vercel MCP server.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Setup SMTP-based email auto-responder cho landing page bán hàng — tự động gửi email sau khi user điền form (tên/SĐT/email), KHÔNG cố định vào provider nào. Skill này (1) hỏi user chọn SMTP provider (Gmail/Google Workspace, Resend SMTP, SendGrid, Mailgun, Brevo, Zoho, hoặc SMTP custom của hosting VN) và detect Next.js stack đang dùng, (2) đọc context từ `offer.json` + `conversion-copy.md` (từ `/biz-offer-alex-hormozi`) HOẶC đọc trực tiếp Next.js project đã build qua `ui-ux-pro-max` (file `app/page.tsx`) HOẶC đọc HTML landing page đã deploy để hiểu offer, dream outcome, mechanism, bonus. Lưu ý: skill `biz-sales-page-layout` đã DEPRECATED 2026-05-14 — pipeline mới skip layout/copy.json, (3) **draft 2 email**: email A — auto-responder gửi cho lead (warm welcome + deliver lead magnet/booking confirm/payment link tuỳ offer type), email B — notification gửi cho owner (lead alert + thông tin liên hệ), (4) **show draft cho user duyệt và chỉnh sửa** trước khi wire vào code, (5) cài `nodemailer`

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Cài chatbot AI dạng floating widget góc dưới phải vào project Next.js có sẵn (App Router hoặc Pages Router), responsive đầy đủ web + tablet + mobile, gọi LLM qua OpenRouter (mặc định google/gemini-3-flash-preview, có thể đổi sang anthropic/claude-sonnet-4.6), streaming response, RENDER MARKDOWN trong assistant bubble (bold / list / link / code) qua react-markdown + remark-gfm — KHÔNG hiện raw **bold** dạng text — có knowledge base từ FAQ + tài liệu sản phẩm/khóa học do user cung cấp để chatbot tự động trả lời khách hàng, và TỰ ĐỘNG EXTRACT + LƯU LEAD (tên + SĐT + email) vào Upstash Redis/Vercel KV dưới namespace `chat-lead:{phone}` khi user cung cấp đầy đủ thông tin trong chat — dedupe theo SĐT, TTL 90 ngày, fire-and-forget không block stream. Skill tự detect số lượng project Next.js — nếu có nhiều hơn 1 sẽ hỏi user chọn project nào để cài; tự detect TypeScript/JS, App Router/Pages Router, Tailwind/CSS module, KV/Upstash availability (skip auto-save nếu chưa có); tự install react-markd

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Đóng gói offer **không thể chối từ** (grand slam offer) theo phương pháp Alex Hormozi ($100M Offers) — nhận input pains/gains/customer jobs theo Value Proposition Design (Osterwalder 2014) + sản phẩm/dịch vụ, rồi sinh ra: (1) markdown report tiếng Việt đầy đủ Value Equation scoring + Core Offer + 3-4 Bonus stack + Guarantee + Urgency + Pricing, (2) offer.json structured cho pipeline downstream (`ui-ux-pro-max` build sales page production-ready), (3) conversion copy block (headline + subheadline + CTA) sẵn sàng paste landing page. 2 input mode: (B) user paste sẵn pains + gains + product, (C) user chỉ có sản phẩm — skill phỏng vấn theo VPD framework để surface pain/gain trước. Bonus stack mode hybrid: skill brainstorm 5-7 candidate có justification value rồi user pick/chỉnh 3-4 cái. Tiếng Việt thuần (xưng anh/chị), giá VND charm pricing (X99K), 3-tier decoy structure. USE WHEN user says 'đóng gói offer', 'tạo offer', 'thiết kế offer Hormozi', 'grand slam offer', 'value stack', 'offer sta

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Nâng cấp copy sales page tiếng Việt — biến copy có sẵn (từ `offer.json` + `conversion-copy.md` của `/biz-offer-alex-hormozi`, HOẶC từ Next.js project đã build qua `ui-ux-pro-max`, HOẶC user paste block copy bất kỳ) thành **copy chất lượng cao chốt đơn**: headline punchy hơn, pain agitation đau hơn, mechanism rõ hơn, benefit cụ thể hơn, FAQ xử lý đúng objection của thị trường VN, CTA mạnh hơn. Skill này có 3 INTENSITY LEVEL: (1) **Polish** — light edit + power word + sửa nhịp; (2) **Conversion-optimized** — rewrite 5 block critical + tạo A/B variants cho hero/CTA; (3) **Sales-letter** — long-form story-driven full rewrite kèm P.S.. Mỗi section áp dụng đúng formula thuyết phục (PAR cho pain, BAB cho solution, FEP cho benefit, PVEN cho final CTA, Star-Chain-Hook cho testimonial). Output gồm 4 file: `copy-upgraded.md`, `copy-variants.md` (A/B test bank — 3 hero + 3 CTA + 3 final-CTA), `copy-changes.md` (diff Trước/Sau + lý do), `copy.json` (structured downstream). Giọng văn tiếng Việt thuầ

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

⚠️ DEPRECATED (2026-05-14) — Skill này KHÔNG còn trong pipeline chuẩn. Quy trình mới đã bỏ bước wireframe markdown trung gian: sau `/biz-offer-alex-hormozi` đi THẲNG sang `ui-ux-pro-max` để build Next.js sales page production-ready (có design language nhất quán: style + palette + font pairing, không phải Tailwind defaults). Lý do deprecate: wireframe markdown làm thêm 1 bước trung gian không tạo giá trị — `ui-ux-pro-max` đọc trực tiếp `offer.json` đã đầy đủ anchor + core + bonus + guarantee + urgency + pricing rồi ra HTML production luôn. Chỉ dùng skill này KHI user EXPLICITLY yêu cầu wireframe markdown để review/in/share trước khi build code (rare case). KHÔNG trigger trên các keyword 'tạo sales page', 'tạo landing page bán hàng' nữa — những keyword đó giờ trigger `ui-ux-pro-max`.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

End-to-end Sepay VietQR payment infrastructure cho landing page Next.js — guide user setup Sepay account + bank linking + scaffold đầy đủ payment flow trong project. Skill này xử lý 7 phase: (0) detect Next.js stack + **HỎI user chọn lead store backend** (Vercel KV hoặc Supabase Free Postgres — default recommend Supabase do free tier rộng hơn ~50× + có SQL + admin UI), (1) GUIDE user đăng ký Sepay account tại my.sepay.vn + link bank account VN (Vietcombank/Techcombank/MB/ACB/VPBank/BIDV/...) + lấy API key + setup webhook URL trên Sepay dashboard, (2A nếu chọn KV) setup Vercel KV (install `@vercel/kv` + KV namespace + 4 env vars KV_*), (2B nếu chọn Supabase) setup Supabase project (tạo project tại Singapore region + chạy migration SQL tạo 4 bảng leads/phone_index/order_counter/webhook_dedup với pg_cron TTL cleanup + RLS deny-all + lấy SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY + Vercel Cron ping `/api/health` mỗi 6 ngày chống auto-pause), (3) scaffold `lib/leads-store.ts` (thin re-export

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Convert a single MP3 voiceover file into a single HeyGen avatar lip-sync MP4 video. Single-purpose — no planning, no SRT, no chunking, no Remotion compositing. Hybrid path — REST upload (helper script) + HeyGen MCP for video creation, because the post-2026 MCP no longer exposes an asset-upload tool. Reads avatar look pool (`HEYGEN_AVATAR_LOOKS`) from `.env` and HeyGen API key (`HEYGEN_API_KEY`) for the upload step. USE WHEN user says "tạo video heygen từ mp3", "mp3 to heygen", "heygen mp4 từ audio", "convert mp3 sang heygen video", "tạo avatar video từ file mp3", "lip sync mp3 heygen", "biến mp3 thành video heygen", or any time the user has exactly one MP3 file and wants exactly one HeyGen avatar MP4 out.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Demand-side market research cho digital product / service ở Việt Nam — đo cầu thật + WTP + channel-market fit + defensibility qua keyword volume, marketplace sales (Shopee/Unica/Edumall/Gitiho/KyNa), community signal (FB groups, TikTok hashtag, Zalo OA), competitor pricing, unit economics — không phải macro PESTEL. Output: keyword portfolio + Niche Score 100 điểm 7-dimension có evidence + go/no-go decision + falsification protocol. Force triangulate ≥3 sources cho mỗi claim quan trọng. USE WHEN user says 'nghiên cứu thị trường', 'market research', 'validate niche', 'keyword research', 'demand validation', 'niche có nên làm không', 'có ai làm chưa', 'thị trường đủ lớn không', 'có lời không', 'chọn ngách', 'kiểm tra cầu'. Cũng trigger khi user mô tả 1 ý tưởng kinh doanh cụ thể và muốn check feasibility. Skill này KHÔNG fit: high-ticket transactional sale (BĐS, ô tô), B2B enterprise (sales cycle 6+ tháng), hyper-local geo micro-market (1 toà nhà, 1 phường). KHÔNG làm: macro PESTEL/Porter

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Transcribe an MP3 voiceover into an SRT subtitle file and a word-level segments JSON using local OpenAI Whisper. Input is a single MP3 path; output is `<stem>.srt` + `<stem>_segments.json` written next to the MP3 (or to a custom output dir). USE WHEN the user says 'extract srt from mp3', 'trích srt từ mp3', 'transcribe voiceover', 'tạo file srt và segments', 'whisper transcribe', 'bóc transcript có timestamp', 'extract voiceover segments', 'tách SRT từ audio', or any time a downstream skill (plan-short-video-edit, heygen-remotion-short-video-editor) needs an SRT + word-level segments JSON from a voiceover MP3 and one does not already exist.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Convert Vietnamese/English script text to MP3 voiceover using ElevenLabs TTS API. Calls POST /v1/text-to-speech/{voice_id}, streams audio bytes, writes MP3 directly. Locked to Hoang's brand voice ID by default. USE WHEN user says 'tạo mp3 elevenlabs', 'elevenlabs tts', 'eleven labs voice', 'text to speech elevenlabs', 'tạo voiceover elevenlabs', 'đọc text bằng elevenlabs', 'tts elevenlabs to mp3', 'eleven labs script to mp3', 'voiceover bằng elevenlabs', 'giọng elevenlabs'.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

End-to-end orchestrator: **script → final MP4 16:9 (1920×1080) podcast-keynote**.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

End-to-end short-video pipeline — từ kịch bản (Việt/Anh) ra MP4 TikTok/Reels 9:16 hoàn chỉnh. Orchestrator 3 phase ghép skill có sẵn — (1) TTS provider chọn được giữa `mkt-elevenlabs-tts-to-mp3` (default, voice của Hoàng) hoặc `mkt-video-script-to-mp3` (MiniMax speech-2.8-hd), (2) checkpoint user duyệt MP3, (3) `heygen-mp3-to-mp4` lip-sync avatar HeyGen, (4) delegate Phase 3 packaging cho sub-agent `mkt-full-video-phase3-packager` (transcribe + scene outline + checkpoint + fan-out N scene writers parallel + scaffold + preview Studio). API key + voice ID cho cả 2 provider lưu ở `.env`. USE WHEN user nói "tạo full video từ script", "script to tiktok video", "pipeline full video heygen + hyperframe", "tạo video từ kịch bản đến mp4", "elevenlabs heygen hyperframe full pipeline", "minimax heygen pipeline", "kịch bản ra video tiktok", hoặc có sẵn 1 script + (optional) ảnh b-roll và muốn ra MP4 9:16 đóng gói có captions, SFX, b-roll.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Build a TikTok/Reels/YouTube **landscape 16:9 (1920×1080)** short video from a pre-recorded talking-head MP4 using HyperFrames — transcribe Vietnamese audio, scaffold a complete preview-ready 1920×1080 project with a slide pane (1200px) on the left and a floating avatar frame on the right, GSAP timeline có SPLIT↔PIP transitions (slide full-screen + avatar PIP corner thumbnail), breathing yoyo zoom + beat-driven punch-in trên avatar, 5 scene compositions kiểu "tier-row" pattern (post-stack hook / chats-stack problem / hero-orb solution / counter-row recap / terminal-row CTA), optional cream-paper editorial infographic image slots với `<img onerror>` fallback, 6 SFX wired tại scene boundaries, **NO captions** (giọng avatar là spine). USE WHEN user nói "edit video 16:9 từ HeyGen MP4", "scaffold project landscape", "podcast keynote editor", "talking head + slide editor", "build hyperframes 1920×1080", "tạo project 16:9 từ avatar mp4", "scaffold 16:9 hyperframes", "hyperframe landscape vide

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Pipeline đóng gói: từ MP4 quay sẵn (talking head 9:16) tới HyperFrames preview project hoàn chỉnh có captions, SFX, zoom hooks, b-roll toàn cảnh có hiệu ứng rich, và CTA finale.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Audit bài Facebook / Reels / YouTube để phát hiện 4 downward drivers (over-branding, over-production, stock imagery, standardized aesthetic) + frequency-over-quality. Mỗi anti-pattern bị drop ~75% performance nếu hiện diện. USE WHEN user says 'audit content', 'check anti pattern', 'content có lỗi gì không', 'kiểm tra over branding', 'vì sao video bị chết', 'vì sao post không ai xem', 'content audit', 'kiểm tra chất lượng content'.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Tìm viral pattern ở ngành khác (bác sĩ, luật sư, tài chính, bất động sản, thủ công) có thể apply cho niche AI/automation của Hoang. Ngách AI educator VN còn ít format được khai thác — cross-industry adaptation là blue ocean. USE WHEN user says 'tìm format ngành khác', 'cross industry research', 'học format từ ngành khác', 'blue ocean format', 'tìm format chưa ai làm', 'adapt format từ niche khác', 'cross industry viral'.

lexuanthanhhy/MKT.LANDINGPAGE.SKILLS

Rewrite CTA kiểu autocratic (Mua ngay! Follow ngay! Đăng ký liền!) sang 3 variants Democratic / Benevolent / Laissez-faire — reach 85% dân số thay vì chỉ 5% (action-based). Áp dụng cho landing page, caption FB, CTA video, email. USE WHEN user says 'rewrite cta', 'sửa cta', 'cta không autocratic', 'viết lại kêu gọi hành động', 'cta cho landing page', 'cta nhẹ nhàng hơn', 'inclusive cta', 'cta democratic benevolent'.

관련 스킬