A live online store and card-buying tool for the trading-card business I run. I designed and built
all of it: backend, database, frontend, payments, security and the tests that keep it working.
Live and taking real orders; AI buylist in private beta
Stack
Java 21, Spring Boot, Spring Security, Spring Data JPA, PostgreSQL, Flyway, React, TypeScript, Stripe, Claude API, AWS, GitHub Actions
The storefront at tailorcards.com.
The problem
A business that ran on messages and in-person sales
I started Tailor Cards in September 2024, selling through Instagram, Facebook, in-person shows and
eBay. It's self-funded and has done about 2,000 transactions and more than $30,000 in sales
volume.
Every sale went through me by hand, from first message to shipping. That was the limit on how far
the business could grow, so I taught myself full-stack development and built a site to move it
online.
What I built
A store, a way to sell to me, and the tools behind them
Live store taking real orders: a Spring Boot REST API, a React and TypeScript frontend, PostgreSQL and Stripe payments.
AI buylist assistant (private beta) where customers list up to 1,000 cards and get an offer. The model identifies the cards; Java calculates every price.
Admin security: TOTP two-factor login, account lockout, CSP and HSTS headers, and an append-only audit log.
Data pipeline on AWS that loads daily market prices and feeds a Tableau dashboard.
Tests: 450+ unit and integration tests (JUnit 5, Mockito, Testcontainers) run in CI on every pull request.
Hard problems
Three things that took the most work
01
Never selling a one-of-a-kind card twice
The problem
Many cards are one-of-a-kind. A customer can open Stripe checkout, leave the tab, and pay
after someone else has already bought the same card. Stripe can also send the same payment
notification more than once.
What I did
JPA optimistic locking on each product, so two checkouts that collide fail cleanly instead of
both succeeding, plus a stock re-check at checkout. Repeated payment notifications are
recognised and never create a second order.
02
Using AI to read card lists without letting it set prices
The problem
Customers describe their cards in free text, pasted lists and spreadsheets, which is a good fit
for a language model. But a model that can be talked into a higher price, or that makes one
up, can't be trusted with money.
What I did
The model only identifies cards; Java looks up and calculates every price. Prompt injection is
blocked by rejecting any price the model emits, server-side. Rate and spend limits cap the cost,
and a weekly evaluation in CI replays golden conversations and fails on any regression from the
baseline.
03
Making card matching fast, and keeping it fast
The problem
Matching a 60-item list took 35.3 seconds, and any later change could quietly make it slower
again.
What I did
Cut matching time for a 60-item list from 35.3 s to 16.6 s, and added a benchmark to CI that
fails the build if it regresses.
Keeping it reliable
Testing, security and data quality
450+ unit and integration tests (JUnit 5, Mockito, Testcontainers) run in CI on every pull request.
TOTP two-factor admin login, account lockout, CSP and HSTS headers, and an append-only audit log.
A weekly LLM evaluation in CI against golden conversations, failing on any regression from the baseline.
An automated AWS ETL pipeline (Lambda, EventBridge) loading daily market prices into date-partitioned S3, with 10 data-quality checks (oversold stock, orphan records, price spikes) and 85 tests in CI.
Screenshots
The site today
The AI buylist assistant. Customers can list up to 1,000 cards.The shop, with condition labels and sold items marked.