AL Store Platform setup guide
AL Store Platform is the self-hosted storefront this site runs on: Stripe Checkout, a webhook that issues license keys, gated downloads, and a free lead-magnet path, all on Cloudflare Pages Functions with a D1 database. This guide covers the architecture, what you deploy, and what each function does.
Architecture
Pages, Functions, D1, Stripe
The storefront is a set of Cloudflare Pages Functions backed by a D1 database. Checkout creates a Stripe Checkout Session with the price read from the server-side catalog, a webhook confirms payment and issues a key, and paid downloads stream from a private R2 bucket only after the payment is verified.
After Stripe confirms a live payment, the signed webhook is designed to issue one license into the database and the delivery page reads that entitlement before showing the key and gated download. Automated tests cover webhook retries, entitlement checks and download gating. Operators should still monitor the first real settled orders and provide a support path if delivery does not complete.
Requirements
What you need
- A Cloudflare account with Pages and Functions. The whole API lives under functions/api as Pages Functions.
- A D1 database, bound as DB, holding the products, licenses, activations and validations tables.
- An R2 bucket, bound as ARTIFACTS, holding the paid product zips served through the gated download.
- A Stripe account for Checkout and the webhook.
- Optional: a KV namespace bound as RL for free-download rate limiting.
Secrets are set on the Pages project, never committed to the repo: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, LICENSE_SIGNING_SECRET, SITE_URL, and optionally MAIL_SEND_URL, MAIL_SEND_TOKEN, MAIL_FROM for email delivery.
Download secrets follow a fixed naming rule: DL_ plus the product's download_key, uppercased with dashes turned to underscores. For example, download_key al-license-zip reads from env DL_AL_LICENSE_ZIP. Paid entries hold an ARTIFACTS object key; the free entry can hold a plain URL.
Setup outline
Each function, by name
- catalog.js GET /api/catalog. The public product list. Reads active SKUs from the products table when D1 is bound, falls back to a static list otherwise, and never exposes download keys.
- checkout.js POST /api/checkout. Creates a Stripe Checkout Session for a SKU. The price and name always come from the server-side catalog, never the request body, and it refuses to sell any SKU whose DL_ artifact secret is not yet provisioned. Each request carries a per-request Idempotency-Key header to Stripe, so a retried click cannot create a duplicate session.
- webhook.js POST /api/webhook. The Stripe webhook. On a paid checkout.session.completed event it issues a license key into D1 and emails it. It verifies the Stripe signature itself, HMAC-SHA256 with a 300 second replay tolerance, and is idempotent, a redelivered event reuses the same key.
- download.js GET /api/download. Streams the private artifact. Free SKUs download openly; paid SKUs must present a paid Stripe session whose metadata SKU matches before anything streams. A successful stream sets Content-Disposition to attachment with a sku.zip filename and Cache-Control to no-store.
- free.js POST /api/free. The free lead-magnet path. Issues a free-tier key for a free SKU with no payment, returns the key and the download link, and optionally emails it. Soft per-IP rate limiting (5 issues per hour) applies only if the RL binding is present.
- order.js GET /api/order. What the delivery page calls after checkout. It accepts only a live-format session id and reads the matching signed-webhook entitlement from D1. It never mints a license from the redirect parameter.
After checkout
What the delivery page gets back
The delivery page calls GET /api/order with the Stripe session id and renders the key, product and exact gated download only after the webhook entitlement exists.
GET /api/order?session_id=cs_... # { "ok": true, "key": "APLU-...", "product": "al-store", "product_name": "AL Store Platform", # "tier": "standard", "max_activations": 1, "email": "[email protected]", # "download_url": "/api/download?sku=al-store&session_id=cs_...", "support_url": "mailto:..." }
What you deploy
A Cloudflare Pages project
The project deploys as a Cloudflare Pages site with Functions, bound to a D1 database for the license tables and to an R2 bucket for the paid product artifacts. It pairs with AL License System for the underlying validate API, and every sale mints a key the same way this store does.
A checkout that is not ready fails closed: the SKU must be active in D1 and its per-SKU artifact mapping must exist before a buyer can be sent to Stripe.