# DashCaddy License Server Production billing and license fulfillment for purchases made on [dashcaddy.net](https://dashcaddy.net). Requires **Node.js 22.5 or newer** for the built-in `node:sqlite` transactional store. ## What it does - Creates Stripe Checkout sessions for one-time purchases and auto-renewing subscriptions. - Verifies signed Stripe webhooks with durable event idempotency. - Creates one stable DashCaddy license key per customer and extends that key on later purchases or renewals. - Tracks one-time and subscription paid-through components separately, so subscription failure cannot erase valid one-time access. - Emails the exact key accepted by the validation API and tracks each renewal email per Stripe invoice. - Keeps subscriptions active through their paid period; failed renewals receive a seven-day grace period. - Supports one-machine activation and deactivation. - Migrates the previous JSON store into a transactional SQLite database on first startup. ## Plans | Plan | One-time / renewal amount | Subscription interval | |---|---:|---:| | `premium_30d` | $20 | 1 month | | `premium_90d` | $50 | 3 months | | `premium_180d` | $70 | 6 months | | `premium_365d` | $99 | 1 year | Stripe remains the billing source of truth. SQLite is the entitlement and fulfillment source of truth. Server-managed keys use DashCaddy's HMAC-compatible code format for initial activation, but renewed expiry is authoritative online because a stable signed code cannot encode changing renewal dates. The DashCaddy client refreshes server-managed entitlements from the license server and does not create a new activation through offline fallback when that server is configured. ## Environment ```env PORT=3010 APP_BASE_URL=https://licenses.dashcaddy.net DASHCADDY_WEBSITE_URL=https://dashcaddy.net STRIPE_SECRET_KEY=sk_live_... STRIPE_PUBLISHABLE_KEY=pk_live_... STRIPE_WEBHOOK_SECRET=whsec_... DASHCADDY_LICENSE_SECRET=64_hex_characters_shared_with_dashcaddy DATA_DIR=./data ADMIN_TOKEN=generate-a-long-random-token SMTP_HOST=mail.sami-ahmed.net SMTP_PORT=587 SMTP_SECURE=false SMTP_USERNAME=licenses@dashcaddy.net SMTP_PASSWORD=... SMTP_FROM=licenses@dashcaddy.net # Keep certificate verification enabled in production. SMTP_TLS_REJECT_UNAUTHORIZED=true ``` ## Public HTTP endpoints - `GET /health` - `GET /api/public/config` - `GET /api/public/plans` - `POST /api/checkout/one-time` - `POST /api/checkout/subscription` - `GET /api/checkout/session/:sessionId` - `POST /api/stripe/webhook` - `POST /api/license/validate` - `POST /api/license/deactivate` `GET /api/admin/debug/store` requires `Authorization: Bearer $ADMIN_TOKEN` and is hidden with a 404 when no admin token is configured. ## Checkout request ```json { "planCode": "premium_30d", "customerEmail": "buyer@example.com" } ``` A successful checkout request returns a Stripe-hosted `url` and a `sessionId`. The website redirects to Stripe. After payment, Stripe calls the webhook, the license is created or extended, SMTP delivery is recorded, and the website polls `/api/checkout/session/:sessionId` to display the same key sent by email. ## Safety properties - Checkout accepts only valid plan codes and email addresses. - Browser CORS is restricted to `dashcaddy.net` and `www.dashcaddy.net`. - Checkout creation is rate limited. - Webhook event IDs and payment intent IDs are idempotent. - Store changes use SQLite transactions and WAL durability. - Repeated checkout sessions are stored independently. - Admin customer/license data is not public. - SMTP certificate verification is enabled by default. An email marked `delivered` means the configured SMTP provider accepted it; final inbox placement remains the receiving mail system's responsibility. The success page also displays the same valid key, so fulfillment does not depend on inbox delivery. ## Test ```bash npm test ``` The tests cover CORS, invalid checkout input, admin isolation, exact-key lookup and validation, payment idempotency, rate limiting, durable webhook claims, repeat checkout lookup, cancellation, and payment-grace expiry.