# DashCaddy License Server Implementation Plan ## Goal Connect live Stripe billing to DashCaddy Premium licensing without inventing a second license model. ## Existing DashCaddy integration points The main DashCaddy app already has the core license hooks: - `dashcaddy-api/license-keygen.js` - `dashcaddy-api/license-manager.js` - `dashcaddy-api/routes/license.js` The app already supports: - `LICENSE_SERVER_URL` - online validation at `/api/license/validate` - online deactivation at `/api/license/deactivate` - offline HMAC validation fallback - premium feature checks for: - `sso` - `recipes` - `swarm` ## Locked product decisions ### Free tier Everything works without a license except premium-gated features. ### Premium tier Paid subscriptions unlock: - `sso` - `recipes` - `swarm` ### Stripe plans - 1 month — $25 - 3 months — $50 - 6 months — $65 - 12 months — $99 ### Subscription policy - recurring subscriptions - 7-day grace period on failed payment - cancel at period end - one active machine at a time - transfer requires deactivation from previous machine - no public lifetime plan ## Recommended system shape ### Website (`dashcaddy.net`) Responsibilities: - show Premium pricing cards - create Stripe Checkout sessions through the license server - redirect to Stripe Checkout - return users to success/cancel pages ### License server (`dashcaddy-license-server`) Responsibilities: - create Stripe Checkout sessions - ingest Stripe webhooks - maintain customer/license/subscription state - issue and renew DashCaddy-compatible licenses - validate license activations - enforce one-active-machine policy - accept deactivation requests - provide admin support tooling later ### Stripe Source of truth for: - customers - subscriptions - invoices - payment state - cancellation state ## Proposed API surface ### Public endpoints for website - `POST /api/checkout/session` - `GET /api/public/plans` ### DashCaddy app endpoints - `POST /api/license/validate` - `POST /api/license/deactivate` ### Stripe webhook endpoint - `POST /api/stripe/webhook` ### Future admin endpoints - `GET /api/admin/licenses/:code` - `POST /api/admin/licenses/:code/override` - `POST /api/admin/licenses/:code/reset-activation` ## Data model ### customers - id - email - stripe_customer_id - created_at - updated_at ### subscriptions - id - customer_id - stripe_subscription_id - stripe_price_id - plan_code - status - current_period_start - current_period_end - cancel_at_period_end - grace_until - created_at - updated_at ### licenses - id - customer_id - subscription_id - license_code - tier - features_json - status - expires_at - last_validated_at - created_at - updated_at ### activations - id - license_id - machine_fingerprint - hostname - platform - arch - cpu - mac_hash - activated_at - deactivated_at - status ### stripe_events - id - stripe_event_id - type - processed_at - payload_json ## Stripe plan mapping One Premium product, four recurring prices: - `premium_monthly` - `premium_3month` - `premium_6month` - `premium_12month` All plans unlock the same Premium features. Only the billing interval differs. ## Activation logic ### Validation request from DashCaddy Expected inputs: - license code - machine fingerprint - machine metadata Validation rules: 1. license exists and is active 2. subscription is active or in grace period 3. license not expired 4. no active activation on a different machine 5. if first activation, bind to this machine 6. if same machine, refresh validation 7. if different machine while old activation still active, reject ### Deactivation request 1. find active activation for license 2. mark activation deactivated 3. free license for reuse on another machine ## Renewal logic On successful Stripe renewal: - keep same customer/license relationship - extend effective entitlement through subscription period - license remains active continuously On payment failure: - mark subscription past_due-like state - set `grace_until = current time + 7 days` On cancellation: - keep access until current_period_end - then expire ## Website integration plan The `dashcaddy.net` marketing site should be updated to: - add Premium pricing section buttons - each button POSTs to `/api/checkout/session` with a plan code - redirect to returned Stripe Checkout URL - success page explains license delivery/activation flow - cancellation page returns user to pricing ## Deployment target Deploy `dashcaddy-license-server` on Contabo. Initial env expected: - `STRIPE_SECRET_KEY` - `STRIPE_WEBHOOK_SECRET` - `STRIPE_PUBLISHABLE_KEY` - `APP_BASE_URL` - `DASHCADDY_WEBSITE_URL` - `DATABASE_URL` - `LICENSE_SIGNING_SECRET` or reused DashCaddy-compatible secret path ## Immediate next build steps 1. Scaffold Node service for `dashcaddy-license-server` 2. Add Stripe SDK, Express, and SQLite/Postgres adapter layer 3. Build `/api/public/plans` 4. Build `/api/checkout/session` 5. Build `/api/stripe/webhook` 6. Port/reuse DashCaddy-compatible license generation/validation helpers 7. Build `/api/license/validate` 8. Build `/api/license/deactivate` 9. Wire pricing CTA buttons into `dashcaddy.net` 10. Test with Stripe test mode flow before flipping deployed env to live operation