Ecommerce Template
Modern ecommerce template built with Next.js 16, React 19, Drizzle, Better Auth, Supabase, and Stripe.
Confirmly converts WhatsApp conversations into structured, payment-ready orders, verifies payment through Monnify, and sends a trusted digital receipt.
CommerceTypeScript · JavaScriptGitHub @Obiajulu-gifOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
WhatsApp-first AI order-to-payment assistant powered by Monnify and NVIDIA NIM.
Translation pending
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
Turn WhatsApp orders into verified payments.
Confirmly is a multi-merchant WhatsApp commerce platform: merchants sign
up, register a business, add a validated settlement bank account (with a
dedicated Monnify subaccount), and take structured WhatsApp orders that are
paid through Monnify, verified server-side, receipted, and settled to their
own bank account.
From chat to confirmed payment.
Scan the QR code or message the WhatsApp number to test a live order:
📱 WhatsApp: +234 704 486 0938
🌐 Live demo: confirmly-alpha.vercel.appRead the full README ↗Turn WhatsApp orders into verified payments.
Confirmly is a multi-merchant WhatsApp commerce platform: merchants sign up, register a business, add a validated settlement bank account (with a dedicated Monnify subaccount), and take structured WhatsApp orders that are paid through Monnify, verified server-side, receipted, and settled to their own bank account.
From chat to confirmed payment.
Scan the QR code or message the WhatsApp number to test a live order:
📱 WhatsApp: +234 704 486 0938
🌐 Live demo: confirmly-alpha.vercel.app
Demo order script (2 min): Send "I need two black polo shirts, large size, delivered to Yaba." → watch the AI extract intent → confirm → pay via Monnify sandbox → receive a verifiable receipt.
Merchant settlement bank account
→ Monnify account-name validation (resolved name, never free-typed)
→ Monnify subaccount creation (idempotent) → store subAccountCode
→ customer confirms order
→ initialize Monnify checkout / dynamic transfer account
→ attach merchant subAccountCode via incomeSplitConfig (split = exactly 100)
→ customer pays the Monnify-generated account
→ verify payment server-to-server
→ Monnify settles the merchant share to the registered bank account
Checkout account vs settlement account. The account a customer pays is a temporary Monnify-generated checkout account. The merchant's own bank account is only ever a settlement destination — encrypted at rest, displayed masked, and never shown as a payment target.
Payment ≠ settlement. A verified payment creates a PENDING settlement;
only a Monnify settlement event or reconciliation marks it SETTLED.
Subaccount activation. Confirmly runs one Monnify platform account; each
merchant gets a subaccount. If the Monnify Sub Account feature is not
enabled on the sandbox account, profiles are marked ACTIVATION_REQUIRED,
merchant-routed checkout is paused (MONNIFY_SUBACCOUNT_ENABLED=true) or
payments fall back to the platform account with a visible dashboard
warning (MONNIFY_SUBACCOUNT_ENABLED=false, the default). The split config
is never dropped silently.
/signup → /onboarding: business profile → settlement account (Monnify
name enquiry → explicit confirmation → subaccount) → starter catalogue and
delivery zones → WhatsApp store code. One shared WhatsApp number serves every
store: customers send START <STORECODE> (or pick from a list) before
ordering, and catalogues are never mixed.
Small WhatsApp merchants manually read scattered messages, calculate totals, send account details, inspect payment screenshots, match bank transfers, write receipts and track fulfilment. The result: wrong totals, unmatched transfers, duplicate fulfilment, disputes, and hours of wasted time — and a thriving market for faked payment screenshots.
A customer sends:
I need two black polo shirts, large size, delivered to Yaba.
Confirmly then:
nvidia/nemotron-3-nano-30b-a3b) to extract
structured order intent — never prices, never stock.Confirm order · Edit order · Talk to merchant buttons).PAID only after server-side verification — a
screenshot, redirect, chat claim or success-page visit can never do it.Expected server calculation for the demo order:
2 × Classic Polo Shirt NGN 24,000
Delivery to Yaba NGN 2,500
TOTAL NGN 26,500 (2,650,000 kobo)
flowchart LR
C[Customer on WhatsApp] -->|message| META[Meta WhatsApp Cloud API]
META -->|signed webhook| WA[/api/webhooks/whatsapp/]
WA --> ENGINE[Conversation engine]
ENGINE -->|free text only| NIM[NVIDIA NIM\nintent extraction]
NIM -->|Zod-validated intent| ENGINE
ENGINE --> DB[(PostgreSQL\nsource of truth)]
ENGINE -->|confirmed order| MNFY[Monnify sandbox\ninvoice + checkout]
MNFY -->|signed webhook| MW[/api/webhooks/monnify/]
MW -->|verify server-to-server| MNFY
MW -->|verified PAID| DB
DB --> RCPT[Public receipt\n+ QR verification]
DB --> DASH[Merchant dashboard]
ENGINE -->|replies, links, receipt| META
Rules the architecture enforces
prefers-reduced-motion aware/start: captures name, WhatsApp number
(auto-normalised to 234…) and delivery area before the first chat, then
deep-links into WhatsApp with a prefilled greeting — the engine recognises
the profile and skips the delivery question on future ordersVALID CONFIRMLY RECEIPT / RECEIPT NOT VALIDNext.js 15 (App Router, strict TypeScript) · Tailwind CSS 4 · PostgreSQL · Prisma 6 · Zod · jose + bcryptjs (sessions) · NVIDIA NIM (OpenAI-compatible) · Meta WhatsApp Cloud API · Monnify sandbox · qrcode · Vitest · Playwright · Vercel + Prisma Postgres.
app/
(auth)/login/ merchant sign-in
start/ customer onboarding → wa.me deep-link handoff
dashboard/ overview, orders, products, conversations, settings
pay/[orderReference]/ public payment page (display-only, never mutates)
receipt/[token]/ public receipt with QR
verify/receipt/[token] public receipt verification
api/
health/ liveness + safe integration diagnostics
webhooks/whatsapp/ GET challenge, POST signed inbound events
webhooks/monnify/ POST signed payment events
whatsapp/test/ admin-only test send
orders/[id]/confirm fulfilment progression
orders/[id]/invoice fresh invoice (new unique reference)
payments/[id]/verify merchant-triggered live verification
payments/reconcile stale-payment reconciliation (cron/manual)
demo/reset admin-only demo fixtures reset
components/ UI primitives + logo
lib/
ai/ NIM client, strict Zod schema, fallback parser
whatsapp/ signature check, payload parsing, send client
monnify/ auth token cache, invoice/verify client, signature
orders/ matching, conversation engine, drafts, audit, summaries
payments/ payment service (the ONLY path to PAID), webhook processor
auth.ts · db.ts · env.ts · logger.ts · money.ts · rate-limit.ts · receipts.ts
prisma/ schema, migrations, seed (Ada Styles)
scripts/ import-local-secrets · verify-no-secrets · sync-vercel-env · smoke-test
tests/ unit / integration / e2e / fixtures
npm install
# Option A: import credentials from a local secrets file (never committed)
npm run secrets:import # writes .env.local, prints names only
# Option B: copy .env.example to .env.local and fill it in by hand.
# Prisma CLI reads .env — put DATABASE_URL there too (unquoted).
npx prisma migrate deploy # apply committed migrations
npm run db:seed # Ada Styles catalogue + demo merchant login
npm run dev # http://localhost:3000
The seed creates the demo merchant Ada Styles with:
| Product | Price | Variants |
|---|---|---|
| Classic Polo Shirt | NGN 12,000 | Black/White/Navy × S/M/L/XL |
| Premium Hoodie | NGN 25,000 | Black/Grey × M/L/XL |
| Canvas Tote Bag | NGN 8,500 | — |
Delivery zones: Yaba 2,500 · Surulere 3,000 · Ikeja 3,500 · Lekki 5,000 · Pickup 0 (all NGN).
Names only — values live in .env.local (git-ignored) or Vercel.
| Variable | Purpose |
|---|---|
APP_URL | Public base URL (used in links/receipts) |
DATABASE_URL | PostgreSQL connection string (unquoted) |
AUTH_SECRET | Session JWT signing key (32 random bytes) |
ENCRYPTION_KEY | AES-256-GCM key for settlement bank details at rest |
MONNIFY_SUBACCOUNT_ENABLED | true = merchant-routed checkout via subaccounts |
MONNIFY_PLATFORM_FEE_PERCENT | Platform fee (MVP default 0) |
RECEIPT_TOKEN_SECRET | Receipt token MAC key |
DEMO_MERCHANT_EMAIL / DEMO_MERCHANT_PASSWORD | Seeded dashboard login |
WHATSAPP_ACCESS_TOKEN | Meta Graph API token |
WHATSAPP_PHONE_NUMBER_ID | Sending phone number id |
WHATSAPP_BUSINESS_ACCOUNT_ID | WABA id |
WHATSAPP_APP_SECRET | Webhook signature validation |
WHATSAPP_VERIFY_TOKEN | Webhook GET challenge |
WHATSAPP_GRAPH_VERSION | e.g. v23.0 |
WHATSAPP_PUBLIC_NUMBER | Display number for wa.me links (not a secret) |
NVIDIA_API_KEY / NVIDIA_BASE_URL / NVIDIA_ORDER_MODEL | NIM extraction |
MONNIFY_BASE_URL / MONNIFY_API_KEY / MONNIFY_SECRET_KEY / MONNIFY_CONTRACT_CODE | Payments (contract code ≠ wallet account number!) |
DEMO_MODE | true only for fixture-based review — shows a banner |
ADMIN_EMAILS | Comma-separated emails granted the /admin platform console (blank = disabled) |
NUDGE_ENABLED | true enables the hourly abandoned-order nudge (sends real messages; off by default) |
Meta (WhatsApp Cloud API)
https://<your-domain>/api/webhooks/whatsappWHATSAPP_VERIFY_TOKENmessages field.hello_world template always works.NVIDIA NIM
nvapi-…).https://integrate.api.nvidia.com/v1 with model
nvidia/nemotron-3-nano-30b-a3b, temperature 0.15, 20 s timeout, two
transient retries, one JSON-repair attempt, and a deterministic fallback
parser when the service is unavailable.Monnify sandbox
MK_TEST_…),
secret key, and contract code (from Contracts — not the wallet
account number).https://<your-domain>/api/webhooks/monnify.npm run lint # eslint
npm run typecheck # tsc --noEmit
npm run test # vitest unit + integration (needs DATABASE_URL)
npm run test:e2e # Playwright (starts next dev on :3100)
npm run build # production build
npm run secrets:scan # fails on any leaked secret
npm i -g vercel
vercel login
vercel link
node scripts/sync-vercel-env.mjs # pushes .env.local values, never echoes them
npx prisma migrate deploy # against the production DATABASE_URL
vercel deploy --prod
node scripts/smoke-test.mjs https://<your-deployment>
Set APP_URL to the final production domain (and re-deploy) so WhatsApp
links and receipts point at the right host. Then configure the two provider
callbacks above.
phone_number_id per merchant.See SECURITY.md. Highlights: signed webhooks (401 on mismatch), idempotent event handling, server-only secrets with a pre-push scanner, bcrypt + HTTP-only JWT sessions, merchant-scoped queries, high-entropy receipt tokens, and the no-screenshot payment rule — only a verified Monnify server response can mark an order paid.
Built by Obiajulu-gif (Emmanuel Okoye) for the hackathon, with Claude Code as pair programmer.
CRON_SECRET | Optional: authorizes the Vercel reconciliation cron |