Three tables + lifecycle enum for Razorpay subscriptions: - subscription_plans: maps razorpay_plan_id -> grants (price/storage/member/child) - family_subscriptions: per-family sub state mirrored from Razorpay - razorpay_webhook_events: append-only log, razorpay_event_id = idempotency key - subscription_status_enum: mirrors Razorpay's lifecycle states exactly - partial unique index family_live_sub_idx: at most one non-terminal sub/family Notes: - Raw-SQL + Drizzle schema both added (repo uses raw sql`` at runtime; schema file keeps drizzle-kit + type inference working) - child_limit added to plan (not in original handoff) since premium lifts the free 1-baby cap to 3 per product decision - Migration 0012 idempotent; also added to debug-migration hot-apply steps - when=1780100000000 (> last entry, per journal drift rule) Entitlement will sync onto families.tier (Task 3/5) so existing quota.ts guards stay unchanged — these tables are audit + Razorpay state mirror. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.3 KiB
SQL
56 lines
2.3 KiB
SQL
-- Billing / Razorpay subscriptions.
|
|
-- Idempotent: safe to re-run (used by debug-migration hot-apply too).
|
|
|
|
-- Subscription lifecycle enum (mirrors Razorpay states).
|
|
DO $$ BEGIN
|
|
CREATE TYPE subscription_status_enum AS ENUM (
|
|
'created','authenticated','active','pending',
|
|
'halted','cancelled','completed','expired','paused'
|
|
);
|
|
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
|
|
|
|
-- Plans: maps a Razorpay plan_id -> what it grants.
|
|
CREATE TABLE IF NOT EXISTS subscription_plans (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
razorpay_plan_id text NOT NULL UNIQUE,
|
|
name text NOT NULL,
|
|
price_paise integer NOT NULL,
|
|
storage_bytes bigint NOT NULL,
|
|
member_limit integer NOT NULL,
|
|
child_limit integer NOT NULL DEFAULT 3,
|
|
is_active boolean NOT NULL DEFAULT true,
|
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- One subscription row per family (append on upgrade).
|
|
CREATE TABLE IF NOT EXISTS family_subscriptions (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
family_id uuid NOT NULL REFERENCES families(id) ON DELETE CASCADE,
|
|
plan_id uuid NOT NULL REFERENCES subscription_plans(id),
|
|
razorpay_subscription_id text NOT NULL UNIQUE,
|
|
razorpay_customer_id text,
|
|
status subscription_status_enum NOT NULL DEFAULT 'created',
|
|
current_start timestamptz,
|
|
current_end timestamptz,
|
|
cancelled_at timestamptz,
|
|
ended_at timestamptz,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS family_subscriptions_family_idx
|
|
ON family_subscriptions (family_id);
|
|
|
|
-- At most one LIVE (non-terminal) subscription per family.
|
|
CREATE UNIQUE INDEX IF NOT EXISTS family_live_sub_idx
|
|
ON family_subscriptions (family_id)
|
|
WHERE status IN ('created','authenticated','active','pending','halted');
|
|
|
|
-- Append-only webhook log. razorpay_event_id = idempotency key.
|
|
CREATE TABLE IF NOT EXISTS razorpay_webhook_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
razorpay_event_id text NOT NULL UNIQUE,
|
|
event_type text NOT NULL,
|
|
payload jsonb NOT NULL,
|
|
received_at timestamptz NOT NULL DEFAULT now()
|
|
);
|