# Deploy Knot a Bother on Cloudflare Workers (free tier)

This guide is for the **Cloudflare-adapted** site, not the original Node.js `server.js` edition. It uses a **Cloudflare Worker + Static Assets**, with a free-tier **D1** database for server-side form throttling and permanent records of Stripe-paid checkout sessions. All pages, visual assets and products are bundled with the Worker deployment; you don't need Pages plus a separate server.

**You must have a Cloudflare account and Node.js 20 or newer.** The site can be hosted on the generated `*.workers.dev` address while you decide whether to register a domain. The proposed Knot a Bother business name and domains have **not** been legally cleared.

## Quick deployment — first time

1. Extract the ZIP and open a terminal in `knot-a-bother-cloudflare/`.
2. Install the CLI: `npm install`.
3. Sign in to your Cloudflare account: `npx wrangler login`.
4. Create the free-tier D1 database: `npx wrangler d1 create knot-a-bother-orders`.
5. Open **wrangler.jsonc** and insert this property **after the `vars` block**, adding a comma after `vars` first:

```json
"d1_databases": [{
  "binding": "ORDERS_DB",
  "database_name": "knot-a-bother-orders",
  "database_id": "PASTE-THE-REAL-DATABASE-ID-FROM-STEP-4"
}]
```

   Use the real UUID returned by your account (never the placeholder). The Worker will run without this binding only to show its static website and public catalogue; checkout, newsletter and contact forms will be unavailable until you set up D1.

6. Apply the SQL schema to the remote database:

```bash
npx wrangler d1 migrations apply knot-a-bother-orders --remote
```

7. Deploy the project:

```bash
npm run deploy
```

Wrangler prints your new `https://knot-a-bother.<your-subdomain>.workers.dev` URL. Open it and browse the full shop. **Checkout and forms intentionally report “not connected” until credentials are configured**.

## Run on your own computer

After `npm install` and adding the D1 binding above:

```bash
npx wrangler d1 migrations apply knot-a-bother-orders --local
npm run dev
```

Open `http://localhost:8787` (or the port displayed by Wrangler). Optional: copy `.dev.vars.example` to `.dev.vars` and fill test-mode secrets. `.dev.vars` is ignored by Git. Test JavaScript without needing Wrangler: `npm run check`.

## Connect your UK domain (optional)

You can use `*.workers.dev` at **£0** indefinitely within limits. After you have purchased a verified, legally suitable domain, add the domain to Cloudflare, use Cloudflare DNS/nameservers, then in the Cloudflare Dashboard select **Workers & Pages → knot-a-bother → Settings → Domains & Routes → Add → Custom Domain**. Follow Cloudflare's current dashboard instructions to configure the hostname; SSL is managed by Cloudflare.

Do not assume `knotabother.co.uk` is available until checked with Nominet/your registrar, and do not register the brand before clearing UK IPO trademark conflicts.

## Connect Stripe securely

1. Create or log in to your Stripe UK merchant account. Test the flow using `sk_test_...` first.
2. In the Cloudflare dashboard, open your Worker → **Settings → Variables and Secrets**, and add `STRIPE_SECRET_KEY` as a **Secret**, not plain text. Or use:

```bash
npx wrangler secret put STRIPE_SECRET_KEY
```

3. In Stripe Dashboard → Developers → Webhooks, create the endpoint for `https://YOUR-REAL-WORKER-DOMAIN/api/stripe-webhook` and subscribe to **`checkout.session.completed`** and **`checkout.session.async_payment_succeeded`**. Set the corresponding **`STRIPE_WEBHOOK_SECRET`** through Cloudflare Secrets / `npx wrangler secret put STRIPE_WEBHOOK_SECRET`.
4. If using more than one domain, optionally set **`PUBLIC_ORIGIN`** to the canonical HTTPS shop origin (for example `https://your-verified-domain.co.uk`) as a Cloudflare text variable. Otherwise the Worker returns to the domain on which the shopper started.
5. Test successful and failed purchases, webhook signature verification, refund handling and order fulfilment **in Stripe test mode** before using live keys.

**Important:** Stripe Checkout calculates prices from `data/products.json` on the server, not from client-submitted prices. The Worker records each **paid** Stripe Checkout Session once in D1, preventing duplicate insertion on webhook retries. This is **not a complete order fulfilment system**: no stock reservations, shipping labels, downloadable pattern delivery, sales tax/legal configuration or customer support automation are included. **Do not turn on live payments until you have operational fulfilment and legally compliant policies.** See `README.md`.

To see recently paid checkout session IDs in your database:

```bash
npx wrangler d1 execute knot-a-bother-orders --remote --command "SELECT checkout_session_id, amount_total, currency, fulfilment_status, created_at FROM paid_orders ORDER BY created_at DESC LIMIT 25;"
```

Fulfilment details (contact, address and purchased line items) remain in your Stripe account; the D1 ledger intentionally stores no customer personal information. When you complete an order manually, you can update its fulfilment status with D1 SQL, but this does **not** send an email or ship goods:

```bash
npx wrangler d1 execute knot-a-bother-orders --remote --command "UPDATE paid_orders SET fulfilment_status='fulfilled' WHERE checkout_session_id='cs_REPLACE_WITH_ACTUAL_SESSION_ID';"
```

The example fixed shipping policy is £3.95 for UK physical orders below £60, and free postage for those at/above £60. Both the values and the restrictions must be reviewed before trading. Set `STRIPE_AUTOMATIC_TAX` to `true` *only* after setting up correct Stripe Tax behaviour and obtaining tax advice.

## Optional newsletter (Mailchimp)

Set the two Cloudflare **Secrets** `MAILCHIMP_API_KEY` and `MAILCHIMP_AUDIENCE_ID`. The Worker adds subscribers as `pending`, so a confirmation email is required. Until configured, the form safely fails instead of falsely claiming to subscribe a visitor. Publish an accurate privacy notice and proper consent information before accepting real subscribers.

## Optional contact form (Resend)

Set `RESEND_API_KEY` (Secret), `CONTACT_FROM_EMAIL` (verified sending-domain mailbox) and `CONTACT_TO_EMAIL` (the address where you want enquiries delivered). Without these settings the form safely returns “not configured”. Publish a valid privacy notice and actual contact details before launch.

Add Cloudflare Turnstile and/or WAF rules for public forms if spam grows. The site includes a simple **D1-backed per-IP rate limit** (8 contact/newsletter attempts and 10 checkout attempts per one-minute fixed window) rather than relying on memory shared between Worker instances.

## Ongoing operations and free tier

- Editing content: change `public/*.html`, `public/assets`, or `data/products.json`, then `npm run deploy` again.
- The storefront URLs are intentionally maintained as `.html` links. Cloudflare configuration disables automatic extensionless redirects to avoid breaking query-string navigation.
- Static asset requests are free and unlimited under Cloudflare's stated Workers Static Assets pricing. API and homepage requests consume Worker quotas; the Workers Free plan currently provides 100,000 Worker requests per day. D1 has separate free-tier limits. **Check current limits** if traffic increases.
- Keep Stripe, Mailchimp and Resend API credentials in encrypted **Secrets**; never put them in `public/` or commit `.dev.vars`.
- Do not switch the draft UK privacy, refunds, delivery, or terms sections to a real sales site without review. Replace illustration-only items with your actual stock, confirmed prices and product photos.
- Review Cloudflare dashboard analytics/logs, D1 storage use, backup/recovery and Stripe webhook failures periodically. For repeated webhooks, Cloudflare must successfully write D1 before returning a 200 acknowledgement.

## Troubleshooting

- **Site works, checkout/form says unavailable:** D1 binding may be missing, migration not applied, or third-party credentials aren't connected.
- **`no such table: api_rate_limits` or `paid_orders`:** apply the migration to the correct database (`--local` or `--remote`).
- **Cloudflare CLI won't run:** install Node.js 20+ and run `npm install` again. `npm run check` needs only Node, but deploying needs Wrangler.
- **Wrong domain after Stripe:** set `PUBLIC_ORIGIN` to your canonical HTTPS shop domain and redeploy.
- **Stripe rejected webhook:** verify `STRIPE_WEBHOOK_SECRET` belongs to the exact live/test webhook destination, and inspect Cloudflare logs.
- **A link loses `.html`:** verify `assets.html_handling` is `none` in `wrangler.jsonc`.

Official docs: [Workers + Static Assets](https://developers.cloudflare.com/workers/static-assets/), [D1 setup](https://developers.cloudflare.com/d1/get-started/), [Worker secrets](https://developers.cloudflare.com/workers/configuration/secrets/), [static asset headers](https://developers.cloudflare.com/workers/static-assets/headers/).
