[grade=B] Make website billing production-ready

This commit is contained in:
Krystie
2026-08-22 18:16:12 -07:00
parent 06d8062a5a
commit 2bfb8ea2b5
16 changed files with 1778 additions and 313 deletions
+69 -45
View File
@@ -1,54 +1,32 @@
# DashCaddy License Server
Stripe-driven license automation for DashCaddy.
Production billing and license fulfillment for purchases made on [dashcaddy.net](https://dashcaddy.net).
## Purpose
Requires **Node.js 22.5 or newer** for the built-in `node:sqlite` transactional store.
This service is the billing and license orchestration layer for DashCaddy.
It receives Stripe webhooks, maps purchases/subscriptions to license entitlements,
and exposes license validation/deactivation endpoints for DashCaddy instances.
## What it does
## Planned responsibilities
- 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.
- Verify Stripe webhook signatures
- Track customers, subscriptions, invoices, and purchases
- Generate or extend DashCaddy licenses
- Expose `/api/license/validate` for DashCaddy activation
- Expose `/api/license/deactivate` for DashCaddy deactivation
- Support renewals, expirations, cancellations, and grace periods
## Plans
## Architecture
| 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** is billing truth
- **License server database** is entitlement truth
- **DashCaddy app** remains the consumer of license validation
- Existing DashCaddy license logic should be reused, not reinvented
Stripe remains the billing source of truth. SQLite is the entitlement and fulfillment source of truth.
## Next steps
1. Extract/reuse the current DashCaddy license key generation and verification logic
2. Define DB schema for customers, licenses, activations, and Stripe mapping
3. Implement webhook ingestion and event processing
4. Implement validate/deactivate endpoints
5. Add admin tooling for manual recovery and support workflows
## Current implementation status
Implemented now:
- Stripe Checkout session creation
- Stripe webhook ingestion scaffold with subscription/license sync
- File-backed persistence for customers, subscriptions, and licenses
- License validation endpoint
- License deactivation endpoint
- One-machine-at-a-time activation enforcement
Still required before production:
- durable database
- email delivery for license keys
- deployment on Contabo
- Stripe webhook registration
- end-to-end live checkout verification
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
@@ -59,16 +37,62 @@ 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
```
## HTTP endpoints
## Public HTTP endpoints
- `GET /health`
- `GET /api/public/config`
- `GET /api/public/plans`
- `POST /api/checkout/session`
- `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`
`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.