AstroBaaS

Operations & platform

Downloadable products with entitlement

Free — GPL coresize Mplanned, not built

Generated from docs/plan/core/downloadable-products-with-entitlement/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.

Downloadable product schema exists but delivery does not. This feature completes the downloadable product flow: signed download links, access tracking, expiry/limit management.

The problem

I sell digital files (eBooks, software, music) but I have no system to limit downloads, track who accessed what, or expire access after a period.

What it does

  • Downloadable product entity: downloadable=true, downloads array [{filename, url, size_bytes}]
  • Entitlement: customer can download after purchase; create an Entitlement record {order_id, product_id, customer_id, download_count, download_limit, expires_at}
  • Signed download links: generate time-limited (24h), HMAC-signed URLs, e.g., /api/downloads/abc123?token=signed_jwt
  • Download tracking: log each download with customer_id, timestamp, IP, filename; surface in admin order view
  • Expiry: entitlement with expires_at in the past returns 403; customer can re-purchase to regain access
  • Download limits: if limit=5, customer can download each file up to 5 times; 6th attempt fails
  • API endpoints: create entitlement (on order creation), generate signed download link, get download history

What it deliberately does NOT do

Each boundary carries its reason. A boundary without a reason gets crossed by the next person who reads this.

  • Streaming (video/audio without download) — files are downloaded/stored locally by customer
  • DRM (digital rights management) or encryption — we provide no copy protection; rely on customer trust
  • Peer-to-peer distribution or torrent — centralized download from our server only

Data model

Entitlement {id, order_id, product_id, customer_id, download_count, download_limit (nullable), expires_at, created_at}. DownloadLog {id, entitlement_id, filename, downloaded_at, ip_address}. Update Product schema to include downloads array.

API

  • POST /api/downloads/create-entitlement — create entitlement (internal, called by order service on purchase)
  • POST /api/downloads/:entitlement_id/link — generate signed download URL (returns URL)
  • GET /api/downloads/:signed_token — perform download (verifies signature, checks expiry/limit, logs download)
  • GET /api/orders/:order_id/downloads — download history for order (customer or admin)

Every route added here must also appear in src/pages/openapi.json.ts — a test fails the build if it does not.

Admin

Order detail, Downloads section: list of entitlements for this order with status (active/expired), download limit/count, expires_at, download history (table with filename, download count, last downloaded at).

The seam — why this is core

Core owns the entitlement model, signed URL generation, and REST API. This is table-stakes: if downloadable schema exists in the database, the download feature must exist.

If you ship the schema, ship the feature. Incomplete downloadables are worse than shipping nothing.

Dependencies

  • Order system (already shipped)
  • File storage (already shipping in core)
  • JWT/HMAC signing library (assume available)

Acceptance checks

Each of these must be able to fail. Before claiming this is done, break the code deliberately and watch each one go red.

  • A customer purchases a downloadable product; an Entitlement is created with download_limit=unlimited, expires_at = 90 days from now
  • Customer receives a download link; clicking it returns a file (HTTP 200 with Content-Disposition: attachment)
  • Download is logged with customer_id, timestamp, and filename
  • Customer who downloads 5 times when limit=5 can still download; 6th attempt fails (403 Forbidden)
  • Customer after expiry date tries to download; request fails (403 Expired); customer can re-purchase
  • Signed URL includes a timestamp; URL is only valid for 24 hours (rejects old URLs)

Risks

Download links must be signed and time-limited; unsigned URLs expose files to unauthorized access. Download count must be atomic; concurrent downloads from same customer could race and bypass limit — mutex required. If file is large (video), download must stream (not load entire file in memory). File deletion must be managed; if a file is deleted from storage but is still in an Entitlement, download fails (document as known limitation).

Commercial context

Suggested priceCore
Rival anchorMagento Open Source, free: full downloadable product support.

The anchor is what the nearest equivalent charges on Shopify or Magento today. It is context for a pricing decision, not the decision.


Generated from the commerce plan. See docs/COMMERCE-PLAN.md for the full catalogue and ../../AI-GUIDE.md for how to work on this repository.