Biz Setup Sepay Payment — End-to-End VietQR Payment-Infrastruktur für Next.js
Dieser Skill baut die komplette Zahlungsinfrastruktur für eine Next.js-Landingpage mit Sepay (dem vietnamesischen VietQR-Zahlungsgateway) auf. Nach Abschluss: Kunde füllt Formular aus → Bestellung wird erstellt → QR-Code wird gescannt → Überweisung wird getätigt → Sepay-Webhook wird empfangen → Zahlung wird verifiziert + Lead wird gefunden + E-Mail/Telegramm-Benachrichtigung wird ausgelöst. Vollautomatisch, keine manuelle Admin-Bestätigung erforderlich.
Philosophie: Sepay ist der Weg des geringsten Widerstands für Vietnam – VietQR + native Bank-API, der Kunde muss sich für nichts registrieren, scannt den QR-Code mit der vorhandenen Banking-App. Gebühr 1,5-2 %, günstiger als Stripe (4-5 %), Abrechnung T+1, Free Tier angemessen für Landingpages mit < 100 Bestellungen/Monat.
Warum ein Lead-Store?: Der Sepay-Webhook sendet nur
content(den vom Kunden bei der Überweisung eingegebenen Text) + Betrag. Sepay WEISS NICHT, wer der Kunde ist. Ein lokaler Lead-Store ist erforderlich, umcontentauf den Lead{name, phone, email, product, amount}abzubilden.
2 Lead-Store-Optionen – der Benutzer wählt in Phase 0:
- Vercel KV (Upstash Redis) – Einrichtung in 2 Minuten, natives TTL, kostenlos 30.000 Befehle/Monat → < 500 Bestellungen/Monat
- Supabase Free (Postgres) – Einrichtung in 5 Minuten, SQL-Abfragen + Studio-UI, Free Tier etwa 50× großzügiger → < 2000 Bestellungen/Monat. EMPFOHLEN.
API-Routen (/api/checkout, /api/sepay-webhook, /api/admin/leads) sind anbieterunabhängig durch den dünnen Re-Export in lib/leads-store.ts. Wechsel des Anbieters später = 1 Zeile ändern.
Der Skill registriERT NICHT selbstständig ein Sepay-Konto für den Benutzer (erfordert KYC für Unternehmen + Bankverbindung). Der Skill führt den Benutzer Schritt für Schritt an und verdrahtet dann den gesamten Code.
Wann dieser Skill verwendet werden sollte
- Der Benutzer startet ein digitales Produkt / einen Kurs / ein Coaching und benötigt eine Online-Zahlungsabwicklung.
- Der Benutzer hat bereits eine Next.js-Landingpage (via
ui-ux-pro-maxoder ähnlich) und möchte einen Payment-Flow hinzufügen. - Der Benutzer bereitet die Einbindung von
biz-email-setupoderbiz-telegram-payment-notifyvor, hat aber noch keine Zahlungsinfrastruktur → dieser Skill ist die VORAUSSETZUNG.
Diesen Skill NICHT verwenden, wenn:
- Der Benutzer Stripe/PayPal (internationale Kartenzahlungen) verwendet → anderer Anwendungsfall.
- Der Benutzer manuelle Banküberweisungen + eigene Verifikation akzeptiert (geringes Volumen, nicht empfohlen).
- Der Benutzer bereits eine Zahlungsinfrastruktur hat und nur noch Benachrichtigungen benötigt → direkt zu
/biz-email-setupoder/biz-telegram-payment-notify.
Gesamtworkflow (7 Phasen)
Phase 0: ERKENNEN des Next.js-Stacks + FRAGEN nach Anbieter (KV / Supabase) — GATE
↓
Phase 1: ANLEITEN des Benutzers zur Einrichtung des Sepay-Kontos — manuelles GATE, warten auf Benutzer
↓
Phase 2A (wenn KV): EINRICHTEN von Vercel KV (Paket installieren + Umgebungsvariablen + KV-Namespace)
Phase 2B (wenn Supabase): EINRICHTEN von Supabase (Projekt erstellen + SQL-Migration + service_role)
↓
Phase 3: ERSTELLEN von lib/leads-store.ts (Re-Export-Wrapper) + lib/leads-{kv|supabase}.ts (Implementierung)
↓
Phase 4: ERSTELLEN der /api/checkout-Route (Formular → Bestellung → speichern)
↓
Phase 5: ERSTELLEN der /api/sepay-webhook-Route (verifizieren + suchen + Platzhalter für Seiteneffekte)
↓
Phase 6: EINBETTEN von VietQR in die Landingpage (Auswahl von 1 von 3 UX-Mustern)
↓
Phase 7: TESTPLAN mit 5 Stufen (Store → Checkout → Webhook → Produktion → (nur Supabase) Cron-Ping)
Phase 0 + 1 haben ein Gate, das auf den Benutzer wartet. Die Phasen 2/3/4/5/6 führt der Skill aktiv aus.
Phase 0 — Next.js-Stack erkennen + Anbieter wählen
0.1 Stack erkennen
test -f next.config.js -o -f next.config.mjs -o -f next.config.ts && echo "Next.js: ✓"
test -d app && echo "Router: App"
test -d pages && echo "Router: Pages"
test -f tsconfig.json && echo "TS: ✓"
test -f pnpm-lock.yaml && echo "pkg: pnpm" || test -f yarn.lock && echo "pkg: yarn" || echo "pkg: npm"
Wenn nicht Next.js: STOPPEN. Melden: "Dieser Skill unterstützt derzeit nur Next.js. Ihr aktuelles Stack ist [X]. Soll ich die Logik auf Vite/static mit Vercel Function portieren?" — auf Benutzer warten.
Wenn sowohl app/ als auch pages/: Benutzer fragen, welches verwendet wird.
0.2 Vorhandene Infrastruktur prüfen
ls app/api/ pages/api/ lib/ 2>/dev/null
grep -E '"@vercel/kv"|"@supabase/supabase-js"|"@upstash/redis"' package.json
grep -E "SEPAY|KV_REST|SUPABASE_" .env.local 2>/dev/null
0.3 Benutzer nach Anbieter fragen
Ich habe festgestellt, dass Sie Next.js [App/Pages] Router + TypeScript + [pnpm/yarn/npm] verwenden.
Wo möchten Sie den Lead-Store speichern?
1. Vercel KV (Upstash Redis)
✓ Schnelle Einrichtung in 2 Minuten (1-Klick im Vercel Dashboard)
✓ Natives TTL – keine Sorgen wegen Projekt-Pause
✗ Nur 30.000 Befehle/Monat kostenlos → ~500 Bestellungen/Monat
✗ Kein SQL – Admin-Filter komplex und schwierig
2. Supabase Free (Postgres) — EMPFOHLEN
✓ Großzügigstes Free Tier (500 MB DB + 5 GB Bandbreite, unbegrenzte Abfragen)
✓ Postgres = mächtige SQL-Abfragen, Supabase Studio UI zum Anzeigen der Daten
✓ Type-sicheres TypeScript automatisch generiert
✓ Einrichtung in 5 Minuten (Konsole + 2 Umgebungsvariablen einfügen)
⚠ Projekt pausiert automatisch nach 7 Tagen Inaktivität → ich richte einen Vercel Cron-Ping zu `/api/health` alle 6 Tage ein, um dies zu beheben
⚠ TTL manuell über pg_cron (bereits in der SQL-Migration enthalten)
→ Geeignet für < 2000 Bestellungen/Monat, einfache Skalierung von Admin-Funktionen
Wählen Sie 1 oder 2?
GATE: ERST mit Phase 1 fortfahren, wenn der Benutzer gewählt hat. Standardmäßig Supabase vorschlagen, aber explizite Auswahl erzwingen.
Nach der Auswahl des Benutzers den Plan zusammenfassen:
Sie haben sich für [Vercel KV / Supabase] entschieden. Ich werde:
- Sie bei der Einrichtung des Sepay-Kontos anleiten (5-10 Minuten)
- Den Lead-Store gemäß dem gewählten Anbieter einrichten
- Code verdrahten: lib/leads-store.ts + /api/checkout + /api/sepay-webhook + VietQR einbetten
- Testen
Sind Sie bereit für Phase 1?
Phase 1 — Benutzer bei der Sepay-Konto-Einrichtung anleiten
Lesen Sie references/sepay-account-setup.md für die vollständige Anleitung. 5 Schritte für den Benutzer zusammenfassen:
Schritt 1 — Registrierung bei my.sepay.vn
- Rufen Sie https://my.sepay.vn auf → Registrieren
- Telefonnummer, E-Mail, Passwort eingeben → OTP verifizieren
- Profil vervollständigen: Name + Personalausweisnummer/CMND (privat) oder Steuernummer (Firma)
Schritt 2 — Bankkonto verknüpfen
Dashboard → Bankkonten → Konto hinzufügen:
- Bank auswählen (Vietcombank, Techcombank, MB, ACB, VPBank, BIDV, VietinBank, TPBank, ...)
- Kontonummer + Kontoinhabername eingeben (muss mit der Banking-App übereinstimmen)
- Verifizierung automatisch (über Bank-API) oder manuell (Überweisung von 1.000-10.000 VND mit dem von Sepay bereitgestellten Verifizierungscode)
Schritt 3 — API-Schlüssel abrufen
Dashboard → Einstellungen → API & Webhook → Neuen API-Schlüssel erstellen:
- Name:
landing-page-prod - Berechtigungen:
webhook:read - Schlüssel kopieren (wird nur einmal angezeigt)
Schritt 4 — Webhook-URL einrichten
Gleiche Seite → Webhook → Webhook hinzufügen:
- URL:
https://yourdomain.vn/api/sepay-webhook(wird nach dem Deployment live sein) - Auth: API Key → den gerade erstellten Schlüssel einfügen
- Events:
transaction:incoming - Speichern
⚠️ Lokale Entwicklung: ngrok verwenden, um
localhost:3000für Sepay-Tests freizugeben.
Schritt 5 — Werte für den Skill einfügen
SEPAY_WEBHOOK_API_KEY=sk_xxxxxxxxxxxxxxxxxxxx
SEPAY_BANK_ACCOUNT_NUMBER=1023456789
SEPAY_BANK_NAME=Vietcombank
SEPAY_ACCOUNT_NAME=NGUYEN VAN A
GATE: ERST mit Phase 2 fortfahren, wenn der Benutzer alle Werte eingefügt hat.
Phase 2A — Vercel KV einrichten (nur wenn Benutzer in Phase 0 KV gewählt hat)
Vercel KV = verwalteter Upstash Redis. Kostenlos: 30.000 Befehle/Monat.
2A.1 KV-Namespace erstellen
Vercel Dashboard → Projekt → Storage → Create Database → KV (Redis) → Name sepay-leads → Erstellen.
4 Umgebungsvariablen werden automatisch generiert:
KV_URL=...
KV_REST_API_URL=...
KV_REST_API_TOKEN=...
KV_REST_API_READ_ONLY_TOKEN=...
Connect to Project klicken → Vercel injectiert die Umgebungsvariablen (Production + Preview + Development).
2A.2 Umgebungsvariablen lokal abrufen
npm i -g vercel # falls nicht vorhanden
vercel link
vercel env pull .env.local
2A.3 Paket installieren
pnpm add @vercel/kv # oder yarn / npm install
2A.4 Überprüfen
node -e "
const { kv } = require('@vercel/kv');
kv.set('test', 'hello').then(() => kv.get('test')).then(v => {
console.log('KV OK:', v);
return kv.del('test');
});
"
Erwarte KV OK: hello. Weiter zu Phase 3.
Phase 2B — Supabase Free einrichten (nur wenn Benutzer in Phase 0 Supabase gewählt hat)
Lesen Sie references/supabase-setup.md für die vollständige, detaillierte 6-Schritte-Anleitung. Zusammenfassung:
2B.1 Supabase-Konto + Projekt erstellen
- https://supabase.com → Start your project → Mit GitHub anmelden
- Neues Projekt:
- Name:
sepay-leads-<brand> - Datenbank-Passwort: Generate klicken → speichern
- Region: ⚠️ Southeast Asia (Singapur) (Standard us-east hat 250ms+ Latenz)
- Preise: Free
- Name:
- ~2 Minuten Provisionierung abwarten
2B.2 SQL-Migration ausführen
Sidebar → SQL Editor → New query → Inhalt von templates/supabase-migration.sql einfügen → Run.
Erstellt 4 Tabellen + Indizes + next_order_id()-Funktion + RLS aktivieren + pg_cron täglichen Cleanup-Job.
Ausgabe überprüfen: 4 Tabellen (leads/phone_index/order_counter/webhook_dedup) + 1 pg_cron-Job + next_order_id() gibt "DH000001" zurück.
Zähler zurücksetzen (da die Überprüfung den Zähler erhöht hat):
UPDATE order_counter SET current_value = 0 WHERE id = 1;
DELETE FROM leads WHERE order_id = 'DH000001';
2B.3 API-Zugangsdaten abrufen
Project Settings → API:
- Project URL kopieren (
https://xxxxx.supabase.co) service_role-Secret kopieren (NICHT denanon-Schlüssel – service_role umgeht RLS, wird auf dem Server verwendet)
2B.4 Umgebungsvariablen einfügen
SUPABASE_URL=https://xxxxx.supabase.co
SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJI...
Skill überprüft automatisch:
- URL entspricht
^https://[a-z0-9]+\.supabase\.co$ - Schlüssel dekodiert JWT, prüft
role: "service_role"(NICHTanon)
Wenn falsch → Fehler melden, erneutes Einfügen anfordern.
2B.5 Paket installieren
pnpm add @supabase/supabase-js
2B.6 Vercel Cron-Ping einrichten (gegen Pause)
Supabase Free pausiert automatisch nach 7 Tagen Inaktivität. Workaround: /api/health alle 6 Tage anpingen.
Skill erstellt app/api/health/route.ts (aus templates/api-health-route.ts) und fügt vercel.json mit folgendem Inhalt hinzu:
{
"crons": [
{ "path": "/api/health", "schedule": "0 3 */6 * *" }
]
}
⚠️ Vercel Cron Hobby ist auf 2 Cron-Jobs/Monat beschränkt. Das Muster */6 days = ~5 Mal/Monat → überschreitet das Kontingent. Optionen:
- Vercel Pro (20 $/Monat) – unbegrenzte Cron-Jobs
- GitHub Actions-Workflow
.github/workflows/keep-alive.yml(KOSTENLOS) – Skill erstellt diesen, wenn ein GitHub-Repository erkannt wird - cron-job.org (KOSTENLOS) – manuelle Einrichtung, URL
/api/healtheinfügen
Skill wählt standardmäßig Option 2 (GitHub Actions), wenn das Repo .git/config mit GitHub-Remote hat. Ansonsten wird die Anleitung für Option 3 ausgegeben.
2B.7 Überprüfen
node -e "
const { createClient } = require('@supabase/supabase-js');
const sb = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_ROLE_KEY);
sb.from('order_counter').select('current_value').eq('id', 1).single().then(r => {
if (r.error) { console.error('FAIL:', r.error.message); process.exit(1); }
console.log('Supabase OK. current_value =', r.data.current_value);
});
"
Erwarte Supabase OK. current_value = 0. Weiter zu Phase 3.
Phase 3 — lib/leads-store.ts + Implementierung erstellen
Anbieterabstraktion: API-Routen importieren aus lib/leads-store.ts und wissen nicht, ob darunter KV oder Postgres liegt.
Wenn KV gewählt (Phase 0)
2 Dateien erstellen:
lib/leads-store.ts← kopieren vontemplates/lib-leads-store-kv.ts(1 Zeileexport * from './leads-kv')lib/leads-kv.ts← kopieren vontemplates/lib-leads-kv.ts(KV-Implementierung, ~160 Zeilen)
Wenn Supabase gewählt
3 Dateien erstellen:
lib/leads-store.ts← kopieren vontemplates/lib-leads-store-supabase.ts(Re-Export Supabase)lib/leads-supabase.ts← kopieren vontemplates/lib-leads-supabase.ts(Postgres-Implementierung + listLeads für Admin)lib/supabase-admin.ts← kopieren vontemplates/lib-supabase-admin.ts(Admin-Client als gecachtes Singleton mit service_role)
GEMEINSAME API-Oberfläche (beide Implementierungen):
createLead(input) → { orderId, lead }— atomare ID + Einfügen + sekundärer IndexgetLeadByOrderId(orderId) → Lead | nullgetLeadByPhone(phone) → Lead | null— Fallback-SuchefindPendingLeadByAmountAndTime(amount, start, end) → Lead[]— Strategie 3 für Webhook-MatchingmarkLeadPaid(orderId, payment) → Lead | nullisTransactionProcessed(sepayId) → boolean— DeduplizierungmarkTransactionProcessed(sepayId) → void
TTL: 7 Tage ausstehend / 90 Tage bezahlt. KV über ex:-Parameter. Supabase über expire_at-Feld + pg_cron tägliche Löschung.
Späterer Anbieterwechsel = 1 Zeile in leads-store.ts ändern (API-Code NICHT berühren).
Lesen Sie references/leads-kv-schema.md (KV) oder references/supabase-leads-schema.md (Supabase) für die detaillierte Begründung.
Phase 4 — /api/checkout-Route erstellen
Lesen Sie templates/api-checkout-app-router.ts (App Router) oder templates/api-checkout-pages-router.ts (Pages Router). In den entsprechenden Pfad kopieren.
Beide Templates import { createLead } from '@/lib/leads-store' – anbieterunabhängig.
Ablauf:
Client POST /api/checkout { name, phone, email, productName, amount }
↓
Server:
1. Validieren (vietnamesisches Telefonformat, E-Mail-Regex, Betrag > 0)
2. createLead() → { orderId: "DH000123" }
3. Sepay-QR-URL generieren: https://qr.sepay.vn/img?acc=X&bank=Y&amount=Z&des=DH000123&template=compact
4. Zurückgeben { orderId, amount, bankInfo, content, qrUrl }
↓
Client: QR-Modal anzeigen ODER zu /checkout/DH000123 weiterleiten
Phase 5 — /api/sepay-webhook-Route erstellen
Lesen Sie templates/api-sepay-webhook-app-router.ts. Kopieren nach app/api/sepay-webhook/route.ts.
Template import ... from '@/lib/leads-store' – anbieterunabhängig.
Ablauf:
Sepay POST /api/sepay-webhook
Authorization: Apikey {SEPAY_WEBHOOK_API_KEY}
Body: SepayWebhookPayload
↓
Server:
1. Authentifizierung über timingSafeEqual prüfen – schützt vor Timing-Angriffen
2. Frühe Deduplizierung: isTransactionProcessed(payload.id) → frühzeitig 200 zurückgeben
3. Deduplizierung ZUERST aufzeichnen – idempotent, selbst wenn die Verarbeitung fehlschlägt
4. Filter transferType === 'in' (ausgehende ignorieren)
5. Multi-Strategie-Matching:
a. content → order_id (Regex /DH\d+/)
b. content → Telefonnummer (Regex /0\d{9}/) → getLeadByPhone
c. amount + timestamp ±30 Minuten → findPendingLeadByAmountAndTime (nur akzeptieren, wenn Länge === 1)
6. Unterzahlung ablehnen, ÜBERZAHLUNG akzeptieren
7. markLeadPaid(orderId, payment) → Store aktualisieren
8. Fan-Out-Seiteneffekte über try/catch-Schleife (KEIN Promise.all)
9. IMMER 200 zurückgeben (auch bei internem Fehler) – Sepay wiederholt bei non-200 bis zu 7 Mal mit Fibonacci-Verzögerung
Platzhalter für Seiteneffekte zur späteren Einbindung von biz-email-setup + biz-telegram-payment-notify.
Lesen Sie references/sepay-webhook-flow.md für die Begründung der Deduplizierung und das Wiederholungsverhalten.
Phase 6 — VietQR in die Landingpage einbetten
3 UX-Muster, Benutzer zur Auswahl auffordern:
| Muster | Wann | UX |
|---|---|---|
| A. Modal-Popup | Einzelprodukt / Impulskauf | Formular absenden → QR-Popup-Modal direkt auf der Ursprungsseite |
| B. /checkout/[orderId]-Seite | Mehrere Produkte / URL muss teilbar sein |