import { createHash } from "node:crypto"; // ePayco (100% de Davivienda desde 2024, ver ADR 0009 -- tercer proveedor // evaluado tras PayU LatAm/Rapyd, elegido por comision PSE mas baja para // bajo volumen y tarifa preferencial con cuenta Davivienda, que el // usuario ya tiene). A diferencia de PayU/Rapyd, no llamamos la API REST // cruda nosotros: usamos el SDK oficial `epayco-sdk-node` (mantenido por // github.com/epayco, ultimo publish reciente) porque su protocolo real // -- leido del codigo fuente, no de la doc -- es bastante mas raro de lo // que parece en los ejemplos: cada campo del payload se cifra con // AES-CBC usando la private key como llave (IV fijo en cero) ANTES de // mandarse a secure.payco.co, ademas de un login previo a // api.secure.payco.co/v1/auth/login que da un bearer token que tambien // va en la request. Reimplementar esto a mano tiene mucho mas riesgo de // error que delegarlo al SDK del propio proveedor. export type EpaycoEnv = "sandbox" | "production"; export interface EpaycoCredentials { publicKey: string; privateKey: string; pCustIdCliente: string; pKey: string; env: EpaycoEnv; } // Sin tipos de TypeScript en el paquete (confirmado: package.json no // declara "types") -- se tipa manualmente el subset que usamos. // eslint-disable-next-line @typescript-eslint/no-var-requires const EpaycoSdk = require("epayco-sdk-node"); export interface EpaycoClient { bank: { create(options: Record): Promise>; get(transactionId: string): Promise>; getBanks(): Promise>; }; } export function createEpaycoClient(credentials: EpaycoCredentials): EpaycoClient { return EpaycoSdk({ apiKey: credentials.publicKey, privateKey: credentials.privateKey, lang: "ES", test: credentials.env === "sandbox", }); } // Firma del webhook de confirmacion (url_confirmation) -- formula // verificada contra la documentacion real de ePayco: // sha256(p_cust_id_cliente^p_key^x_ref_payco^x_transaction_id^x_amount^x_currency_code). // p_cust_id_cliente/p_key son valores propios del dashboard de ePayco // (Configuracion > Llaves secretas), distintos de publicKey/privateKey // -- confirmar contra el dashboard real antes de la primera prueba. // Falla cerrado: cualquier mismatch devuelve false, nunca se traga la // excepcion. export function verifyWebhookSignature(params: { pCustIdCliente: string; pKey: string; refPayco: string; transactionId: string; amount: string; currencyCode: string; signature: string; }): boolean { const raw = `${params.pCustIdCliente}^${params.pKey}^${params.refPayco}^${params.transactionId}^${params.amount}^${params.currencyCode}`; const expected = createHash("sha256").update(raw).digest("hex"); return expected.toLowerCase() === params.signature.toLowerCase(); }