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>
108 lines
3.2 KiB
PL/PgSQL
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';
|