2

Jinx — Digital-Goods Storefront & Payments Platform

A five-repo commerce platform for instant-delivery digital goods — a Next.js 16 storefront and operator dashboard on a NestJS API that runs its own crypto payment rails, reconciles peer-to-peer transfers nobody offers an API for, and treats every deliverable as a single inventory line that can only be sold once.

Technology Stack

Back end — NestJS 11, TypeScript 5.9, PostgreSQL 16, Prisma 6, Redis 7, Bull, Passport JWT, argon2, Speakeasy TOTP, Cloudflare Turnstile, bitcoinjs-lib, ethers, @solana/web3.js, @scure/bip32 + bip39, Socket.IO, Resend, Handlebars, Supabase Storage, sharp, @react-pdf/renderer, web-push, Pino, Sentry, Swagger

Front end — Next.js 16 (App Router, standalone output), React 19, Tailwind CSS 4, TanStack Query v5, Zustand, React Hook Form, Zod 4, Untitled UI, React Aria, Motion, Recharts, TipTap, socket.io-client

Operations — Docker Compose, nginx, Let's Encrypt/certbot, single-VPS deployment with scripted deploy, nightly Postgres backup, and twice-daily certificate renewal

Overview

Duration: 2026

Jinx is a storefront for digital goods that have to arrive the moment money lands — gift cards, points balances, reward accounts. A customer browses the catalog, builds a cart, pays in crypto or through a hosted fiat checkout or from internal store credit, and receives the code before they have closed the tab.

The product looks simple from the outside and is not, for one reason: there is no shipping step to hide behind. Everything a physical store gets to defer — is the item actually in stock, did the payment really clear, who does this deliverable belong to — has to be resolved correctly inside the few seconds between a confirmation arriving on-chain and a code appearing in someone's inbox. Getting it wrong doesn't delay an order, it hands the same secret to two people.

It ships as five repositories deployed together, with all business logic held in one place:

RepoRoleDomain
jinx-beREST API, WebSocket, background workers — the single source of truthapi.jinx.to
jinx-feCustomer storefrontjinx.to
jinx-adminOperator dashboardadmin.jinx.to
jinx-pdfGift-card link generator and share viewerpdf.jinx.to
deployCompose, nginx, TLS, runbooks—

Neither front end holds a database. Both are clients of the same /v1 API, which is what keeps a price, a stock count, and a discount identical whether a customer or an operator is looking at it.

One Image, Two Roles

The backend builds once and boots differently depending on a single environment variable, and the split is the reason the API stays responsive under load:

Chain polling, payment forwarding, email sending, and reconciliation are all latency-tolerant and all bursty. Keeping them off the process that serves checkout means a congested network or a slow mail provider can never show up as a slow Add to Cart.

Inventory as Single-Use Lines

The catalog is three levels deep — category, product, variant — but the unit that actually matters is the fourth: a stock line, one deliverable secret, moving through AVAILABLE → RESERVED → SOLD.

Modelling inventory as individual lines rather than a count per variant is what makes double-delivery structurally impossible instead of merely unlikely. A count can be decremented twice by two concurrent checkouts; a line that is already RESERVED cannot be reserved again. Every order item points at the exact line it was allocated, so a support question months later — which code did this person get — is a lookup, not an investigation.

The cost of that model is abandoned reservations, so a cron sweeps stale ones back to AVAILABLE every few minutes. That job is not housekeeping; without it, every cart someone opened and walked away from would quietly retire a sellable item.

Five Payment Rails

An order is PENDING until exactly one payment object satisfies it. On confirmation the backend allocates lines, delivers content, emails a receipt, and completes the order — one path, whatever the customer paid with.

RailHow confirmation arrives
Crypto — BTC, ETH, LTC, BCH, SOL, USDT (ERC-20/TRC-20), USDCA derived HD address per payment; a chain monitor polls for confirmations, then forwards the funds on
Hosted fiat — Cash App, Apple Pay, Google PayGateway webhook; card details never touch the platform
Telegram StarsBot API webhook
Manual P2P — Chime, VenmoNo API exists. A cron polls a mailbox, parses the "you got paid" notification, and matches it to a pending payment by note key
Wallet creditInstant internal debit

Crypto is self-custodied rather than outsourced to a processor: addresses are derived per payment from an HD wallet, which is what allows an incoming transaction to identify its own order without the customer being asked to quote a reference. Rates come from a primary provider with a second one behind it, because a rate lookup failing must not be able to stop a sale.

The peer-to-peer rail is the honest one. Chime and Venmo publish no merchant API, so the only signal that a transfer happened is an email about it — and the platform treats that email as a first-class record, matched to its payment by a generated note key rather than by amount, which is the only field a customer can be relied upon to reproduce exactly.

Wallet credit is the rail that makes the rest tolerable. A returning customer tops up once and every later purchase clears instantly, which converts an unavoidable wait for block confirmations from a per-order tax into a one-time one.

Storefront

Eight surfaces, each doing a job that a single catalog page would do badly:

The FAQ copy is worth noting as product work rather than filler: it leads with the two failure modes that actually cost customers money — sending the right coin on the wrong network, and signing up with a disposable inbox that later dies holding the delivery codes.

Loyalty That Resolves Itself

The rewards system is a pricing engine, not a badge display. Eight ranks run from a base tier to a 20% discount at $30k lifetime spend, and three rules keep it from becoming a support burden:

A referral link pays both sides, and the replacement warranty window scales with order size — fifteen minutes on a single item, forty-eight hours on a hundred — because verifying a bulk order legitimately takes longer than verifying one.

The Identity Edge Case

The sharpest constraint in the data model is one line: email is not globally unique by design. One address may hold a customer account and a staff account, because the people running the store are also customers of it.

Prisma cannot express that, so it is enforced by two partial unique indexes written in SQL directly — unique per portal, not unique overall. The rule that falls out of it governs every query in the codebase: never look a user up by email alone, always scope by role bucket. Authorization itself is composable role buckets shared between the API and the admin app, so a permission is defined once and mirrored rather than re-derived client-side.

Operations

The whole runtime is one Compose file — Postgres, Redis, a migrator that must complete before anything else starts, the API, the worker, three front ends, nginx, and certbot. Only nginx publishes ports; Postgres and the API bind to loopback.

Two operational details that are easy to get wrong and expensive to discover late:

Support runs over Socket.IO with Web Push for notifications, transactional mail goes through templated Handlebars, uploaded proof images are watermarked server-side, and receipts and monthly reports are rendered in-process rather than through an external document service.

The thread running through this build is that instant delivery is an inventory problem wearing a payments costume. The payment rails are the visible engineering — five of them, one self-custodied, one reconstructed from email because no API exists — but the decision that keeps the store correct is much smaller: a deliverable is a row, a row can be reserved once, and everything else is arranged so that nothing can talk it out of that.