mirror of
https://github.com/tiennm99/sepay-playground.git
synced 2026-05-31 14:17:24 +00:00
aa119868ce
Initial scaffolding from prior planning session: SePay/Upstash/Vercel research notes, locked tech-stack (SvelteKit 2 + Svelte 5 + JS + pnpm + Tailwind v4 + shadcn-svelte + adapter-vercel + @upstash/redis), technical-minimal design guidelines, HTML wireframes for / and /pay (form/awaiting/paid), plus handoff notes. No application code yet.
5.4 KiB
5.4 KiB
Tech Stack — sepay-playground
User-fixed stack. Research confirms feasibility.
Runtime / Framework
- SvelteKit (latest stable, 2.x) running on Svelte 5 (runes available —
$state,$derived,$effect). - JavaScript (not TypeScript). Use JSDoc for type hints where it pays for itself (Redis client, SePay payload shapes).
jsconfig.jsonfor editor IntelliSense +checkJs: falseto stay loose. - Package manager: pnpm (
pnpm-lock.yamlcommitted,packageManagerfield inpackage.json,.npmrcwithshamefully-hoist=false). - Node.js runtime for all server routes (incl. webhook). No edge runtime — keeps things uniform and avoids Upstash REST cold-path edge cases.
UI
- Tailwind CSS v4 (PostCSS plugin +
@import "tailwindcss"inapp.css). - shadcn-svelte (https://shadcn-svelte.com) — direct Svelte port of shadcn/ui, copy-paste components into
src/lib/components/ui. Install only what's used:button,input,label,card,badge,alert. Powered bybits-uiunder the hood. lucide-sveltefor icons (pulled by shadcn-svelte).mode-watcherfor light/dark mode (shadcn-svelte standard).
Storage
- Upstash Redis via
@upstash/redis(framework-agnostic; works fine in SvelteKit).- Env:
UPSTASH_REDIS_REST_URL,UPSTASH_REDIS_REST_TOKEN— canonical names forRedis.fromEnv(). - Auto-serializes JSON values → store plain objects.
set(key, val, { nx: true })for idempotent webhook dedup.set(key, val, { ex: 86400 })— orders TTL 24h (demo).
- Env:
Payments
- SePay VietQR.
- QR image:
https://qr.sepay.vn/img?acc=<ACC>&bank=<BANK>&amount=<N>&des=<CODE>&template=compact. No server-side QR lib. - Webhook: SePay POSTs to
/api/webhooks/sepaywithAuthorization: Apikey <SEPAY_WEBHOOK_API_KEY>. - Order code surfaces in payload
code(auto-extracted by SePay's dashboard prefix rule); fallback regex oncontent. - Dedup on payload
id(stable across retries — up to 7 retries / 5h). - Respond
200 {"success":true}within 30s.
- QR image:
Deployment
- Vercel via
@sveltejs/adapter-vercel(auto-detected by Vercel when present insvelte.config.js). - No
vercel.jsonneeded. - Env vars:
vercel env add ...per-environment,vercel env pull .env.localfor dev. - Local public URL for SePay during dev: ngrok (
ngrok http 5173— SvelteKit's default dev port).
Project Layout (target)
sepay-playground/
├── src/
│ ├── app.css # Tailwind import + tokens
│ ├── app.html
│ ├── lib/
│ │ ├── server/
│ │ │ ├── redis.js # Redis.fromEnv() client
│ │ │ ├── orders.js # createOrder / getOrder / markPaid
│ │ │ └── sepay.js # buildQrUrl, verifyWebhookAuth
│ │ ├── components/
│ │ │ ├── ui/ # shadcn-svelte components
│ │ │ ├── PayForm.svelte
│ │ │ ├── PayAwaiting.svelte
│ │ │ └── PayPaid.svelte
│ │ └── types.js # JSDoc typedefs (Order, SepayWebhookPayload)
│ └── routes/
│ ├── +page.svelte # /
│ ├── pay/
│ │ ├── +page.svelte # /pay (state machine: form → awaiting → paid)
│ │ └── +page.server.js # form action: createOrder
│ └── api/
│ ├── orders/[code]/+server.js # GET status (polled by /pay)
│ ├── webhooks/sepay/+server.js # POST receiver
│ └── dev/simulate-webhook/+server.js # gated by !PROD
├── static/
├── svelte.config.js # adapter-vercel
├── vite.config.js
├── tailwind.config.js (or v4 inline) + postcss.config.js
├── jsconfig.json
├── .npmrc
├── .env.example
├── package.json # packageManager: pnpm@...
└── pnpm-lock.yaml
Env Vars
| Name | Source | Notes |
|---|---|---|
SEPAY_API_TOKEN |
SePay dashboard | Reserved per user spec; unused by current flow |
SEPAY_WEBHOOK_API_KEY |
SePay dashboard | Compared against Authorization: Apikey <...> |
SEPAY_ACCOUNT_NUMBER |
SePay-bound bank account | Used in QR URL |
SEPAY_BANK_CODE |
qr.sepay.vn/banks.json | e.g. MBBank, Vietcombank, ACB |
UPSTASH_REDIS_REST_URL |
Upstash console / Vercel integration | |
UPSTASH_REDIS_REST_TOKEN |
Upstash console / Vercel integration | |
SEPAY_ORDER_PREFIX |
local config | Defaults SEVQR (VietinBank-compatible) |
All loaded via SvelteKit's $env/static/private (server-only — never leaks to client).
Out of Scope (demo)
- Real auth / users
- Multi-currency (VND only)
- Refunds, settlement reconciliation
- Production observability beyond
console.log+ Redis state
Confirmed decisions (vs. prior Next.js draft)
- SvelteKit replaces Next.js — Vercel still primary deploy target via official adapter.
- JavaScript replaces TypeScript — JSDoc typedefs cover the boundaries (Redis values, webhook payload).
- pnpm replaces npm — lockfile committed,
packageManagerfield set. - shadcn-svelte replaces shadcn/ui — same design system, Svelte port.
- Everything else (Upstash, SePay endpoints, env vars, ngrok-for-dev) unchanged.
Open Question
- Include a dev-only
POST /api/dev/simulate-webhook(gated by!import.meta.env.PROD) to test paid-state without a real transfer. Recommendation: yes.