Pak Jabit: full stack — storefront, Spree customisations, catalogue tooling

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>
This commit is contained in:
2026-09-15 22:09:55 +08:00
co-authored by Claude Opus 5
commit a58a61b98c
98 changed files with 15524 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
# 🏷️ 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.
```bash
# 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.*