Procedimiento de activacion

This commit is contained in:
2026-03-06 12:11:17 +01:00
parent 37e41a0130
commit 221abe0d33
16 changed files with 375 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
CREATE EXTENSION pgcrypto; -- para los random bytes
-- 1. Función de generacion de uuidv7 copiada porque no esta en postgre 19
-- 1. Función de generacion de uuidv7 copiada de github porque no está en postgre 16
CREATE OR REPLACE FUNCTION
uuid_generate_v7()
RETURNS
@@ -44,6 +44,8 @@ CREATE TABLE activation_codes (
card_id UUID REFERENCES payment_cards(card_id), -- Una tarjeta, maximo un un código activo borrar o solo con expires_at?
code_hash TEXT NOT NULL, -- Guardar el código hasheado, el original se imprime y se manda
is_used BOOLEAN DEFAULT FALSE,
is_blocked BOOLEAN DEFAULT FALSE,
failed_attempts INT DEFAULT 0,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
@@ -59,3 +61,27 @@ CREATE TABLE activation_logs (
geo_location VARCHAR(100), -- Opcional: Ciudad/País derivado de IP
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE OR REPLACE FUNCTION log_activation_attempt()
RETURNS TRIGGER AS $$
BEGIN
-- Si el intento falló (esto lo controlas desde tu lógica de UPDATE)
-- Hay que barajar si es viable hacer los updates de failed_attempts desde aquí
-- y nó desde código.
IF NEW.failed_attempts > OLD.failed_attempts THEN
INSERT INTO activation_logs (card_id, action_type, created_at)
VALUES (NEW.card_id, 'FAILED_ATTEMPT', NOW());
END IF;
-- Bloqueo automático si llega al límite
IF NEW.failed_attempts >= 3 THEN
NEW.is_blocked := TRUE;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_check_attempts
BEFORE UPDATE ON activation_codes
FOR EACH ROW EXECUTE FUNCTION log_activation_attempt();

View File

@@ -0,0 +1,95 @@
-- He separado el procedimiento de activacion en otro archivo por comodidad de desarrollo
-- creo que es mas seguro meter la lógica de activaciones directamente en la bdd que en
-- un server node y asi evitamos problemas de consistencia entre versiones.
CREATE OR REPLACE FUNCTION activate_payment_card(
p_card_id UUID, -- uuidv7 de la tarjeta
p_input_code TEXT, -- El código que mete el usuario, puede ser incorrecto en este punto
p_ip_address INET, -- Datos de origen de la activación
p_device_info JSONB -- Datos del dispositivo de origen
)
RETURNS TABLE (
success BOOLEAN,
message TEXT
) AS $$
DECLARE
v_code_hash TEXT;
v_is_used BOOLEAN;
v_is_blocked BOOLEAN;
v_expires_at TIMESTAMPTZ;
v_failed_attempts INT;
v_max_attempts CONSTANT INT := 3;
BEGIN
-- 1. Obtener datos del código y bloquear la fila para actualización (FOR UPDATE)
-- sería raro que 2 clientes intentasen modificar la tarjeta a la vez.
SELECT code_hash, is_used, is_blocked, expires_at, failed_attempts
INTO v_code_hash, v_is_used, v_is_blocked, v_expires_at, v_failed_attempts
FROM activation_codes
WHERE card_id = p_card_id
FOR UPDATE;
-- 2. Validaciones:
-- 2.1 Si no existe ninguna tarjeta con ese card_id
IF NOT FOUND THEN
RETURN QUERY SELECT FALSE, 'CARD_NOT_FOUND';
RETURN;
END IF;
-- 2.2 Si el código introducido ya ha sido usado
IF v_is_used THEN
RETURN QUERY SELECT FALSE, 'ALREADY_ACTIVATED';
RETURN;
END IF;
-- 2.3 Si el código ya ha sido bloqueado o se ha intentado demasiadas veces
IF v_is_blocked OR v_failed_attempts >= v_max_attempts THEN
RETURN QUERY SELECT FALSE, 'CODE_BLOCKED';
RETURN;
END IF;
IF v_expires_at < NOW() THEN
RETURN QUERY SELECT FALSE, 'CODE_EXPIRED';
RETURN;
END IF;
-- 3. Verificación del Hash del codigo que ha introducido el usuario
-- En la bdd guardo el hash, al procedimiento se introduce el codigo per-se
-- Si el código NO coincide:
IF v_code_hash != crypt(p_input_code, v_code_hash) THEN
UPDATE activation_codes
-- 3.1 Control de intentos
SET failed_attempts = failed_attempts + 1,
is_blocked = (failed_attempts + 1 >= v_max_attempts)
WHERE card_id = p_card_id;
-- 3.2 Se loguea el fallo
INSERT INTO activation_logs (card_id, action_type, ip_address, device_info)
VALUES (p_card_id, 'FAILED_ATTEMPT', p_ip_address, p_device_info);
RETURN QUERY SELECT FALSE, 'INVALID_CODE';
RETURN;
END IF;
-- 4. Si el código ES correcto:
-- Marcar código como usado
UPDATE activation_codes
SET is_used = TRUE
WHERE card_id = p_card_id;
-- Activar la tarjeta
UPDATE payment_cards
SET status = 'ACTIVE',
activated_at = NOW()
WHERE card_id = p_card_id;
-- Registrar éxito en auditoría
INSERT INTO activation_logs (card_id, action_type, ip_address, device_info)
VALUES (p_card_id, 'ACTIVATION_SUCCESS', p_ip_address, p_device_info);
RETURN QUERY SELECT TRUE, 'SUCCESS';
EXCEPTION WHEN OTHERS THEN
-- En caso de error inesperado, Postgres hace rollback automático
RETURN QUERY SELECT FALSE, 'INTERNAL_ERROR: ' || SQLERRM;
END;
$$ LANGUAGE plpgsql;