- PagoValidadorService: búsqueda IMAP progresiva (30min → 2h → todo el día) con una sola conexión - SolicitudRecarga::pendiente() amplía ventana de 30min a todo el día (startOfDay) - Auto-reintento silencioso: web via wire:poll cada 20s (9 intentos ≈3min), Telegram via ValidarPagoTelegramJob - Mensaje al usuario al recibir comprobante: 'Estamos validando tu pago...' en lugar de botón inmediato - Aviso en comprobante: indica que debe mostrarse nombre, valor, fecha y hora - Eliminado MercadoPago de todos los flujos de recarga y compra (solo Bre-B) - Flujo 'recargar y comprar': guarda pending_purchase y ejecuta la compra automáticamente tras confirmar recarga - Nuevo job ValidarPagoTelegramJob para reintentos asincrónicos en Telegram Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
149 lines
5.6 KiB
PHP
149 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Models\recarga;
|
|
use App\Models\Saldo;
|
|
use App\Models\SolicitudRecarga;
|
|
use App\Models\User;
|
|
use App\Services\PagoValidadorService;
|
|
use App\Services\TelegramBotService;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class ValidarPagoTelegramJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 1;
|
|
public int $timeout = 60;
|
|
|
|
private const MAX_INTENTOS = 9;
|
|
private const STATE_TTL = 315360000;
|
|
|
|
public function __construct(
|
|
private string $chatId,
|
|
private array $datosPago,
|
|
private int $intento = 1
|
|
) {}
|
|
|
|
public function handle(): void
|
|
{
|
|
$cacheKey = "tgbot_state_{$this->chatId}";
|
|
$state = Cache::get($cacheKey, []);
|
|
$usuarioId = $state['user_id'] ?? null;
|
|
$bot = new TelegramBotService();
|
|
|
|
if (! $usuarioId) {
|
|
return;
|
|
}
|
|
|
|
$solicitud = SolicitudRecarga::pendiente($usuarioId);
|
|
if (! $solicitud) {
|
|
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: solicitud ya no está pendiente, abortando.");
|
|
return;
|
|
}
|
|
|
|
$nombreUsuario = $state['data']['nombre_remitente'] ?? null;
|
|
|
|
$resultado = app(PagoValidadorService::class)->validar(
|
|
$this->datosPago, $usuarioId, $nombreUsuario, 'telegram'
|
|
);
|
|
|
|
if ($resultado['estado'] === 'confirmado') {
|
|
$this->aplicarYNotificar($bot, $state, $cacheKey, $solicitud, $usuarioId);
|
|
return;
|
|
}
|
|
|
|
if ($resultado['estado'] === 'ya_usado') {
|
|
$bot->sendWithKeyboard($this->chatId,
|
|
"⛔ *Comprobante ya utilizado.*\nEste pago ya fue registrado anteriormente.",
|
|
[[['text' => '🔙 Menú principal', 'callback_data' => 'menu']]]
|
|
);
|
|
return;
|
|
}
|
|
|
|
// No encontrado — reintentar o agotar
|
|
if ($this->intento < self::MAX_INTENTOS) {
|
|
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intento {$this->intento}/" . self::MAX_INTENTOS . " — reintentando en 20s");
|
|
self::dispatch($this->chatId, $this->datosPago, $this->intento + 1)
|
|
->delay(now()->addSeconds(20));
|
|
} else {
|
|
Log::info("[ValidarPagoTelegramJob] chatId={$this->chatId}: intentos agotados, mostrando botón manual");
|
|
$bot->sendWithKeyboard(
|
|
$this->chatId,
|
|
"⚠️ No pudimos confirmar tu pago automáticamente.\nSi ya realizaste la transferencia, presiona Reintentar.",
|
|
[
|
|
[['text' => '🔄 Reintentar', 'callback_data' => 'pay_retry']],
|
|
[['text' => '🔙 Menú principal', 'callback_data' => 'menu']],
|
|
]
|
|
);
|
|
}
|
|
}
|
|
|
|
private function aplicarYNotificar(
|
|
TelegramBotService $bot,
|
|
array $state,
|
|
string $cacheKey,
|
|
SolicitudRecarga $solicitud,
|
|
int $usuarioId
|
|
): void {
|
|
$user = User::with('saldo')->find($usuarioId);
|
|
$saldo = $user->saldo ?? Saldo::create(['usuario_id' => $usuarioId, 'valor' => 0]);
|
|
$monto = $solicitud->monto;
|
|
$nuevoSaldo = $saldo->valor + $monto;
|
|
|
|
$saldo->update(['valor' => $nuevoSaldo]);
|
|
|
|
recarga::create([
|
|
'usuario_id' => $usuarioId,
|
|
'saldo_id' => $saldo->id,
|
|
'monto' => $monto,
|
|
'valor_recarga' => $monto,
|
|
'status' => 'Confirmado',
|
|
'reference' => 'breb-bot-' . $solicitud->id,
|
|
]);
|
|
|
|
$solicitud->confirmar();
|
|
|
|
$pp = $state['data']['pending_purchase'] ?? null;
|
|
unset($state['data']['pago_pendiente'], $state['data']['solicitud_recarga_id'], $state['data']['pending_purchase']);
|
|
Cache::put($cacheKey, $state, now()->addSeconds(self::STATE_TTL));
|
|
|
|
$bot->send($this->chatId,
|
|
"✅ *¡Tu recarga de \$" . number_format($monto) . " fue aplicada exitosamente!*\n"
|
|
. "Tu nuevo saldo es *\$" . number_format($nuevoSaldo) . "*"
|
|
);
|
|
|
|
if ($pp && ($pp['type'] ?? '') === 'tarifa') {
|
|
$bot->sendWithKeyboard(
|
|
$this->chatId,
|
|
"🛒 Tienes una compra pendiente: *{$pp['servicio']}* — \$" . number_format($pp['valor']) . "\nPresiona para completarla:",
|
|
[[['text' => "Comprar {$pp['servicio']}", 'callback_data' => 'buy_start|' . $pp['tarifa_id']]]]
|
|
);
|
|
} elseif ($pp && ($pp['type'] ?? '') === 'promo') {
|
|
$bot->sendWithKeyboard(
|
|
$this->chatId,
|
|
"🛒 Tienes una compra pendiente: *{$pp['nombre']}* — \$" . number_format($pp['valor']) . "\nPresiona para completarla:",
|
|
[[['text' => "Comprar {$pp['nombre']}", 'callback_data' => 'promo_saldo|' . $pp['promo_id']]]]
|
|
);
|
|
} else {
|
|
$bot->sendWithKeyboard($this->chatId, "¿En qué más te puedo ayudar?", [
|
|
[
|
|
['text' => '📺 Ver Servicios', 'callback_data' => 'svc_list'],
|
|
['text' => '🎁 Promociones', 'callback_data' => 'promo_list'],
|
|
],
|
|
[
|
|
['text' => '🔑 Mis Credenciales', 'callback_data' => 'creds'],
|
|
['text' => '📋 Historial', 'callback_data' => 'history'],
|
|
],
|
|
]);
|
|
}
|
|
}
|
|
}
|