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.
Checkout completes on Stripe, the webhook issues a unique license key into the database and emails it, and the delivery page reads the paid session and shows the key plus a gated download. If the webhook is ever missed, the delivery page issues the key on the spot, so fulfillment never stalls. Downloads stream through a function that checks the paid session, so a direct link cannot be shared to bypass payment.
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. Verifies the Stripe session is paid, then issues the license key idempotently and returns it with the download link and a managed-hosting link, the safety net if the webhook was missed.
After checkout
What the delivery page gets back
The delivery page calls GET /api/order with the Stripe session id and renders whatever comes back: the key, the product, the download link, and a managed-hosting link.
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_...", "managed_url": "..." }
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 yet ready to sell fails closed: a global enable flag and the per-SKU artifact guard in checkout.js both have to pass before a buyer can be charged, so a purchase can never outrun what the server can actually deliver.