Setup guide // Self-hosted // Storefront

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.