trading-platform-database/ddl/schemas/financial/tables/03-subscriptions.sql

108 lines
4.8 KiB
SQL

-- =====================================================
-- ORBIQUANT IA - SUBSCRIPTIONS TABLE
-- =====================================================
-- Description: User subscription management with Stripe integration
-- Schema: financial
-- =====================================================
-- DECISION: Planes en USD como moneda base
-- =====================================================
CREATE TABLE financial.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE RESTRICT,
-- Plan y estado
plan financial.subscription_plan NOT NULL,
status financial.subscription_status NOT NULL DEFAULT 'incomplete',
-- Stripe integration
stripe_subscription_id VARCHAR(255) UNIQUE,
stripe_customer_id VARCHAR(255),
stripe_price_id VARCHAR(255),
stripe_product_id VARCHAR(255),
-- Pricing
price DECIMAL(10,2) NOT NULL,
currency financial.currency_code NOT NULL DEFAULT 'USD',
billing_interval VARCHAR(20) NOT NULL DEFAULT 'month', -- month, year
-- Billing periods
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
trial_start TIMESTAMPTZ,
trial_end TIMESTAMPTZ,
-- Cancelación
cancelled_at TIMESTAMPTZ,
cancel_at_period_end BOOLEAN DEFAULT false,
cancellation_reason TEXT,
cancellation_feedback JSONB,
-- Downgrade/Upgrade tracking
previous_plan financial.subscription_plan,
scheduled_plan financial.subscription_plan,
scheduled_plan_effective_at TIMESTAMPTZ,
-- Payment tracking
last_payment_at TIMESTAMPTZ,
next_payment_at TIMESTAMPTZ,
failed_payment_count INTEGER DEFAULT 0,
-- Features/Quotas (se pueden mover a tabla separada si crece)
metadata JSONB DEFAULT '{}',
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ended_at TIMESTAMPTZ,
-- Constraints
CONSTRAINT positive_price CHECK (price >= 0),
CONSTRAINT valid_billing_interval CHECK (billing_interval IN ('month', 'year')),
CONSTRAINT trial_dates_order CHECK (
(trial_start IS NULL AND trial_end IS NULL) OR
(trial_start IS NOT NULL AND trial_end IS NOT NULL AND trial_start < trial_end)
),
CONSTRAINT period_dates_order CHECK (
(current_period_start IS NULL AND current_period_end IS NULL) OR
(current_period_start IS NOT NULL AND current_period_end IS NOT NULL AND current_period_start < current_period_end)
),
CONSTRAINT cancel_date_valid CHECK (
(cancelled_at IS NULL) OR
(cancelled_at >= created_at)
),
CONSTRAINT ended_when_cancelled CHECK (
(ended_at IS NULL) OR
(cancelled_at IS NOT NULL AND ended_at >= cancelled_at)
),
CONSTRAINT scheduled_plan_different CHECK (
scheduled_plan IS NULL OR scheduled_plan != plan
)
);
-- Indexes
CREATE INDEX idx_subscriptions_user_id ON financial.subscriptions(user_id);
CREATE INDEX idx_subscriptions_status ON financial.subscriptions(status);
CREATE INDEX idx_subscriptions_plan ON financial.subscriptions(plan);
CREATE INDEX idx_subscriptions_stripe_sub ON financial.subscriptions(stripe_subscription_id) WHERE stripe_subscription_id IS NOT NULL;
CREATE INDEX idx_subscriptions_stripe_customer ON financial.subscriptions(stripe_customer_id) WHERE stripe_customer_id IS NOT NULL;
CREATE INDEX idx_subscriptions_active ON financial.subscriptions(user_id, status) WHERE status = 'active';
CREATE INDEX idx_subscriptions_period_end ON financial.subscriptions(current_period_end) WHERE status = 'active';
CREATE INDEX idx_subscriptions_trial_end ON financial.subscriptions(trial_end) WHERE status = 'trialing';
CREATE INDEX idx_subscriptions_next_payment ON financial.subscriptions(next_payment_at) WHERE next_payment_at IS NOT NULL;
-- Unique constraint: un usuario solo puede tener una suscripción activa a la vez
CREATE UNIQUE INDEX idx_subscriptions_user_active ON financial.subscriptions(user_id)
WHERE status IN ('active', 'trialing', 'past_due');
-- Comments
COMMENT ON TABLE financial.subscriptions IS 'User subscription management with Stripe integration';
COMMENT ON COLUMN financial.subscriptions.plan IS 'Subscription plan tier';
COMMENT ON COLUMN financial.subscriptions.status IS 'Subscription status (Stripe-compatible states)';
COMMENT ON COLUMN financial.subscriptions.price IS 'Subscription price in specified currency';
COMMENT ON COLUMN financial.subscriptions.billing_interval IS 'Billing frequency: month or year';
COMMENT ON COLUMN financial.subscriptions.cancel_at_period_end IS 'If true, subscription will cancel at end of current period';
COMMENT ON COLUMN financial.subscriptions.scheduled_plan IS 'Plan to switch to at scheduled_plan_effective_at';
COMMENT ON COLUMN financial.subscriptions.failed_payment_count IS 'Number of consecutive failed payment attempts';
COMMENT ON COLUMN financial.subscriptions.metadata IS 'Plan features, quotas, and additional configuration';