template-saas-database-v2/ddl/schemas/billing/tables/01-subscriptions.sql
rckrdmrd 3ce06fbce4 Initial commit - Database de template-saas migrado desde monorepo
Migración desde workspace-v2/projects/template-saas/apps/database
Este repositorio es parte del estándar multi-repo v2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:07:11 -06:00

108 lines
3.2 KiB
PL/PgSQL

-- ============================================
-- TEMPLATE-SAAS: Subscriptions
-- Schema: billing
-- Version: 1.0.0
-- ============================================
CREATE TABLE billing.subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
plan_id UUID NOT NULL REFERENCES plans.plans(id),
-- Stripe
stripe_subscription_id VARCHAR(255) UNIQUE,
stripe_customer_id VARCHAR(255),
-- Status
status tenants.subscription_status DEFAULT 'trialing' NOT NULL,
-- Billing interval
interval billing.billing_interval DEFAULT 'month',
-- Current period
current_period_start TIMESTAMPTZ,
current_period_end TIMESTAMPTZ,
-- Trial
trial_start TIMESTAMPTZ,
trial_end TIMESTAMPTZ,
-- Cancellation
cancel_at TIMESTAMPTZ,
canceled_at TIMESTAMPTZ,
cancel_reason VARCHAR(500),
-- Pricing at subscription time
price_amount DECIMAL(10, 2),
currency VARCHAR(3) DEFAULT 'USD',
-- Metadata
metadata JSONB DEFAULT '{}'::jsonb,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
-- Constraints
CONSTRAINT unique_active_subscription UNIQUE (tenant_id) -- One active sub per tenant
);
-- Subscription items (for metered/usage-based)
CREATE TABLE billing.subscription_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
subscription_id UUID NOT NULL REFERENCES billing.subscriptions(id) ON DELETE CASCADE,
-- Stripe
stripe_subscription_item_id VARCHAR(255),
stripe_price_id VARCHAR(255),
-- Item details
product_name VARCHAR(200),
quantity INT DEFAULT 1,
-- Pricing
unit_amount DECIMAL(10, 2),
-- For metered billing
is_metered BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- Indexes
CREATE INDEX idx_subscriptions_tenant ON billing.subscriptions(tenant_id);
CREATE INDEX idx_subscriptions_stripe ON billing.subscriptions(stripe_subscription_id);
CREATE INDEX idx_subscriptions_status ON billing.subscriptions(status);
CREATE INDEX idx_subscription_items_sub ON billing.subscription_items(subscription_id);
-- RLS
ALTER TABLE billing.subscriptions ENABLE ROW LEVEL SECURITY;
ALTER TABLE billing.subscription_items ENABLE ROW LEVEL SECURITY;
CREATE POLICY subscriptions_tenant_isolation ON billing.subscriptions
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY subscription_items_tenant_isolation ON billing.subscription_items
USING (subscription_id IN (
SELECT id FROM billing.subscriptions
WHERE tenant_id = current_setting('app.current_tenant_id', true)::UUID
));
-- Trigger
CREATE OR REPLACE FUNCTION billing.update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_subscriptions_updated_at
BEFORE UPDATE ON billing.subscriptions
FOR EACH ROW
EXECUTE FUNCTION billing.update_updated_at();
-- Comments
COMMENT ON TABLE billing.subscriptions IS 'Active subscriptions per tenant';
COMMENT ON TABLE billing.subscription_items IS 'Line items within a subscription';