-- ============================================ -- Migration: V20260120_002 -- Description: Migrate billing.subscriptions structure -- Changes: -- - Add stripe_subscription_id, stripe_customer_id columns -- - Add interval column (ENUM: month, year) -- - Add trial_start, cancel_at, cancel_reason columns -- - Add price_amount, currency columns -- - Rename cancelled_at -> canceled_at -- - Change billing.subscription_status enum value: 'trial' -> 'trialing' -- ============================================ -- UP Migration BEGIN; -- ============================================ -- 1. Ensure billing_interval ENUM exists -- ============================================ DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_type WHERE typname = 'billing_interval' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'billing') ) THEN CREATE TYPE billing.billing_interval AS ENUM ('month', 'year'); RAISE NOTICE 'Created billing.billing_interval enum'; END IF; END $$; -- ============================================ -- 2. Fix billing.subscription_status enum (trial -> trialing) -- ============================================ DO $$ DECLARE has_trial_value BOOLEAN; BEGIN -- Check if 'trial' exists in the enum (needs migration) SELECT EXISTS ( SELECT 1 FROM pg_enum e JOIN pg_type t ON e.enumtypid = t.oid JOIN pg_namespace n ON t.typnamespace = n.oid WHERE n.nspname = 'billing' AND t.typname = 'subscription_status' AND e.enumlabel = 'trial' ) INTO has_trial_value; IF has_trial_value THEN -- PostgreSQL doesn't allow renaming enum values directly -- We need to create a new type and migrate -- Step 1: Create new enum type CREATE TYPE billing.subscription_status_new AS ENUM ('trialing', 'active', 'past_due', 'cancelled', 'expired'); -- Step 2: Update columns using the old enum -- First, change column to use text temporarily ALTER TABLE billing.subscriptions ALTER COLUMN status TYPE VARCHAR(50) USING status::VARCHAR(50); -- Step 3: Update 'trial' to 'trialing' UPDATE billing.subscriptions SET status = 'trialing' WHERE status = 'trial'; -- Step 4: Drop old type DROP TYPE IF EXISTS billing.subscription_status; -- Step 5: Rename new type ALTER TYPE billing.subscription_status_new RENAME TO subscription_status; -- Step 6: Convert column back to enum ALTER TABLE billing.subscriptions ALTER COLUMN status TYPE billing.subscription_status USING status::billing.subscription_status; -- Step 7: Restore default ALTER TABLE billing.subscriptions ALTER COLUMN status SET DEFAULT 'trialing'::billing.subscription_status; RAISE NOTICE 'Migrated subscription_status enum: trial -> trialing'; END IF; END $$; -- ============================================ -- 3. Add Stripe columns if they don't exist -- ============================================ -- stripe_subscription_id DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'stripe_subscription_id' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN stripe_subscription_id VARCHAR(255) UNIQUE; CREATE INDEX idx_subscriptions_stripe ON billing.subscriptions(stripe_subscription_id); RAISE NOTICE 'Added stripe_subscription_id column'; END IF; END $$; -- stripe_customer_id DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'stripe_customer_id' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN stripe_customer_id VARCHAR(255); RAISE NOTICE 'Added stripe_customer_id column'; END IF; END $$; -- ============================================ -- 4. Add interval column -- ============================================ DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'interval' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN "interval" billing.billing_interval DEFAULT 'month'; RAISE NOTICE 'Added interval column'; END IF; END $$; -- ============================================ -- 5. Add trial and cancellation columns -- ============================================ -- trial_start DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'trial_start' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN trial_start TIMESTAMPTZ; RAISE NOTICE 'Added trial_start column'; END IF; END $$; -- cancel_at (scheduled cancellation date) DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'cancel_at' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN cancel_at TIMESTAMPTZ; RAISE NOTICE 'Added cancel_at column'; END IF; END $$; -- cancel_reason DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'cancel_reason' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN cancel_reason VARCHAR(500); RAISE NOTICE 'Added cancel_reason column'; END IF; END $$; -- ============================================ -- 6. Add pricing columns -- ============================================ -- price_amount DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'price_amount' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN price_amount DECIMAL(10, 2); RAISE NOTICE 'Added price_amount column'; END IF; END $$; -- currency DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'currency' ) THEN ALTER TABLE billing.subscriptions ADD COLUMN currency VARCHAR(3) DEFAULT 'USD'; RAISE NOTICE 'Added currency column'; END IF; END $$; -- ============================================ -- 7. Rename cancelled_at -> canceled_at -- ============================================ DO $$ BEGIN -- Check if cancelled_at exists (British spelling) IF EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_schema = 'billing' AND table_name = 'subscriptions' AND column_name = 'cancelled_at' ) THEN ALTER TABLE billing.subscriptions RENAME COLUMN cancelled_at TO canceled_at; RAISE NOTICE 'Renamed cancelled_at to canceled_at'; END IF; END $$; -- ============================================ -- 8. Update comments -- ============================================ COMMENT ON COLUMN billing.subscriptions.stripe_subscription_id IS 'Stripe subscription ID for payment tracking'; COMMENT ON COLUMN billing.subscriptions.stripe_customer_id IS 'Stripe customer ID'; COMMENT ON COLUMN billing.subscriptions."interval" IS 'Billing interval: month or year'; COMMENT ON COLUMN billing.subscriptions.trial_start IS 'When the trial period started'; COMMENT ON COLUMN billing.subscriptions.cancel_at IS 'Scheduled cancellation date (end of period)'; COMMENT ON COLUMN billing.subscriptions.cancel_reason IS 'Reason for cancellation'; COMMENT ON COLUMN billing.subscriptions.price_amount IS 'Price amount at subscription time'; COMMENT ON COLUMN billing.subscriptions.currency IS 'Currency code (ISO 4217)'; COMMIT;