Add Stripe billing, free trials, and cross-platform subscription guards

- Stripe integration: add StripeService with checkout sessions, customer
  portal, and webhook handling for subscription lifecycle events.
- Free trials: auto-start configurable trial on first subscription check,
  with admin-controllable duration and enable/disable toggle.
- Cross-platform guard: prevent duplicate subscriptions across iOS, Android,
  and Stripe by checking existing platform before allowing purchase.
- Subscription model: add Stripe fields (customer_id, subscription_id,
  price_id), trial fields (trial_start, trial_end, trial_used), and
  SubscriptionSource/IsTrialActive helpers.
- API: add trial and source fields to status response, update OpenAPI spec.
- Clean up stale migration and audit docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-03-05 11:36:14 -06:00
parent d5bb123cd0
commit 72db9050f8
35 changed files with 1555 additions and 1120 deletions

View File

@@ -2350,6 +2350,121 @@ paths:
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/checkout/:
post:
tags: [Subscriptions]
operationId: createCheckoutSession
summary: Create a Stripe Checkout session for web subscription purchase
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [price_id, success_url, cancel_url]
properties:
price_id:
type: string
description: Stripe Price ID
success_url:
type: string
format: uri
cancel_url:
type: string
format: uri
responses:
'200':
description: Checkout session created
content:
application/json:
schema:
type: object
properties:
checkout_url:
type: string
format: uri
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
'409':
description: Already subscribed on another platform
content:
application/json:
schema:
type: object
properties:
error:
type: string
existing_platform:
type: string
message:
type: string
/subscription/portal/:
post:
tags: [Subscriptions]
operationId: createPortalSession
summary: Create a Stripe Customer Portal session for managing web subscriptions
security:
- tokenAuth: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [return_url]
properties:
return_url:
type: string
format: uri
responses:
'200':
description: Portal session created
content:
application/json:
schema:
type: object
properties:
portal_url:
type: string
format: uri
'400':
$ref: '#/components/responses/Error'
'401':
$ref: '#/components/responses/Unauthorized'
/subscription/webhook/stripe/:
post:
tags: [Subscriptions]
operationId: handleStripeWebhook
summary: Handle Stripe webhook events (server-to-server)
description: |
Receives Stripe webhook events for subscription lifecycle management.
Verifies the webhook signature using the configured signing secret.
No auth token required — uses Stripe signature verification.
requestBody:
required: true
content:
application/json:
schema:
type: object
responses:
'200':
description: Webhook processed successfully
content:
application/json:
schema:
type: object
properties:
received:
type: boolean
'400':
$ref: '#/components/responses/Error'
# ===========================================================================
# Uploads
# ===========================================================================
@@ -4434,6 +4549,12 @@ components:
SubscriptionStatusResponse:
type: object
properties:
tier:
type: string
description: 'Subscription tier (free or pro)'
is_active:
type: boolean
description: Whether the subscription is currently active
subscribed_at:
type: string
format: date-time
@@ -4444,6 +4565,20 @@ components:
nullable: true
auto_renew:
type: boolean
trial_start:
type: string
format: date-time
nullable: true
trial_end:
type: string
format: date-time
nullable: true
trial_active:
type: boolean
subscription_source:
type: string
nullable: true
description: 'Platform source of the active subscription (ios, android, stripe, or null)'
usage:
$ref: '#/components/schemas/UsageResponse'
limits: