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