Back to Projects

Primsell NFT E-Commerce

Node.js Business Logic on PostgreSQL — Checkout, Stripe Connect & Royalty Ledger

Overview

Role: Main full-stack developer in a team of 3–5: backend business logic in Node.js on PostgreSQL (Express/Sequelize core API, NestJS payment service), the blockchain background jobs and the React apps. Mentored 2 junior engineers.

What it is. A B2B platform where brands sell NFT-backed products: buyers pay by card through Stripe, the platform mints the NFT on Polygon, and the buyer later burns it to redeem a real-world discount code or ticket that venue staff verify. Brands also earn royalties when their NFTs resell on OpenSea or Rarible.

Backend. Business logic in Node.js on PostgreSQL: an Express + Sequelize core API (~79 REST endpoints, JWT with ACL roles, per-route validation and rate limits, Swagger generated from the route definitions) and a NestJS + MikroORM payment service on Stripe Connect, called with short-lived service tokens. Order, royalty-ledger and contract-deployment lifecycles run on a state-machine workflow engine with guards and lifecycle events; background work runs as cron jobs with a DB-backed job log and a single-instance guard.

Correctness. Checkout overselling was fixed with a row lock and atomic SQL counters. The order-expiry job checks Stripe before expiring, so late payments still complete, and money math moved from floats to decimal.js. Burn-to-redeem is idempotent on the transaction hash, with a recovery job for redeems whose HTTP call never arrived, and brand ↔ customer CRM data is kept by a PostgreSQL trigger plus an idempotent backfill.

Royalty ledger. A resale indexer for OpenSea and Rarible reads checkpointed block windows with an overlap re-scan and adaptive range splitting, and feeds a per-brand ledger whose withdrawals are gated by balance guards and block confirmations.

Frontends. React apps for brands, checkout, redeem and venue verification share a UI kit and API client through a git submodule.

Key Features

  • Express + Sequelize core API on PostgreSQL (~79 REST endpoints, JWT + ACL roles, validation and rate limits per route)
  • NestJS + MikroORM payment service on Stripe Connect: per-brand accounts, application fees, signed webhooks, decimal.js money math
  • Checkout overselling fixed with SELECT … FOR UPDATE on the campaign row and atomic SQL counters
  • Royalty ledger fed by an OpenSea/Rarible resale indexer (checkpoints, overlap re-scan, dedup) with confirmation-gated withdrawals
  • Idempotent burn-to-redeem on the transaction hash, plus a recovery job for interrupted redeems
  • State-machine workflow engine for orders, ledger operations and contract deployment; cron jobs with a DB-backed job log

Tech Stack

Backend

Node.jsTypeScriptExpressSequelizeNestJSMikroORMPostgreSQLRedis

Payments & Blockchain

Stripe Connectdecimal.jsethers.jsweb3.jsPolygonAlchemyOpenSea & Rarible APIs

Frontend

React 18Redux Toolkitredux-sagaReact QueryMaterial UIwagmiWalletConnect

Infra

DockerAWS (S3, ECS)GitHub ActionsSentry

Challenges & Solutions

Overselling Under Concurrent Checkout

Problem

Order creation read the campaign, checked stock in application memory, then wrote the new booked count back. Two concurrent buyers could both pass the check, one increment was lost, and more NFTs were booked than existed.

Solution

One READ COMMITTED transaction takes SELECT … FOR UPDATE on the campaign row, checks stock under the lock, inserts the order and bumps the booked count with an atomic SQL update. Release and booked → bought transfers are atomic updates too, and available stock is a derived value that cannot be written directly.

Late Payments vs Order Expiry

Problem

Orders hold stock for a limited time. A buyer who paid just after the reservation expired could be charged for an order the system had already released, and amounts computed as floats broke on prices like 19.99.

Solution

The expiry job asks Stripe for the PaymentIntent status before expiring an order: paid orders complete, the rest release stock in bulk and cancel their intent so they can never be charged. Money moved to decimal.js minor units, with the platform fee rounded down so it never exceeds the percentage.

Royalty Ledger From On-Chain Resales

Problem

Brands earn royalties when their NFTs resell on OpenSea or Rarible, and must be able to withdraw them — without missing events near the chain head or counting a sale twice.

Solution

A resale indexer reads block windows from a stored checkpoint, re-scans an overlap, halves the range on provider errors and dedupes by transaction. It feeds a per-brand, per-currency ledger of signed operations whose lifecycle runs on the workflow engine, with a balance guard on withdrawals and N block confirmations before completion.

Redeems That Must Not Be Lost

Problem

A buyer burns the NFT on-chain to get a real-world code. If the browser closed after signing, the burn happened but the backend never heard about it.

Solution

The client records a redeem intent before signing. The redeem endpoint verifies the receipt and the decoded event and is idempotent on the transaction hash, and a recovery job scans chain events from the intent's block to complete redeems whose HTTP call never arrived.

Brand ↔ Customer CRM Mapping

Problem

Brands must see only their own customers, while one wallet can buy from many brands — and the mapping had to cover past purchases too.

Solution

A many-to-many table with a composite key, filled by a BEFORE INSERT trigger on purchases with ON CONFLICT DO NOTHING, plus a separate idempotent backfill migration for the history.

Key Achievements

Row-Locked Checkout
SELECT … FOR UPDATE + atomic counters stopped overselling
Stripe Connect
Per-brand accounts, application fees, signed webhooks, decimal money math
Royalty Ledger
OpenSea/Rarible resale indexer with confirmation-gated withdrawals
Mentored 2
Junior engineers on the team
Primsell NFT E-Commerce - Project | Oleksandr Yusypenko