trading-platform-database-v2/ddl/schemas/financial/tables/10-payment_methods.sql
rckrdmrd 45e77e9a9c feat: Initial commit - Database schemas and scripts
DDL schemas for Trading Platform:
- User management
- Authentication
- Payments
- Education
- ML predictions
- Trading data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-18 04:30:23 -06:00

181 lines
6.7 KiB
PL/PgSQL

-- ============================================================================
-- FINANCIAL SCHEMA - Tabla: payment_methods
-- ============================================================================
-- Metodos de pago guardados por usuarios para pagos recurrentes
-- Integra con Stripe para almacenamiento seguro de tarjetas y cuentas
-- ============================================================================
-- Enum para tipo de metodo de pago guardado
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'saved_payment_type' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'financial')) THEN
CREATE TYPE financial.saved_payment_type AS ENUM (
'card',
'bank_account',
'sepa_debit',
'crypto_wallet'
);
END IF;
END$$;
-- Enum para estado del metodo de pago
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'payment_method_status' AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'financial')) THEN
CREATE TYPE financial.payment_method_status AS ENUM (
'pending_verification',
'active',
'expired',
'failed',
'removed'
);
END IF;
END$$;
CREATE TABLE IF NOT EXISTS financial.payment_methods (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
-- Relaciones
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
customer_id UUID REFERENCES financial.customers(id) ON DELETE SET NULL,
-- Stripe integration
stripe_payment_method_id VARCHAR(100) UNIQUE,
stripe_fingerprint VARCHAR(100), -- Para detectar duplicados
-- Tipo y estado
payment_type financial.saved_payment_type NOT NULL,
status financial.payment_method_status NOT NULL DEFAULT 'pending_verification',
-- Informacion del metodo (datos no sensibles)
-- Para tarjetas: last4, brand, exp_month, exp_year
-- Para bancos: last4, bank_name, account_type
display_info JSONB NOT NULL DEFAULT '{}',
-- Metodo por defecto
is_default BOOLEAN NOT NULL DEFAULT FALSE,
-- Datos de tarjeta (solo informacion visible)
card_brand VARCHAR(20), -- 'visa', 'mastercard', 'amex', etc.
card_last4 VARCHAR(4),
card_exp_month INTEGER,
card_exp_year INTEGER,
card_funding VARCHAR(20), -- 'credit', 'debit', 'prepaid'
-- Datos de cuenta bancaria (solo informacion visible)
bank_name VARCHAR(100),
bank_last4 VARCHAR(4),
bank_account_type VARCHAR(20), -- 'checking', 'savings'
-- Datos de crypto wallet
crypto_network VARCHAR(20), -- 'ethereum', 'bitcoin', 'polygon'
crypto_address_last8 VARCHAR(8),
-- Verificacion
verified_at TIMESTAMPTZ,
verification_method VARCHAR(50), -- 'micro_deposits', '3d_secure', 'instant'
-- Billing address (para 3DS y validacion)
billing_address JSONB DEFAULT '{}',
-- Metadata
metadata JSONB DEFAULT '{}',
-- Timestamps
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ, -- Para tarjetas con fecha de expiracion
removed_at TIMESTAMPTZ,
-- Constraints
CONSTRAINT chk_card_info CHECK (
payment_type != 'card' OR (
card_brand IS NOT NULL AND
card_last4 IS NOT NULL AND
card_exp_month IS NOT NULL AND
card_exp_year IS NOT NULL
)
),
CONSTRAINT chk_bank_info CHECK (
payment_type != 'bank_account' OR (
bank_name IS NOT NULL AND
bank_last4 IS NOT NULL
)
),
CONSTRAINT chk_crypto_info CHECK (
payment_type != 'crypto_wallet' OR (
crypto_network IS NOT NULL AND
crypto_address_last8 IS NOT NULL
)
),
CONSTRAINT chk_valid_exp_month CHECK (
card_exp_month IS NULL OR (card_exp_month >= 1 AND card_exp_month <= 12)
),
CONSTRAINT chk_valid_exp_year CHECK (
card_exp_year IS NULL OR card_exp_year >= 2024
)
);
-- Indices
CREATE INDEX idx_payment_methods_user ON financial.payment_methods(user_id);
CREATE INDEX idx_payment_methods_customer ON financial.payment_methods(customer_id);
CREATE INDEX idx_payment_methods_stripe ON financial.payment_methods(stripe_payment_method_id);
CREATE INDEX idx_payment_methods_status ON financial.payment_methods(status);
CREATE INDEX idx_payment_methods_default ON financial.payment_methods(user_id, is_default)
WHERE is_default = TRUE;
CREATE INDEX idx_payment_methods_fingerprint ON financial.payment_methods(stripe_fingerprint)
WHERE stripe_fingerprint IS NOT NULL;
CREATE INDEX idx_payment_methods_active ON financial.payment_methods(user_id, payment_type)
WHERE status = 'active';
-- Comentarios
COMMENT ON TABLE financial.payment_methods IS 'Metodos de pago guardados por usuarios con integracion Stripe';
COMMENT ON COLUMN financial.payment_methods.stripe_payment_method_id IS 'ID del PaymentMethod en Stripe';
COMMENT ON COLUMN financial.payment_methods.stripe_fingerprint IS 'Fingerprint para detectar tarjetas duplicadas';
COMMENT ON COLUMN financial.payment_methods.display_info IS 'Informacion visible del metodo para UI';
COMMENT ON COLUMN financial.payment_methods.is_default IS 'Metodo de pago por defecto del usuario';
COMMENT ON COLUMN financial.payment_methods.billing_address IS 'Direccion de facturacion para 3D Secure';
-- Trigger para asegurar un solo metodo por defecto por usuario
CREATE OR REPLACE FUNCTION financial.ensure_single_default_payment_method()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.is_default = TRUE THEN
UPDATE financial.payment_methods
SET is_default = FALSE, updated_at = NOW()
WHERE user_id = NEW.user_id
AND id != NEW.id
AND is_default = TRUE;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_ensure_single_default_payment_method
BEFORE INSERT OR UPDATE OF is_default ON financial.payment_methods
FOR EACH ROW
WHEN (NEW.is_default = TRUE)
EXECUTE FUNCTION financial.ensure_single_default_payment_method();
-- Funcion para marcar tarjetas expiradas
CREATE OR REPLACE FUNCTION financial.check_expired_cards()
RETURNS INTEGER AS $$
DECLARE
v_count INTEGER;
BEGIN
UPDATE financial.payment_methods
SET status = 'expired', updated_at = NOW()
WHERE payment_type = 'card'
AND status = 'active'
AND (
card_exp_year < EXTRACT(YEAR FROM CURRENT_DATE) OR
(card_exp_year = EXTRACT(YEAR FROM CURRENT_DATE) AND card_exp_month < EXTRACT(MONTH FROM CURRENT_DATE))
);
GET DIAGNOSTICS v_count = ROW_COUNT;
RETURN v_count;
END;
$$ LANGUAGE plpgsql;
COMMENT ON FUNCTION financial.check_expired_cards() IS 'Marca como expiradas las tarjetas vencidas. Ejecutar mensualmente.';