CREATE TABLE durations ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(100) NOT NULL ); CREATE TABLE plans ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100), max_contacts BIGINT NOT NULL, daily_emails INT NOT NULL, ai_generation BOOLEAN NOT NULL, account_limit INT NOT NULL, price DECIMAL(10, 2) NOT NULL, discounted_price DECIMAL(10, 2) NOT NULL, duration_id UUID NOT NULL REFERENCES durations(id) ON DELETE CASCADE, savings SMALLINT, public BOOLEAN NOT NULL DEFAULT false, -- Stripe integration stripe_price_id VARCHAR(255), stripe_product_id VARCHAR(255), -- Organization limits max_campaigns INT, max_active_campaigns INT, max_team_members INT, max_email_accounts INT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT valid_savings CHECK (savings BETWEEN 0 AND 100) ); CREATE INDEX idx_plans_stripe_price ON plans(stripe_price_id); CREATE TABLE offers ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255) NOT NULL, description TEXT NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE offer_options ( offer_id UUID NOT NULL REFERENCES offers(id) ON DELETE CASCADE, plan_id UUID NOT NULL REFERENCES plans(id) ON DELETE CASCADE, title VARCHAR(255) NOT NULL, PRIMARY KEY (offer_id, plan_id) ); CREATE TABLE secret_plans ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, plan_id UUID NOT NULL REFERENCES plans(id) ON DELETE CASCADE, PRIMARY KEY (user_id, plan_id) ); -- Subscription status enum CREATE TYPE subscription_status AS ENUM ( 'trialing', 'active', 'past_due', 'canceled', 'unpaid', 'incomplete', 'incomplete_expired', 'paused' ); CREATE TABLE subscriptions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, plan_id UUID NOT NULL REFERENCES plans(id) ON DELETE RESTRICT, -- Stripe identifiers stripe_customer_id VARCHAR(255) NOT NULL, stripe_subscription_id VARCHAR(255) UNIQUE, stripe_price_id VARCHAR(255), -- Subscription state status subscription_status NOT NULL DEFAULT 'incomplete', -- Billing period current_period_start TIMESTAMPTZ, current_period_end TIMESTAMPTZ, cancel_at_period_end BOOLEAN NOT NULL DEFAULT FALSE, canceled_at TIMESTAMPTZ, -- Trial info trial_start TIMESTAMPTZ, trial_end TIMESTAMPTZ, -- Enterprise flag - when true, uses user_rate_limits for custom limits is_enterprise BOOLEAN NOT NULL DEFAULT FALSE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT unique_org_subscription UNIQUE (organization_id) ); CREATE INDEX idx_subscriptions_stripe_customer ON subscriptions(stripe_customer_id); CREATE INDEX idx_subscriptions_stripe_subscription ON subscriptions(stripe_subscription_id); CREATE INDEX idx_subscriptions_status ON subscriptions(status); CREATE INDEX idx_subscriptions_period_end ON subscriptions(current_period_end); CREATE INDEX idx_subscriptions_org ON subscriptions(organization_id); -- Stripe webhook events log for idempotency CREATE TABLE stripe_webhook_events ( id VARCHAR(255) PRIMARY KEY, event_type VARCHAR(100) NOT NULL, processed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), payload JSONB ); CREATE INDEX idx_stripe_events_type ON stripe_webhook_events(event_type); CREATE INDEX idx_stripe_events_processed ON stripe_webhook_events(processed_at);