A full-stack Next.js e-commerce site for selling lighters, with a secured admin dashboard, MongoDB (NoSQL) storage, and encrypted payments via Stripe (cards, SEPA, Revolut Pay) and BTCPay Server (Bitcoin).
- Framework: Next.js 16 (App Router, Server Components, Route Handlers)
- Database: MongoDB via Mongoose, Redis for caching / sessions
- Payments: Stripe (cards, SEPA Direct Debit, Revolut Pay) + BTCPay Server (Bitcoin)
- Auth: Custom JWT sessions in httpOnly cookies, bcrypt password hashing, TOTP 2FA for admins
- Styling: Tailwind CSS v4
- Node.js 20+
- A MongoDB instance — easiest options:
- MongoDB Atlas free tier (recommended, no local install)
- Or install MongoDB Community Server locally
- A Stripe account (test mode is fine to start)
- A BTCPay Server instance for Bitcoin payments (self-hosted or via a third-party host)
npm install
cp .env.example .env.localEdit .env.local:
| Variable | Where to get it |
|---|---|
MONGODB_URI |
Atlas connection string, or mongodb://localhost:27017/lighter-shop |
JWT_SECRET |
Generate with openssl rand -base64 32 — never use the placeholder in production |
ADMIN_EMAIL / ADMIN_PASSWORD |
Credentials for your first admin user (used only by the seed script) |
STRIPE_SECRET_KEY / STRIPE_PUBLISHABLE_KEY |
Stripe Dashboard → Developers → API keys |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Same publishable key, exposed to the browser |
STRIPE_WEBHOOK_SECRET |
Created when you set up the webhook (step 4) |
BTCPAY_HOST |
Full URL of your BTCPay Server instance, e.g. https://btcpay.yourdomain.com |
BTCPAY_API_KEY |
BTCPay Server → Account → API keys → Generate key (grant btcpay.store.invoices.create) |
BTCPAY_STORE_ID |
BTCPay Server → Store Settings → General (shown at the top) |
BTCPAY_WEBHOOK_SECRET |
Generated when you add a webhook in BTCPay Server → Store Settings → Webhooks |
npm run seed:admin # creates your first admin login
npm run seed:products # adds 6 demo lighters so the store isn't emptyPayments are confirmed via webhooks, not the checkout redirect — this is intentional and more secure (a user closing their browser early should never be the only thing that marks an order as paid).
Stripe, using the Stripe CLI for local dev:
stripe listen --forward-to localhost:3000/api/webhooks/stripeCopy the whsec_... it prints into STRIPE_WEBHOOK_SECRET.
BTCPay Server: in your store, go to Store Settings → Webhooks → Create Webhook.
Set the payload URL to https://<your-domain>/api/webhooks/btcpayserver and enable
the InvoiceSettled, InvoiceExpired, InvoiceInvalid, InvoiceReceivedPayment,
and InvoiceProcessing events. Copy the secret into BTCPAY_WEBHOOK_SECRET.
For local development use a tunnelling tool such as ngrok so BTCPay can reach your machine.
npm run dev- Storefront: http://localhost:3000
- Admin: http://localhost:3000/admin/login
- No card data ever touches our server — Stripe Elements collects card
details directly in the browser and tokenizes them; our backend only ever
sees a
PaymentIntentID. - No private keys stored — Bitcoin payments redirect to BTCPay Server's hosted invoice page; we only store the invoice ID.
- Webhook signature verification on both Stripe (
stripe-signature) and BTCPay Server (BTCPay-Sig) webhooks, using constant-time HMAC comparison. - Server-side price/stock validation — the checkout API never trusts prices sent from the browser; it always re-reads them from MongoDB.
- Admin auth: bcrypt-hashed passwords (cost factor 12), JWT sessions in httpOnly + SameSite cookies, login rate-limiting, and automatic account lockout after 5 failed attempts.
- Route protection:
middleware.jsblocks any/admin/*page without a valid session, andrequireAdmin()wraps every admin API route. - Input validation with
zodon every API route that accepts user input. - Security headers: CSP, HSTS, X-Frame-Options, X-Content-Type-Options,
Referrer-Policy (see
next.config.tsandmiddleware.js). - Generic auth error messages to avoid leaking which emails have admin accounts (user enumeration protection).
- Rate limiting on login and checkout endpoints.
- JWT_SECRET: the app will refuse to build/start in production with the placeholder secret. Generate a real one.
- Age-restricted product compliance: lighters are age-restricted (and in
some places, torch/novelty lighters specifically are restricted or banned)
in many jurisdictions. The site includes a basic age-gate, but you are
responsible for confirming:
- Stripe's acceptable-use / prohibited-business policies permit your specific products
- Any state/national age-verification or shipping requirements (e.g. some regions require adult-signature delivery for lighters)
- HTTPS in production is required — cookies are set
secure: trueoutside development, so the app will not maintain sessions over plain HTTP in production. - Switch Stripe to live keys once you've tested fully in test mode.
Point
BTCPAY_HOSTat your production BTCPay Server instance. - Consider swapping the in-memory rate limiter (
lib/rateLimit.js) for a Redis-backed one if you deploy more than one server instance — the current one is per-instance memory and resets on restart.
app/
(shop)/ # storefront - wrapped in AgeGate, Header, Footer, CartProvider
page.jsx # homepage
products/ # listing + detail pages
cart/ # cart page
checkout/ # checkout + success pages
admin/ # admin dashboard - protected by middleware.js
login/
dashboard/
products/
orders/
api/
products/ # public product API
checkout/ # validates cart, creates order + payment session
orders/[orderNumber]/ # public order status lookup
webhooks/
stripe/ # signature-verified Stripe webhook
btcpayserver/ # signature-verified BTCPay Server webhook
admin/ # all protected by requireAdmin()
login/ logout/ me/
products/ orders/ stats/
lib/
db.js # MongoDB connection (cached across hot reloads)
models/ # Product, Order, AdminUser (Mongoose schemas)
auth/ # session signing/verification, requireAdmin wrapper
payments/ # Stripe + BTCPay Server clients, webhook verification
rateLimit.js
utils-shop.js
components/
ui/ # Button, Input
shop/ # Header, Footer, AgeGate, CartContext, ProductCard, etc.
admin/ # ProductFormModal
scripts/
seed-admin.js
seed-products.js