template-saas-database-v2/ddl/schemas/billing/tables/02-invoices.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

175 lines
5.2 KiB
PL/PgSQL

-- ============================================
-- TEMPLATE-SAAS: Invoices & Payments
-- Schema: billing
-- Version: 1.0.0
-- ============================================
CREATE TABLE billing.invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
subscription_id UUID REFERENCES billing.subscriptions(id),
-- Invoice number
invoice_number VARCHAR(50) UNIQUE NOT NULL,
-- Stripe
stripe_invoice_id VARCHAR(255) UNIQUE,
-- Status
status billing.invoice_status DEFAULT 'draft' NOT NULL,
-- Amounts
subtotal DECIMAL(10, 2) NOT NULL DEFAULT 0,
tax DECIMAL(10, 2) DEFAULT 0,
discount DECIMAL(10, 2) DEFAULT 0,
total DECIMAL(10, 2) NOT NULL DEFAULT 0,
amount_paid DECIMAL(10, 2) DEFAULT 0,
amount_due DECIMAL(10, 2) NOT NULL DEFAULT 0,
currency VARCHAR(3) DEFAULT 'USD',
-- Dates
invoice_date DATE NOT NULL DEFAULT CURRENT_DATE,
due_date DATE,
paid_at TIMESTAMPTZ,
-- Period
period_start DATE,
period_end DATE,
-- PDF
invoice_pdf_url VARCHAR(500),
hosted_invoice_url VARCHAR(500),
-- Customer info at time of invoice
customer_name VARCHAR(255),
customer_email VARCHAR(255),
billing_address JSONB,
-- Metadata
metadata JSONB DEFAULT '{}'::jsonb,
notes TEXT,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- Invoice line items
CREATE TABLE billing.invoice_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
invoice_id UUID NOT NULL REFERENCES billing.invoices(id) ON DELETE CASCADE,
-- Description
description VARCHAR(500) NOT NULL,
quantity INT DEFAULT 1,
-- Pricing
unit_amount DECIMAL(10, 2) NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
-- Period (for subscription items)
period_start DATE,
period_end DATE,
-- Stripe
stripe_invoice_item_id VARCHAR(255),
-- Metadata
metadata JSONB DEFAULT '{}'::jsonb,
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL
);
-- Payments
CREATE TABLE billing.payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
invoice_id UUID REFERENCES billing.invoices(id),
-- Stripe
stripe_payment_intent_id VARCHAR(255) UNIQUE,
stripe_charge_id VARCHAR(255),
-- Amount
amount DECIMAL(10, 2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
-- Status
status billing.payment_status DEFAULT 'pending' NOT NULL,
-- Payment method
payment_method_type VARCHAR(50), -- 'card', 'bank_transfer', etc.
payment_method_last4 VARCHAR(4),
payment_method_brand VARCHAR(50),
-- Failure info
failure_code VARCHAR(100),
failure_message TEXT,
-- Dates
created_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
updated_at TIMESTAMPTZ DEFAULT NOW() NOT NULL,
succeeded_at TIMESTAMPTZ,
failed_at TIMESTAMPTZ,
refunded_at TIMESTAMPTZ,
-- Refund info
refund_amount DECIMAL(10, 2),
refund_reason VARCHAR(500)
);
-- Indexes
CREATE INDEX idx_invoices_tenant ON billing.invoices(tenant_id);
CREATE INDEX idx_invoices_subscription ON billing.invoices(subscription_id);
CREATE INDEX idx_invoices_status ON billing.invoices(status);
CREATE INDEX idx_invoices_stripe ON billing.invoices(stripe_invoice_id);
CREATE INDEX idx_invoice_items_invoice ON billing.invoice_items(invoice_id);
CREATE INDEX idx_payments_tenant ON billing.payments(tenant_id);
CREATE INDEX idx_payments_invoice ON billing.payments(invoice_id);
CREATE INDEX idx_payments_stripe ON billing.payments(stripe_payment_intent_id);
-- RLS
ALTER TABLE billing.invoices ENABLE ROW LEVEL SECURITY;
ALTER TABLE billing.invoice_items ENABLE ROW LEVEL SECURITY;
ALTER TABLE billing.payments ENABLE ROW LEVEL SECURITY;
CREATE POLICY invoices_tenant_isolation ON billing.invoices
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
CREATE POLICY invoice_items_tenant_isolation ON billing.invoice_items
USING (invoice_id IN (
SELECT id FROM billing.invoices
WHERE tenant_id = current_setting('app.current_tenant_id', true)::UUID
));
CREATE POLICY payments_tenant_isolation ON billing.payments
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
-- Trigger
CREATE TRIGGER trg_invoices_updated_at
BEFORE UPDATE ON billing.invoices
FOR EACH ROW
EXECUTE FUNCTION billing.update_updated_at();
CREATE TRIGGER trg_payments_updated_at
BEFORE UPDATE ON billing.payments
FOR EACH ROW
EXECUTE FUNCTION billing.update_updated_at();
-- Invoice number sequence
CREATE SEQUENCE billing.invoice_number_seq START 1000;
-- Function to generate invoice number
CREATE OR REPLACE FUNCTION billing.generate_invoice_number()
RETURNS VARCHAR AS $$
BEGIN
RETURN 'INV-' || TO_CHAR(NOW(), 'YYYYMM') || '-' || LPAD(nextval('billing.invoice_number_seq')::TEXT, 6, '0');
END;
$$ LANGUAGE plpgsql;
-- Comments
COMMENT ON TABLE billing.invoices IS 'Invoices generated for billing';
COMMENT ON TABLE billing.invoice_items IS 'Line items within an invoice';
COMMENT ON TABLE billing.payments IS 'Payment records and attempts';