[PROP-CORE-004] feat: Add payment_terminals schema DDL

Propagated from erp-core v1.5.0
Use: Pagos de consultas y procedimientos
Note: PCI-DSS compliant - no card data in clinical records

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-01-25 02:46:56 -06:00
parent cf07a84e26
commit 313c750629

View File

@ -0,0 +1,276 @@
-- =============================================================
-- ARCHIVO: 05-payment-terminals.sql
-- DESCRIPCION: Terminales de pago TPV (MercadoPago, Clip)
-- VERSION: 1.0.0
-- PROYECTO: ERP-Clinicas
-- FECHA: 2026-01-25
-- PROPAGADO DESDE: erp-core v1.5.0 (PROP-CORE-004)
-- =============================================================
-- Uso: Pagos de consultas, procedimientos, tratamientos
-- NOTA: NO almacenar datos de tarjeta en expediente clinico
-- =============================================================
-- =====================
-- SCHEMA: payment_terminals
-- =====================
CREATE SCHEMA IF NOT EXISTS payment_terminals;
-- =====================
-- ENUMS
-- =====================
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'terminal_provider') THEN
CREATE TYPE payment_terminals.terminal_provider AS ENUM (
'mercadopago',
'clip',
'stripe_terminal'
);
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'terminal_payment_status') THEN
CREATE TYPE payment_terminals.terminal_payment_status AS ENUM (
'pending',
'processing',
'approved',
'authorized',
'in_process',
'rejected',
'refunded',
'partially_refunded',
'cancelled',
'charged_back'
);
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'payment_method_type') THEN
CREATE TYPE payment_terminals.payment_method_type AS ENUM (
'card',
'qr',
'link',
'cash',
'bank_transfer'
);
END IF;
END$$;
-- =====================
-- TABLA: tenant_terminal_configs
-- =====================
CREATE TABLE IF NOT EXISTS payment_terminals.tenant_terminal_configs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
provider payment_terminals.terminal_provider NOT NULL,
name VARCHAR(100) NOT NULL,
credentials JSONB NOT NULL DEFAULT '{}',
config JSONB DEFAULT '{}',
is_active BOOLEAN DEFAULT true,
is_verified BOOLEAN DEFAULT false,
verification_error TEXT,
verified_at TIMESTAMPTZ,
daily_limit DECIMAL(12,2),
monthly_limit DECIMAL(12,2),
transaction_limit DECIMAL(12,2),
webhook_url VARCHAR(500),
webhook_secret VARCHAR(255),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id),
UNIQUE(tenant_id, provider, name)
);
-- =====================
-- TABLA: terminal_payments
-- =====================
CREATE TABLE IF NOT EXISTS payment_terminals.terminal_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
config_id UUID REFERENCES payment_terminals.tenant_terminal_configs(id),
branch_terminal_id UUID,
provider payment_terminals.terminal_provider NOT NULL,
external_id VARCHAR(255),
external_status VARCHAR(50),
amount DECIMAL(12,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'MXN',
status payment_terminals.terminal_payment_status DEFAULT 'pending',
payment_method payment_terminals.payment_method_type DEFAULT 'card',
card_last_four VARCHAR(4),
card_brand VARCHAR(20),
card_type VARCHAR(20),
customer_email VARCHAR(255),
customer_phone VARCHAR(20),
customer_name VARCHAR(200),
description TEXT,
statement_descriptor VARCHAR(50),
reference_type VARCHAR(50),
reference_id UUID,
fee_amount DECIMAL(10,4),
fee_details JSONB,
net_amount DECIMAL(12,2),
refunded_amount DECIMAL(12,2) DEFAULT 0,
refund_reason TEXT,
provider_response JSONB,
error_code VARCHAR(50),
error_message TEXT,
processed_at TIMESTAMPTZ,
refunded_at TIMESTAMPTZ,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id)
);
-- =====================
-- TABLA: terminal_webhook_events
-- =====================
CREATE TABLE IF NOT EXISTS payment_terminals.terminal_webhook_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
provider payment_terminals.terminal_provider NOT NULL,
event_type VARCHAR(100) NOT NULL,
event_id VARCHAR(255),
payment_id UUID REFERENCES payment_terminals.terminal_payments(id),
external_id VARCHAR(255),
payload JSONB NOT NULL,
headers JSONB,
signature_valid BOOLEAN,
processed BOOLEAN DEFAULT false,
processed_at TIMESTAMPTZ,
processing_error TEXT,
retry_count INTEGER DEFAULT 0,
idempotency_key VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
UNIQUE(provider, event_id)
);
-- =====================
-- TABLA: terminal_retry_queue
-- =====================
CREATE TABLE IF NOT EXISTS payment_terminals.terminal_retry_queue (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id),
payment_id UUID REFERENCES payment_terminals.terminal_payments(id),
provider payment_terminals.terminal_provider NOT NULL,
operation VARCHAR(50) NOT NULL,
payload JSONB NOT NULL,
attempts INTEGER DEFAULT 0,
max_attempts INTEGER DEFAULT 5,
next_retry_at TIMESTAMPTZ,
last_error TEXT,
last_error_code VARCHAR(50),
status VARCHAR(20) DEFAULT 'pending',
completed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
);
-- =====================
-- TABLA: terminal_payment_links
-- =====================
CREATE TABLE IF NOT EXISTS payment_terminals.terminal_payment_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL REFERENCES auth.tenants(id) ON DELETE CASCADE,
config_id UUID REFERENCES payment_terminals.tenant_terminal_configs(id),
provider payment_terminals.terminal_provider NOT NULL,
external_id VARCHAR(255),
url VARCHAR(1000) NOT NULL,
short_url VARCHAR(500),
amount DECIMAL(12,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'MXN',
title VARCHAR(200),
description TEXT,
status VARCHAR(30) DEFAULT 'active',
payment_id UUID REFERENCES payment_terminals.terminal_payments(id),
expires_at TIMESTAMPTZ,
reference_type VARCHAR(50),
reference_id UUID,
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
created_by UUID REFERENCES auth.users(id)
);
-- =====================
-- INDICES
-- =====================
CREATE INDEX IF NOT EXISTS idx_terminal_configs_tenant ON payment_terminals.tenant_terminal_configs(tenant_id);
CREATE INDEX IF NOT EXISTS idx_terminal_configs_provider ON payment_terminals.tenant_terminal_configs(provider);
CREATE INDEX IF NOT EXISTS idx_terminal_payments_tenant ON payment_terminals.terminal_payments(tenant_id);
CREATE INDEX IF NOT EXISTS idx_terminal_payments_status ON payment_terminals.terminal_payments(status);
CREATE INDEX IF NOT EXISTS idx_terminal_payments_external ON payment_terminals.terminal_payments(external_id);
CREATE INDEX IF NOT EXISTS idx_terminal_payments_reference ON payment_terminals.terminal_payments(reference_type, reference_id);
CREATE INDEX IF NOT EXISTS idx_webhook_events_tenant ON payment_terminals.terminal_webhook_events(tenant_id);
CREATE INDEX IF NOT EXISTS idx_webhook_events_processed ON payment_terminals.terminal_webhook_events(processed, created_at) WHERE processed = false;
CREATE INDEX IF NOT EXISTS idx_retry_queue_next ON payment_terminals.terminal_retry_queue(next_retry_at) WHERE status = 'pending';
CREATE INDEX IF NOT EXISTS idx_payment_links_tenant ON payment_terminals.terminal_payment_links(tenant_id);
CREATE INDEX IF NOT EXISTS idx_payment_links_status ON payment_terminals.terminal_payment_links(status);
-- =====================
-- RLS POLICIES
-- =====================
ALTER TABLE payment_terminals.tenant_terminal_configs ENABLE ROW LEVEL SECURITY;
ALTER TABLE payment_terminals.terminal_payments ENABLE ROW LEVEL SECURITY;
ALTER TABLE payment_terminals.terminal_webhook_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE payment_terminals.terminal_retry_queue ENABLE ROW LEVEL SECURITY;
ALTER TABLE payment_terminals.terminal_payment_links ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_configs_isolation ON payment_terminals.tenant_terminal_configs
USING (tenant_id = current_setting('app.current_tenant', true)::UUID);
CREATE POLICY terminal_payments_isolation ON payment_terminals.terminal_payments
USING (tenant_id = current_setting('app.current_tenant', true)::UUID);
CREATE POLICY webhook_events_isolation ON payment_terminals.terminal_webhook_events
USING (tenant_id = current_setting('app.current_tenant', true)::UUID);
CREATE POLICY retry_queue_isolation ON payment_terminals.terminal_retry_queue
USING (tenant_id = current_setting('app.current_tenant', true)::UUID);
CREATE POLICY payment_links_isolation ON payment_terminals.terminal_payment_links
USING (tenant_id = current_setting('app.current_tenant', true)::UUID);
-- =====================
-- FUNCIONES
-- =====================
CREATE OR REPLACE FUNCTION payment_terminals.calculate_next_retry(
p_attempts INTEGER,
p_base_delay_seconds INTEGER DEFAULT 60
) RETURNS TIMESTAMPTZ AS $$
BEGIN
RETURN NOW() + (p_base_delay_seconds * POWER(2, p_attempts)) * INTERVAL '1 second';
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION payment_terminals.expire_old_pending_payments()
RETURNS INTEGER AS $$
DECLARE
affected_rows INTEGER;
BEGIN
UPDATE payment_terminals.terminal_payments
SET status = 'cancelled', updated_at = NOW(), error_message = 'Payment expired'
WHERE status = 'pending' AND created_at < NOW() - INTERVAL '24 hours';
GET DIAGNOSTICS affected_rows = ROW_COUNT;
RETURN affected_rows;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION payment_terminals.expire_payment_links()
RETURNS INTEGER AS $$
DECLARE
affected_rows INTEGER;
BEGIN
UPDATE payment_terminals.terminal_payment_links
SET status = 'expired', updated_at = NOW()
WHERE status = 'active' AND expires_at IS NOT NULL AND expires_at < NOW();
GET DIAGNOSTICS affected_rows = ROW_COUNT;
RETURN affected_rows;
END;
$$ LANGUAGE plpgsql;
COMMENT ON SCHEMA payment_terminals IS 'Terminales de pago TPV - Clinicas (PCI-DSS compliant)';
-- =============================================================
-- FIN DEL ARCHIVO
-- =============================================================