Volta Series
E-commerce and operations platform for agro-processing equipment

Overview
A full-stack e-commerce platform for industrial agricultural processing equipment, built for the Nigerian market (NGN base currency). It pairs a public, SEO-indexed storefront (catalogue, variants, cart, invoice/Paystack checkout, multi-currency display) with a role-based admin back office for managing products, orders, users, analytics, and audit logs.
My role
Sole developer. Full-stack scope: data model and Postgres migrations, authentication and role-based access control, the public storefront, the admin back office, Paystack/invoice checkout, transactional email, media handling, SEO, and deployment configuration.
The problem
Sellers of industrial agricultural processing equipment need an online catalogue to showcase high-value machines and capture orders, plus a back office where a small, mixed-seniority team can run the business safely. The catalogue must be browsable and search-engine friendly, checkout must work for buyers who prefer to pay by invoice, and day-to-day operations (catalogue edits, order status changes) need role-appropriate permissions and an audit trail rather than unchecked admin access.
The solution
A Next.js App Router application split into public, auth, customer-dashboard, and admin route groups. The storefront serves a product catalogue with parent/child variants, search, cart, and dual checkout (invoice "pay later" plus Paystack). The admin console is governed by a central, server-enforced permission map across five roles, with maker-checker approval workflows for sensitive changes, append-only audit logging, and transactional inventory handling. Payments are reconciled through HMAC-verified, idempotent Paystack webhooks.
Key features
- Public storefront: category browsing, product detail (image gallery, specs, rich-text descriptions), parent/child product variants, search, related products, cart with localStorage persistence, and multi-currency display (NGN base with USD/EUR/GBP conversion).
- Dual checkout: invoice ("pay later") and Paystack; the Paystack path uses HMAC-SHA512 webhook signature verification with idempotent order and inventory processing.
- Role-based admin back office (SUPER_ADMIN / ADMIN / MANAGER / SUPPORT / CUSTOMER) with server-side permission guards: product & category CRUD (TipTap rich text, Cloudinary uploads with drag-to-reorder, up to 8 images), order management, and hierarchy-aware user role management.
- Maker-checker approval workflows: managers/support request order-status changes and product/category edits; admins review and approve, with the stored form payload replayed to apply the change.
- Authentication: NextAuth v5 (Google OAuth + credentials), bcrypt hashing, email verification and password reset via time-limited tokens over Resend, and guest checkout with optional account creation.
- Analytics & audit: dashboard for revenue over time, orders by status, product views, and top products/categories, plus append-only audit logging of catalogue, order, payment, and approval actions.
Architecture & engineering
Next.js 16 App Router with route groups and server components; mutations run through server actions. PostgreSQL via Prisma 7 using the pg driver adapter, evolved over ten incremental migrations (15 models). NextAuth uses JWT sessions but re-fetches the user role from the database per request, keeping the DB the source of truth for authorisation. RBAC is a static permission map plus a numeric role hierarchy, enforced by reusable server guards (page redirects and API throws). Inventory deduction is a flag-guarded, idempotent DB transaction shared by the payment webhook, the verify callback, and admin order transitions. Cloudinary handles media, Resend handles transactional email, storefront pages use cache-tag revalidation, admin-authored HTML is sanitized with DOMPurify, and security headers are set in next.config.ts.
The hardest problem
A card payment can be confirmed by two independent paths (the asynchronous Paystack webhook and the synchronous browser redirect to the verify page), creating a risk of double order-completion and double inventory deduction. This was handled by making both paths idempotent: a PENDING-status guard means whichever arrives first wins and the other no-ops, an inventoryDeducted boolean plus a DB transaction prevent double-decrementing stock, and the webhook verifies the HMAC signature and that the amount paid covers the order total before marking it paid.
Key learnings
When an outcome can be confirmed by two independent channels (webhook + redirect), design for idempotency up front: a status guard plus a dedicated flag is simpler and safer than coordinating the two. Authorisation belongs on the server. And persisting an approval's full form payload as JSON and replaying it on approval keeps a maker-checker flow simple and auditable.
Want to see it in action?
Explore the live product, or reach out to talk through the build.