Md. Nayeem Ahmed
All projects

Senpai Supply

An online store for anime merchandise in Bangladesh, with guest checkout and cash on delivery.

My role
Sole developer: planning, backend, database, frontend and tests
Stack
Java 21, Spring Boot 3.5, PostgreSQL, Flyway, React 19
Status
Backend and storefront complete. Online payments are built but not yet tested against the providers’ sandboxes.
Links
Backend codeFrontend code
The Senpai Supply storefront home page
The storefront running locally with demo products.

The problem

This is a full rewrite of an e-commerce project I first built while learning Spring Boot. The first version worked on the happy path, but it had serious gaps: weak security, prices stored as floating-point numbers, and no protection against selling more stock than we had.

For the rewrite, I asked one question of every feature: what happens if two people do this at the same moment, or one person clicks twice?

Who it is for

The store is designed for shoppers in Bangladesh who buy figures, cosplay, bottles and bags, mostly on their phones and often without a card. It is a portfolio project and does not have real customers yet.

What I built

  • Guest checkout with cash on delivery, and sign-in with a one-time code sent by SMS.
  • A product catalogue with options, specifications and a 360-degree product viewer.
  • Order management with a clear status lifecycle, plus order tracking for guests.
  • Product reviews that only verified buyers can leave.
  • The React storefront, covered by end-to-end tests with Playwright.

Architecture

The backend is a modular monolith: nine Maven modules deployed as one application. Modules can only talk to each other through public interfaces or events, so the compiler prevents one module from reaching into another’s database tables.

checkoutordercatalogpaymentnotificationusercartreviewcommon
Every checkout request carries a unique idempotency key. If the request is sent twice, the second one returns the original order instead of creating a new one.

Key decisions

Spring MVC instead of WebFlux

I use WebFlux every day at work, but it was the wrong fit here. Checkout updates stock, the order, its items and its history together, and with JPA that is one simple transaction. A reactive version would have added complexity without any benefit at this scale.

The server owns every price

The browser only sends product IDs and quantities. Prices, delivery charges and totals are always calculated on the server from the database, using BigDecimal so money is never rounded incorrectly.

Payment providers behind one interface

Cash on delivery, bKash and SSLCommerz each implement the same PaymentProvider interface. Which provider handles which payment method is set in configuration, so enabling a new one does not require code changes. An order is marked paid only after the provider confirms it server-to-server.

How I solved it

When two customers try to buy the last item at the same time, checking the stock first and updating it afterwards is not safe. Both checks can pass. Instead, the check and the update happen in a single statement:

update Product p set p.stock = p.stock - :qty, p.version = p.version + 1
where p.id = :id and p.stock >= :qty and p.status = ACTIVE

If the stock has already run out, the statement updates nothing and the order fails cleanly. Products are always updated in the same order to avoid deadlocks. Order emails are saved to an outbox table in the same transaction and sent afterwards, so a cancelled order never sends an email and two servers never send the same one twice.

What’s next

  • Add a CI pipeline with integration tests that run against a real PostgreSQL database.
  • Test bKash and SSLCommerz in their sandboxes before enabling online payments.
  • Move rate limiting to Redis so the app can safely run on more than one server.