Catalogue & product data
Digital Download Entitlement
Generated from docs/plan/core/digital-download-entitlement/PLAN.md in the AstroBaaS repository. Nothing described below is implemented — it is the written plan for it.
A fulfillment model for digital products (PDFs, videos, software, music) where customers receive time-limited download links, audit trails show who downloaded what, and merchants see delivery reports. Entitlements are granted per order and revoked after expiry or refund. Completes the product-type system for merchants selling digital goods.
The problem
I sell PDF guides and video courses. When a customer buys, they need a download link, but I have no way to create time-limited links or see who downloaded what. I need to limit downloads to 30 days, track all downloads for audit, and revoke access on refund.
What it does
- Digital product type: select ‘Digital’ when creating product; upload file or provide URL (not stored in AstroBaaS)
- Download entitlement: when order is placed, entitlement is created (download_link + expiry_date)
- Download link: time-limited URL (valid for 30 days default, configurable per product) that signs request and verifies entitlement
- Download count limit: merchant can set max downloads per order (e.g., ‘max 3 downloads’)
- Download tracking: log each download with timestamp, IP, user agent, file name; show download report in order detail
- Email delivery: send download link in order confirmation email (or as separate ‘Your Download is Ready’ email)
- Refund revocation: when order is refunded, entitlements are immediately revoked (links expire)
- Download audit report: export all downloads per product (who, when, IP, success/failure) for audit purposes
- Resend download link: merchant or customer can request link to be resent if lost
- File hosting: link points to external storage (S3, CDN, merchant’s server) — AstroBaaS doesn’t host files
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.
- License key generation (per-customer unique license) — that is licensing module. This is download links only.
- Phone-home activation (software calls home to validate license) — that is license activation, separate feature.
- DRM or copy protection — that is content protection module; AstroBaaS does not DRM-protect files.
- Version control (customer downloads v1.0, merchant publishes v2.0, old link auto-upgrades) — that is version management.
- Device pairing (limit downloads to specific devices) — that is device licensing; this is download-link-only.
Data model
New field on PRODUCT: product_type (enum: ‘simple’ | ‘digital’ | ‘variable’ | ‘bundle’ | ‘grouped’). New DIGITAL_PRODUCT table (product_id, file_url, file_size_mb, max_downloads_per_order, expiry_days_default). New DOWNLOAD_ENTITLEMENT table (entitlement_id, order_id, product_id, download_link_token, created_at, expires_at, download_count, is_revoked). New DOWNLOAD_LOG table (log_id, entitlement_id, downloaded_at, ip_address, user_agent, success). Migration required: update PRODUCT.product_type.
API
- POST /admin/products (create digital product with file_url, max_downloads, expiry_days)
- GET /admin/products/:id (if digital: show file_url, max_downloads, expiry_days, download stats)
- GET /orders/:orderId/downloads (list entitlements for order)
- GET /downloads/:token (public: verify token, log download, return signed redirect to file_url)
- POST /orders/:orderId/downloads/:id/resend (send download link to customer email again)
- GET /admin/products/:id/download-report (export CSV of all downloads for this product)
Every route added here must also appear in src/pages/openapi.json.ts — a test
fails the build if it does not.
Admin
Product create form: product_type=‘Digital’. Digital section shows file_url (path to external storage), max_downloads_per_order, expiry_days. Order detail: Downloads section shows entitlements with link, expiry date, download count, and ‘Resend’ button. Download audit report page: export downloads per product with date, IP, success/failure.
The seam — why this is core
Core owns entitlement schema, download link generation and verification, and audit logging. Core does not own file hosting (merchant provides URL) or license key generation (that is licensing module). Core provides the download mechanism; paid modules add DRM, licensing, or version control on top.
Schema exists; implementation does not. Delivers fulfillment for digital products. Completes the product-type system.
Dependencies
- Order management (core, existing)
- Email system (core, existing)
- Product model (core, existing)
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.
- Digital product ‘PDF Guide’ is created with file_url=‘https://cdn.example.com/guide.pdf’, expiry_days=30
- Customer purchases Digital product; entitlement is created with download_link_token and expires_at=now+30 days
- Email sent to customer with download link; link is valid and downloads file for 30 days
- Download attempt after 31 days: link returns 401 ‘Entitlement Expired’
- Max downloads per order is set to 3; on 4th download, link returns 403 ‘Download limit exceeded’
- Download log records: timestamp, IP, user agent, success=true/false for each download
- Order is refunded; entitlement.is_revoked=true and link returns 403 ‘Entitlement Revoked’
- Export download report shows all downloads for product with date, IP, download_count per entitlement
Risks
If download token is predictable (sequential integers), attackers enumerate and download without owning order. Mitigate: use strong random tokens (128-bit) or HMACs. If download link is not rate-limited, attackers hammer it (DDoS or quota exhaustion). Mitigate: rate-limit per token (e.g., 10 downloads/min). If file_url is exposed in response, attacker can bypass entitlement check and download directly from storage. Mitigate: generate signed redirect URLs with expiry, never return raw file_url. If refund doesn’t revoke entitlement immediately, customer keeps access after refund (revenue loss). Mitigate: atomic refund + revoke operation.
Commercial context
| Suggested price | Free; completes product types |
| Rival anchor | Magento Open Source downloadable products; free |
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.