DO $$ BEGIN IF EXISTS ( SELECT 1 FROM users WHERE billing_customer_id IS NOT NULL AND billing_customer_id <> '' GROUP BY billing_customer_id HAVING COUNT(*) > 1 ) THEN RAISE EXCEPTION 'duplicate users.billing_customer_id values require Stripe reconciliation before migration 017'; END IF; IF EXISTS ( SELECT 1 FROM subscriptions WHERE provider = 'stripe' AND status <> 'canceled' GROUP BY user_id HAVING COUNT(*) > 1 ) THEN RAISE EXCEPTION 'multiple open Stripe subscriptions per user require reconciliation before migration 017'; END IF; END $$; CREATE UNIQUE INDEX IF NOT EXISTS idx_users_billing_customer_unique ON users(billing_customer_id) WHERE billing_customer_id IS NOT NULL AND billing_customer_id <> ''; CREATE UNIQUE INDEX IF NOT EXISTS idx_subscriptions_user_open_stripe_unique ON subscriptions(user_id) WHERE provider = 'stripe' AND status <> 'canceled'; CREATE TABLE IF NOT EXISTS billing_checkout_sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan_id UUID NOT NULL REFERENCES plans(id), stripe_customer_id VARCHAR(200), stripe_session_id VARCHAR(200), checkout_url TEXT, status VARCHAR(20) NOT NULL DEFAULT 'pending', expires_at TIMESTAMPTZ NOT NULL, lease_owner UUID, lease_until TIMESTAMPTZ, error_message TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), completed_at TIMESTAMPTZ, CONSTRAINT billing_checkout_sessions_status_check CHECK (status IN ('pending', 'completed', 'expired', 'failed', 'canceled')), CONSTRAINT billing_checkout_sessions_stripe_session_unique UNIQUE (stripe_session_id) ); CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_checkout_sessions_user_pending ON billing_checkout_sessions(user_id) WHERE status = 'pending'; CREATE INDEX IF NOT EXISTS idx_billing_checkout_sessions_expiry ON billing_checkout_sessions(expires_at) WHERE status = 'pending';