[PROP-CORE-004] feat: Add payment_terminals schema DDL
Propagated from erp-core v1.5.0 Use: Anticipos de obra, pagos parciales Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
8e118a84cd
commit
f1582ebd43
275
init-scripts/02-payment-terminals.sql
Normal file
275
init-scripts/02-payment-terminals.sql
Normal file
@ -0,0 +1,275 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- ARCHIVO: 02-payment-terminals.sql
|
||||||
|
-- DESCRIPCION: Terminales de pago TPV (MercadoPago, Clip)
|
||||||
|
-- VERSION: 1.0.0
|
||||||
|
-- PROYECTO: ERP-Construccion
|
||||||
|
-- FECHA: 2026-01-25
|
||||||
|
-- PROPAGADO DESDE: erp-core v1.5.0 (PROP-CORE-004)
|
||||||
|
-- =============================================================
|
||||||
|
-- Uso: Anticipos de obra, pagos parciales, materiales mostrador
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- 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 - Construccion';
|
||||||
|
-- =============================================================
|
||||||
|
-- FIN DEL ARCHIVO
|
||||||
|
-- =============================================================
|
||||||
Loading…
Reference in New Issue
Block a user