DDL schemas for Trading Platform: - User management - Authentication - Payments - Education - ML predictions - Trading data Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
181 lines
6.7 KiB
PL/PgSQL
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.';
|