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>
53 lines
2.6 KiB
PHP
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";
|