[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:
parent
cf07a84e26
commit
313c750629
276
init/05-payment-terminals.sql
Normal file
276
init/05-payment-terminals.sql
Normal 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
|
||||
-- =============================================================
|
||||
Loading…
Reference in New Issue
Block a user