Skip to content

LouisDev314/popbox-studio-node

Repository files navigation

PopBox Studio Backend

Production-oriented Express + TypeScript REST API for PopBox Studio, an anime merchandise and Ichiban Kuji storefront. The backend owns catalog data, inventory, checkout, Stripe payment finalization, order access, Kuji tickets, admin operations, transactional email, and operational safety rules.

This repo is backend-only. A separate Next.js storefront/admin client consumes the API but does not duplicate commerce, payment, inventory, or Kuji business logic.

Tech Stack

Area Stack
Runtime Node.js 22, Express 5, TypeScript
Database PostgreSQL, Supabase, Drizzle ORM
Payments Stripe Checkout, Stripe webhooks, refunds
Auth and storage Supabase Auth JWT verification, Supabase Storage
Email and observability Resend, Pino, Sentry
API quality Zod validation, OpenAPI 3.1, centralized response and error handling
Operations Docker, node-cron jobs, Postgres advisory locks
Testing Vitest, supertest, TypeScript, ESLint

Key Backend Capabilities

  • Versioned REST API under /api/v1, with admin routes under /api/v1/admin and Stripe webhooks under /api/v1/webhooks/stripe.
  • Catalog APIs for products, collections, tags, search, recommendations, product images, legal content, FAQ, and store settings.
  • Standard product checkout with server-side price calculation, Canada-only shipping, Stripe Checkout Session creation, and required Idempotency-Key.
  • Inventory reservations with a fixed 10-minute TTL to prevent overselling while customers complete Stripe Checkout.
  • Stripe webhook-driven payment finalization with raw-body signature verification, persisted webhook event dedupe, and retry-safe processing.
  • Ichiban Kuji support: Kuji prize inventory, paid-order ticket allocation, unrevealed prize masking, idempotent reveal endpoints, and Last One prize handling where modeled.
  • Guest order access through signed bootstrap links and an HttpOnly order session cookie.
  • Admin workflows for catalog management, product images, inventory, Kuji prizes, orders, shipments, refunds, customers, legal pages, FAQ, and store settings.
  • Sentry request sanitization for sensitive query values and headers, plus structured logging for checkout, webhook, email, and background job failures.

Architecture Overview

Routes stay thin and delegate to service modules. Validation happens at the HTTP boundary, consistency-sensitive operations use Drizzle transactions and Postgres row locks, and external providers are isolated behind integration modules.

flowchart LR
    Client[Client and admin apps] --> API[Express API]
    API --> Middleware[Middleware]
    Middleware --> Routes[Routes]
    Routes --> Services[Services]
    Services --> DB[PostgreSQL]
    Services --> Stripe[Stripe]
    Services --> Supabase[Supabase]
    Services --> Resend[Resend]
    Stripe --> Webhook[Webhook route]
    Jobs[Background jobs] --> DB
    API --> Sentry[Sentry]
Loading

Main code areas:

  • src/routes: public, admin, checkout, order, legal, settings, and webhook routers.
  • src/middleware: auth, validation, rate limiting, response envelopes, and exception handling.
  • src/services: catalog, checkout, orders, webhooks, admin, notifications, settings, and legal logic.
  • src/db/schema.ts: Drizzle schema for catalog, inventory, orders, payments, refunds, Kuji, webhooks, and content.
  • src/jobs: reservation and pending-order cleanup with advisory locks.

Core Commerce Flows

Checkout session creation

  1. Client posts cart items to POST /api/v1/checkout/session with Idempotency-Key.
  2. Server normalizes cart lines, locks product and inventory rows, validates active products and available stock, calculates subtotal and shipping, creates a pending order, inserts order items, writes inventory reservations, and creates a pending payment row.
  3. Server creates a Stripe Checkout Session with the same idempotency key and stores the Stripe session/payment identifiers.
  4. If Stripe session creation fails, reservations are released and the order is cancelled.

Stripe payment finalization

  1. Stripe calls POST /api/v1/webhooks/stripe.
  2. The raw request body is signature-verified before parsing.
  3. The Stripe event id is persisted in stripe_webhook_events for dedupe.
  4. Paid checkout sessions finalize the order inside a transaction: customer/address snapshots are reconciled, reservations are converted, standard inventory is decremented, Kuji tickets are allocated, payment is marked paid, and order emails are attempted.
  5. Late or inconsistent paid sessions are moved to paid_needs_attention instead of silently finalizing unsafe inventory.

Kuji draw and reveal

  • Kuji product inventory is derived from non-Last-One prize remaining quantities.
  • Prize rows are locked during paid-order ticket allocation.
  • Tickets are assigned using weighted random selection over remaining prize quantities.
  • Unrevealed ticket responses hide prize details.
  • Reveal endpoints are idempotent and protected by an order-level advisory lock.

Security and Reliability Highlights

  • Admin APIs verify Supabase JWTs server-side and check public.users.role; client-side role flags are not trusted.
  • Guest order links use signed tokens, then exchange into an HttpOnly cookie session.
  • Plaintext guest access secrets are not stored in the database.
  • Zod validates params, query strings, and request bodies before service logic runs.
  • Stripe webhooks use raw-body signature verification.
  • Checkout, webhook handling, order actions, emails, refunds, and cleanup jobs use idempotency markers, uniqueness constraints, transactions, row locks, or advisory locks where appropriate.
  • Sentry sanitizes token, session_id, checkout_session_id, authorization, cookie, set-cookie, and x-order-token.
  • GET /health reports process and database health.

Local Setup

Prerequisites: Node.js 22+, pnpm, PostgreSQL or Supabase, Stripe test keys, Supabase project credentials, and Resend credentials.

pnpm install
pnpm dev

Useful commands:

pnpm build        # TypeScript build to dist/
pnpm start        # Run dist/index.js
pnpm test         # Vitest launch/safety suite
pnpm check        # ESLint --fix, TypeScript noEmit, then tests

The main environment variables are validated at startup in src/config/env.ts: database URL, Supabase URL/keys/storage bucket, Stripe secret and webhook secret, Stripe success/cancel URLs, CORS/client/admin URLs, Resend settings, order notification email, contact email, and ORDER_TOKEN_PEPPER.

The reservation TTL is intentionally fixed at 10 minutes. STRIPE_CHECK_SESSION_RESERVATION_TTL may be omitted or set to 600000.

API Contract and Tests

  • openapi.yml documents the public/admin HTTP contract.
  • pnpm test runs the Vitest launch suite covering checkout, webhooks, guest order access, Kuji tickets, refunds, settings, Sentry, catalog behavior, and cleanup jobs.
  • CI runs pnpm install --frozen-lockfile and pnpm check on Node.js 22.

Deployment

The included multi-stage Dockerfile builds the TypeScript app and runs node dist/index.js as the non-root node user on port 3000. It is suitable for container platforms such as Northflank when the required environment variables are provided.

Future Improvements

  • Queue-backed workers for webhook follow-up work and cleanup tasks if traffic grows beyond in-process cron.
  • Stronger admin audit logging for inventory, refund, and order-status changes.
  • More integration coverage around live Stripe test-mode flows and refund reconciliation.
  • Inventory reconciliation reports and operational dashboards.
  • Targeted caching for hot read paths after profiling.

License

ISC

Packages

 
 
 

Contributors