99 lines
4.0 KiB
Markdown
99 lines
4.0 KiB
Markdown
# 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.
|