Files
ystp/migrations/017_billing_checkout_invariants.sql
2026-07-26 03:07:30 +08:00

60 lines
2.0 KiB
SQL

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';