A South African men's apparel store on Spree 5.6 + Next.js 15. 160 products
live, three markets, live Malaysian courier rates, customer-owned carts.
What is here:
storefront/ the Next.js app
backend/ ONLY the custom Spree code. Spree itself is a prebuilt image
that mounts no app code, so every file here needs an explicit
volume entry in docker-compose.yml or it is silently ignored.
catalogue/ the scripts that loaded and maintain the products
docs/ manifest, mock registry, napkins
Real .env files are committed deliberately. This lives on the house's private
Gitea, and a clone that runs beats a clone that needs a secrets handover.
LANDMINE.md maps every key, what it opens, and what must be rotated before this
repo goes anywhere less private.
The admin API key used to be a literal in six catalogue scripts. They now read
SPREE_ADMIN_KEY from the environment and refuse to run without it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
6.5 KiB
🏷️ MOCKS — every fake value in Pak Jabit
The convention: anything standing in for a real credential, rate, or record is
tagged #mock. Going live = find them all, replace them, delete this list.
# storefront + backend files
rg '#mock' ~/pak-jabit-store ~/pak-jabit
🔴 rg cannot find half of them. Mocks that live in the Spree database —
payment methods, test products, test orders, test users — have no file to grep.
This file is the authority; the inline tags are a convenience. When you add a
mock, add it here first.
⚠️ Never tag an env var inline. Docker Compose's env_file does not strip
trailing comments — it takes the rest of the line as the value, so
PHOTOROOM_API_KEY=abc123 #mock makes the key literally abc123 #mock and
Photoroom fails with no error anywhere. The tag goes on its own line above,
which is safe in both Compose and dotenv.
Files — rg '#mock' finds these
Storefront ~/pak-jabit-store/.env.production (celest-proto, 34.21.217.116)
| Var | Why it's mock | Replace with |
|---|---|---|
EASYPARCEL_CLIENT_ID |
Malaysia-only courier, left from the MY phase | Bob Go credentials |
EASYPARCEL_CLIENT_SECRET |
same | Bob Go credentials |
STORE_POSTCODE |
10150 = Penang, Malaysia |
real SA warehouse postcode |
STORE_STATE |
MY-07 = Penang, Malaysia |
real SA province |
The whole EasyParcel lane (src/lib/easyparcel.ts, src/app/api/shipping/*,
src/app/api/webhooks/easyparcel) is MY-only and dead for a South African store.
It points at https://api.easyparcel.com — the production host, not a sandbox.
It is not wired into the SA checkout path; it just hasn't been removed.
Backend ~/pak-jabit/.env (pak-jabit-backend, 35.247.170.3)
| Var | Why it's mock | Replace with |
|---|---|---|
PHOTOROOM_API_KEY |
sandbox key — every output is watermarked | live key from /root/.photoroom/credentials (house box), 10/month |
Storefront env — SPREE_CARD_PAYMENT_METHOD_ID
Tagged in .env.production. Holds pm_EfhxLZ9ck8, the prefixed id of the Bogus
gateway. Repoint it at the Payfast payment method when that lands. The route that
reads it (src/app/api/auth/cards/setup/route.ts) errors loud with an explicit
message when it is unset — it never guesses an id, because a wrong one would
attach cards to whichever gateway happened to match.
That route is itself #mock-shaped: it does BOTH halves of card setup in one
request, which only works because Bogus needs no card entry. A real gateway must
split it — server creates the session, the CLIENT collects the card in the
gateway's hosted field against external_client_secret, then the server
completes. Card numbers must never reach our server. The file says so at the top.
Database — grep will never find these
Payment methods
| Name | Class | Why it's mock |
|---|---|---|
Test Card (sandbox) |
Spree::Gateway::Bogus |
Fake card gateway, added 2026-09-15. Accepts 4111111111111111 (Visa), 5555555555554444 (MC), 378282246310005 (Amex), 6011111111111117 (Discover); anything else is a forced decline, which is how to test the failure path. show_in_admin? is false so it stays out of the admin list. |
Test Payment |
Spree::PaymentMethod::Check |
Offline placeholder. No money moves. |
Both carry #mock in their description column, visible in admin.
Replace with: Payfast, once NSH's merchant registration lands. Payflex after. Deactivate both of these before launch — a live store must not offer a gateway that approves every card ending in the right digits.
Shipping methods
| Name | Why it's mock |
|---|---|
Standard Delivery (South Africa) |
Flat R99, zone South Africa (ZA only), added 2026-09-15. The number is a PLACEHOLDER, not a quoted rate — it exists so SA checkout can complete at all. Replace with Bob Go once the SA courier account is registered. |
Before this, ZA sat in the Africa zone with no method attached and every
physical method was bound to Malaysia/US, so a South African buyer got no quote
and checkout dead-ended.
Shipping rates per market — all placeholders
| Market | Method | Rate |
|---|---|---|
| South Africa (ZAR) | Standard Delivery (South Africa) |
R99 |
| Malaysia (MYR) | EasyParcel Domestic |
live EasyParcel quote — not mock |
| United States (USD) | Free Shipping |
$0 |
🔴 ZA and US are placeholders; Malaysia is real. MY charges the courier the customer picked, verified server-side against a live EasyParcel quote. They exist so each market can complete a checkout at all. A shipping rate only applies when its calculator's currency matches the ORDER's currency — every non-SA method was priced in a currency no order used, which produced no rate, no fulfillment, and a checkout with nothing to charge. That, not zones or stock, is why Malaysian checkout died silently.
Standard Shipping (USD $15) sits in the Malaysia zone and will never offer —
wrong currency for that market. Harmless, but it is noise to clear when the MY
lane is removed.
Products
| Slug | Why it's mock |
|---|---|
studio-test-chinos |
Built 2026-09-15 proving the Image Studio pipe end to end. Kept deliberately as the worked example for colleagues testing the image editor. R259.11, watermarked sandbox images. Delete before launch. |
That's 1 of 161 products. The other 160 are real scraped stock.
Orders
14 orders, 4 completed, all test. They are priced in the old currency at the
old 23× rate — the newest completed one reads $2,577.12, which is neither
ZAR nor a real price. They predate both the price fix and the ZAR switch.
Purge before launch or the first real report is nonsense.
Users
Test accounts matching celeste-*@example.com — created during API verification
on 2026-09-15. No real customers exist yet.
Not mock, but not real either
These aren't fake credentials; they're claims the store makes that nothing backs.
| Thing | Problem |
|---|---|
| "Free shipping over R500" | Hardcoded storefront copy. No shipping rule exists behind it. A buyer who qualifies gets charged anyway — or gets no quote at all, see below. |
| SA shipping | ZA is in the Africa zone with no shipping method attached. Every physical method is bound to Malaysia/US. A South African buyer gets no quote and cannot complete checkout. Blocked on courier registration. |
| S/M/L/XL buttons | Decorative. No Spree::Variant behind them — every product is one variant. |
Started 2026-09-15. Add to this file when you add a mock, not after.