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

Overview
Volta Series manufactures and distributes agro-processing equipment: cassava peelers, rice mills, hammer mills, the kind of machine a business buys once, for several million naira, after asking a great many questions first. That makes it an awkward fit for a normal online shop. Nobody impulse-buys a rice mill, buyers often need an invoice to take to their finance department rather than a card payment page, and the catalogue has to be findable on Google because that is where the search starts. Behind the shop is the other half of the problem: a team of mixed seniority running the business day to day, where not everyone should be able to change a price or mark an order as paid. The store is live and selling.
My role
Solo fullstack developer, contracted by the company to build their online store. The data model and its migrations, sign-in and permissions, the public shop, the admin back office, both ways of paying, the transactional emails, the image handling, the search-engine work, and the deployment. The build is delivered and the contract closed, and I still handle maintenance and support on it.
The problem
Selling high-value machinery online needs two things at once, and they pull against each other. The shop has to work like a shop. The catalogue must be browsable, properly indexed by search engines, and clear enough that a buyer can compare variants of the same machine. But it also has to accommodate how these purchases really happen: many buyers cannot pay by card on the spot, because the purchase has to go through someone else's approval and needs an invoice first. The back office has a different problem entirely. A small team of mixed seniority runs the business, and giving everyone full administrative access is how prices get changed by accident and orders get marked paid when they are not. What was needed was permissions matched to the job, a way for junior staff to propose changes that someone senior approves, and a record of who did what, rather than trust.
The solution
A shop and a back office in one application, kept firmly apart. The shop side is a catalogue built for machines rather than clothes: categories, a detail page with a gallery and full specifications, variants of the same model grouped under a parent, related products, and a cart that survives closing the tab. Prices are held in naira and displayed in other currencies for buyers looking from abroad. Checkout works two ways. A buyer who can pay now pays by card. A buyer who needs approval first requests an invoice and pays later, which is how most of these purchases actually happen. The back office is governed by one permission map covering five roles, enforced on the server rather than by hiding buttons. The business asked specifically for an approval step, so junior staff request a change to a price or an order status and a senior administrator reviews it. On approval the change is applied from the exact form that was submitted, rather than reconstructed. Every catalogue edit, order change, payment and approval is written to a log that can only be added to. That request-and-approve requirement is unusual for a shop this size, and it tells you something about the business: when a single line item runs to several million naira, a mistyped price is not a small mistake.
Key features
- Browse machines by category and compare variants of the same model, with a full gallery, specifications and a detailed description on every product.
- See prices in naira, or converted into dollars, euros or pounds if you are buying from abroad.
- Pay by card, or request an invoice and pay later, which is how most equipment purchases actually go through a company.
- Check out as a guest without creating an account, and turn that into an account afterwards if you want to.
- For the team: manage the catalogue with rich descriptions and up to eight images per product that can be dragged into order.
- Request rather than perform. Managers and support staff propose changes to prices, products or order statuses; a senior administrator reviews and approves, and the change is then applied exactly as it was submitted.
- See how the business is doing: revenue over time, orders by status, which products are being viewed, and which categories sell.
- Look back at who did what. Every catalogue edit, order change, payment and approval is recorded in a log that cannot be edited after the fact.
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.
Results
The store is live and selling, with the catalogue, both checkout paths and the back office all in real use by the company. The usage figures belong to the business rather than to me, so none are quoted here. The build was delivered and the contract closed, and I have continued to maintain and support it since.
Key learnings
When the same outcome can arrive down two different roads, design for that from the beginning rather than trying to coordinate the two afterwards. A payment here is confirmed both by the bank calling the server and by the customer's browser returning from the payment page, and either may arrive first. Letting whichever arrives first win, and making the second one harmless, is far simpler and safer than trying to make them take turns. Authorisation belongs on the server, every time. A hidden button is a courtesy to the user, not a control. And storing the whole submitted form alongside an approval request, then replaying it once approved, kept the review workflow honest: what gets applied is exactly what was proposed, not a reconstruction of it.
Want to see it in action?
Explore the live product, or reach out to talk through the build.