# 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.
There is no Premium free trial. Premium is simply locked until a paid subscription activates a license.

### 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, with no trial period:
- `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
