# Stub docs > Till, online ordering with real payments, and receipts — one codebase, > two deploy targets: Cloudflare Workers or your own box via celld. Docs site is a single static page (`index.html`) + screenshots. No build step, no framework — edit and `pnpm run deploy` (from `apps/docs`). ## Start - **Deploy it** — Cloudflare (`cf` path) or your own hardware (celld path); see Deployment. POS, admin, ordering and API go live together. - **Add products** — admin → items → new. Price, stock, barcode. - **Make a sale** — cashier → tap items → charge → print. - **Take online orders** — share `/order`; customers pay by card via Stripe/Razorpay links, paid orders auto-approve into the queue. ```sh pnpm install pnpm dev # app (4871) + docs (4873) ``` ## Cashier guide Phone-first sale screen: quick-sell grid, search, cart rail with quantity steppers. Discounts per-sale (percent or flat), customers by phone, split payments across cash / card / mobile pay — change-due follows the tendered total. Review freezes the cart so totals, tax and payments confirm on one screen. Every charge mints an invoice number that is never reused, even after a void. ## Online ordering + payments `/order` catalog → submit → `/order/:id` status with Pay buttons. Providers are plugins behind one interface (`src/lib/payments/`): `manual` (instructions + QR fallback), `stripe` + `razorpay` (hosted payment links only — no card inputs, no PCI scope). Presence of secrets = capability; admin toggles per provider; Razorpay requires INR. Every link carries a 5-field metadata contract (`order_id, order_number, amount_minor, currency, env`) verified on return-URL redirect (instant confirm) and webhook (backup). Paid flips `paid + approved` in one idempotent transaction (auto-approve); decline after paid triggers an API refund day one. Attempts live in `order_payments` (1:N); the order row caches status for the queue. Optional flat **online order charge** (every order) + **delivery fee** (delivery only), server-priced and snapshotted per order. ## Admin guide Items, stock, bills and spend. Stock decrements on every sale; low-stock reads from the same ledger. Spend is a flat ledger: name, amount, category tag, attachment, date. Business details, tax rules and receipt template are configurable (name, address, tax label + ID, inclusive/exclusive display, invoice prefix, footer note) with a live 58mm preview before saving. Order queue shows PAID badges; paid orders arrive pre-approved. ## Deployment (two paths, one codebase) Cloudflare-only binding was the rate limiter — now a D1 sliding-window limiter (`src/platform/ratelimit.ts`, tiers: sensitive 20/min, default 600/min, webhooks exempt, `Retry-After` headers). Everything else (D1, R2, KV, vars, assets) exists on both targets. - **Cloudflare** — source of truth `apps/app/cloudflare.config.ts` (bindings, secrets, domains). Public publishable keys are plain `bindings.text` vars; only true secrets use `bindings.secret`. ```sh cd apps/app cf d1 create stub # once pnpm db:migrate:prod pnpm deploy # → your workers.dev ``` - **celld (your box)** — config `apps/app/wrangler.celld.jsonc` (deliberate name: wrangler/cf tooling must never auto-discover it). Secrets merge from gitignored `.dev.vars` at deploy time via `pnpm run celld:deploy` (never committed). Bucket creds in gitignored `.celld.env` (`CELLD_BUCKET=s3:///stub`). Needs an S3 bucket with conditional writes (R2, AWS S3, Tigris — not B2/MinIO/Spaces) and celld ≥ 0.6.2 on the box. D1/KV/R2 are cells in your bucket; migrations run **on the box** (fleet routing is loopback-local); supervise with Docker (`--restart unless-stopped`, `--stop-timeout 60`). ```sh cd apps/app source .celld.env pnpm run celld:deploy # bundle + upload, node adopts in place ssh box 'celld d1 migrations apply stub ~/stub-app.jsonc --bucket "$CELLD_BUCKET" ...' ``` Known celld notes: export the entry as a plain `{ fetch }` object, not a class instance (unbound dispatch breaks private fields); health endpoint hangs until the first deployment exists; one node per bucket prefix. ## API overview Everything the UI can do is JSON: `/api/v1/products`, `/api/v1/sales`, `/api/v1/invoices`, `/api/v1/stock`. Same DTOs feed the receipt renderer, so API sales print identically to till sales. ## MCP tools Agents drive the till: look up items, check stock, draft a sale, read invoices. Read-first — moving money or stock needs the same explicit confirm the cashier review screen enforces. ## Printing Client-side ESC/POS bytes: header, invoice number, items, tax, totals, payment lines, footer, cut. Text-only for 58mm, no images in v1. Any ESC/POS thermal printer over USB or network; test-print from admin first. ## Conventions (repo laws) - Tailwind standard-scale utilities only; primitives in `src/components/ui/`; `class` not `className` in Hono JSX. - Single object params everywhere except `src/lib/data/*`. - `cf` CLI only, never bare `wrangler`. `pnpm db:migrate` pins `--persist-to ./.wrangler/state`. - colony: `apps/*`, `packages/*`. Dev ports: app 4871, docs 4873. ## FAQ - Offline? The cashier queues sales and syncs when back online. - Data? Cloudflare path: your account's D1 + R2. celld path: SQLite cells inside your own S3 bucket. We never see it either way. - Cost? Cloudflare free tier covers a small shop; then usage-based. celld path: S3 + your box, nothing else.