CLAUDE.md – map of this shop for your AI assistant
This file is written for Claude (or another AI coding assistant) working on a copy of Open Small Shop. The person you are helping is probably not a programmer. Explain in plain words, change as little as needed, and test locally before they upload anything.
What this is
A small online shop in plain PHP 8.2+ with SQLite. No framework, no Composer, no build step, no database server. It runs on ordinary shared hosting (Apache with .htaccess, PHP, cron). Two small libraries are vendored: PHPMailer (email) and tFPDF (PDF invoices and customs forms).
It was written for a real shop: a small exporter in India selling motorcycle parts worldwide with India Post, prices in euros, payment by PayPal, five languages (English, German, French, Spanish, Portuguese). The demo content keeps that setup, because all the country-specific parts (carrier rates in rupees, US import duty, CN22/CN23 customs forms) then work together as one example. Most users will adapt exactly those parts.
Run it locally
cp config.example.php private/config.php # set 'admin_initial_password' to 10+ characters
php -S 127.0.0.1:8080 index.php
Open http://127.0.0.1:8080 and http://127.0.0.1:8080/admin/ (log in with admin_initial_password).
With 'env' => 'dev': emails are written to private/logs/mail/ instead of being sent, and
'paypal' => ['mode' => 'fake'] simulates PayPal without network (the PayPal button itself needs the
real SDK; to test the order flow, POST to /api/create-order.php then /api/capture-order.php).
The dev server also serves /api/*.php, /admin/*.php, /health.php and /download.php through
index.php (Apache runs them directly).
Request flow
.htaccess: HTTPS, blocksprivate/ lib/ templates/ tools/ data/, sends every non-file toindex.php.-
index.php: the front controller. Routes/,/shop/,/product/<slug>/,/product-category/<slug>/,/cart/,/checkout/,/checkout/order-received/,/my-account/..., content pages/<slug>/,sitemap.xml,robots.txt; the same under/de/ /fr/ /es/ /pt/. Old URLs:data/redirects.json. WordPress internals (wp-admin,xmlrpc.php…) get 410. -
templates/*.phprender insidetemplates/layout.php(render()in lib/render.php). -
The cart lives in the browser (localStorage: SKUs and quantities only). Every price, shipping cost and total is computed on the server in
build_quote()(lib/shipping.php) – the only place. -
Checkout:
assets/cart.js→api/create-order.php(saves a pending order, creates the PayPal order) → buyer approves in PayPal →api/capture-order.php(reserves stock, captures, numbers the order, sends emails).api/paypal-webhook.phpand the hourlylib/cron.phpfinish orders whose browser never came back. Webhooks are verified with PayPal before anything changes.
Where things are
| Topic | File(s) |
|---|---|
| Shop name, address, email, logo, menu, home texts, customs details | data/site.json (shop block) |
| Products (starting point) | data/products.json; edits made in Admin → Products live in the DB (product_edits) and win; merged in all_products() (lib/bootstrap.php) |
| Stock | DB table stock (seeded once from data/stock-initial.json); Admin → Stock |
| Translations | data/i18n/<lang>/ui.json (interface, keyed by the English text), products.json (by slug), pages.json + data/pages/<lang>/*.html |
| Content pages | data/pages.json + data/pages/*.html (English); a missing translation falls back to English (noindex) |
| Languages list | LANGS in lib/i18n.php; t() translates; money format money_lang() |
| Shipping prices and countries | data/zones.json (groups of countries, prices in INR, blocked countries, special rules) + shipping_quote() in lib/shipping.php |
| Exchange rate INR→EUR, US duty % | settings() in lib/shipping.php, Admin → Settings (private/settings.json) |
| Letter shipping (flat price) | product field shipping_class: letter, letter_eur_cents in zones.json |
| Customs forms CN22/CN23 + address label | lib/shipdocs.php, background images in data/shipping/; coordinates in mm |
| Invoice PDF | lib/invoice.php (seller block from site.json) |
| Emails | lib/orders.php (send_order_emails, send_shipped_email), lib/accounts.php (password reset), lib/downloads.php; sending: lib/mail.php |
| PayPal | lib/paypal.php (Orders v2 API), api/*.php |
| Customer accounts | lib/accounts.php, templates/account.php |
| Digital product (download) | lib/downloads.php: personal link per order, the first PDF in private/downloads/ |
| Database schema + migrations | lib/db.php (PRAGMA user_version, add a new numbered step for changes) |
| Admin pages | admin/*.php (login in lib/admin.php) |
| Backups | lib/backup.php (after each new paid order, via cron), Admin → Backup |
| Maintenance mode | lib/maintenance.php, Admin → Settings |
| Server self-test | admin/check.php (keep SHOP_FILES up to date when adding files), health.php |
Rules that keep it safe
-
private/holds config.php (passwords), shop.db (customers, orders), invoices and keys. Never commit it, never put it in a public folder, never paste its contents into chat or an issue. -
Money is integer cents everywhere. Times are UTC strings
Y-m-d H:i:s. - All output goes through
e()(HTML escape). All SQL usesq($sql, $params)with placeholders. - Forms in admin and account pages carry
csrf_token()and are checked withcsrf_ok(). - The Content-Security-Policy in .htaccess forbids inline
<script>: put JavaScript inassets/*.js. - Prices the browser sends are never trusted; the server prices everything again.
- After changing PHP files:
php -l file.php; after changing products:php tools/validate.php.
Typical adaptations (what users ask for)
- Own shop details –
data/site.json(shop,site_name, logo, menu),assets/invoice-logo.png(PNG without transparency), pages indata/pages/, the legal pages (they are templates!). - Other carrier / other country of dispatch – rewrite
data/zones.json(groups and prices; the*_inrprices andinr_to_eur_cents()assume rupees – for another currency of the carrier, change the conversion in lib/shipping.php or store prices directly in euro cents), remove or change the US duty rule (duty_prepaidin zones.json,us_duty_percent), replace or drop the CN22/CN23 module (lib/shipdocs.php; called fromsend_order_emails()in lib/orders.php and admin/order.php). - Other shop currency – prices are euro cents;
money()/money_lang()and the PayPal calls (currency_code'EUR' in lib/paypal.php and api code) need the new currency. - Taxes (VAT / sales tax) – not built in (the original seller exports from India, zero-rated).
Sellers in the EU or US must add tax rules in
build_quote()and show tax on the invoice. - Other payment provider (e.g. Stripe) – add an api/ endpoint pair like PayPal's and a button in
templates/checkout.php + assets/cart.js;
mark_paid()in lib/orders.php does everything after payment. - Fewer or more languages –
LANGSin lib/i18n.php plus the files in data/i18n/.
Testing checklist before going live
Fake order locally (dev) → test site with PayPal sandbox keys and the sandbox webhook → orders in every language, a country with duty, a download product, a backorder, a closed browser tab after approval (the webhook must finish it), cancel in PayPal, customer account, password reset, admin status "Completed" with tracking (shipped email), Admin → Server check all green, then live keys.