Files
sirpremiumv2/tests/whatsapp_signature_check.php
T
Lizandro GuarnizoandClaude Opus 5 5bc1ec14ca feat(whatsapp): mismo motor, menú y configuración que Telegram y el chat web
WhatsApp dejaba de usar su bot propio (árbol de menús sin login ni compras) y
pasa al motor compartido, así los tres canales quedan iguales.

- TelegramBotService recibe el canal por constructor; flujos, menús, Gemini,
  OCR, validación de pagos y datos bancarios salen de ChatConfig sin duplicar
- WhatsApp no tiene teclados inline: las opciones se envían numeradas y la
  respuesta numérica se traduce al callback equivalente
- estado en caché con prefijo por canal (Telegram conserva el suyo para no
  cerrar las sesiones abiertas)
- descarga de comprobantes y audios por la Graph API de Meta
- respuesta manual del asesor y aviso de vencimiento salen también por WhatsApp
- la pantalla de WhatsApp queda sólo con credenciales; el resto se administra
  en Chat / Bot

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 23:24:21 -05:00

53 lines
2.6 KiB
PHP

<?php
// ponytail: check suelto, corre con `php tests/whatsapp_signature_check.php` sin phpunit ni vendor.
// Verifica el contrato de firma que aplica WhatsappWebhookController::signatureValid().
$secret = 'secreto-de-prueba';
$payload = '{"entry":[{"changes":[{"field":"messages"}]}]}';
$firmar = fn(string $body, string $sec) => 'sha256=' . hash_hmac('sha256', $body, $sec);
$valida = function (string $header, string $body, string $sec): bool {
if ($sec === '' || ! str_starts_with($header, 'sha256=')) return false;
return hash_equals(hash_hmac('sha256', $body, $sec), substr($header, 7));
};
assert($valida($firmar($payload, $secret), $payload, $secret) === true, 'firma correcta debe pasar');
assert($valida($firmar($payload, $secret), $payload . ' ', $secret) === false, 'cuerpo alterado debe fallar');
assert($valida($firmar($payload, 'otro'), $payload, $secret) === false, 'secreto distinto debe fallar');
assert($valida($firmar($payload, $secret), $payload, '') === false, 'sin secreto debe fallar cerrado');
assert($valida('', $payload, $secret) === false, 'sin cabecera debe fallar');
assert($valida(hash_hmac('sha256', $payload, $secret), $payload, $secret) === false, 'sin prefijo sha256= debe fallar');
// ── Teclado numerado: mismo mapeo que TelegramBotService::numerarTeclado() ──
$numerar = function (array $teclado): array {
$mapa = []; $lineas = []; $n = 1;
foreach ($teclado as $fila) {
foreach ($fila as $boton) {
if (! isset($boton['callback_data'])) continue;
$mapa[(string) $n] = $boton['callback_data'];
$lineas[] = "{$n}. {$boton['text']}";
$n++;
}
}
return [$mapa, $lineas];
};
[$mapa, $lineas] = $numerar([
[['text' => 'Ver Servicios', 'callback_data' => 'svc_list'],
['text' => 'Promociones', 'callback_data' => 'promo_list']],
[['text' => 'Recargar', 'callback_data' => 'rec_init']],
]);
assert($lineas === ['1. Ver Servicios', '2. Promociones', '3. Recargar'], 'numeración corrida por filas');
assert($mapa['2'] === 'promo_list', 'el número devuelve el callback correcto');
assert($mapa['3'] === 'rec_init', 'la segunda fila sigue la numeración');
assert(count($mapa) === 3, 'un número por botón');
assert(! isset($mapa['4']), 'no inventa opciones');
[$vacio, $sinLineas] = $numerar([[['text' => 'sin accion']]]);
assert($vacio === [] && $sinLineas === [], 'botones sin callback_data se ignoran');
echo "OK: 6 casos de firma + 6 de teclado numerado\n";