feat(logging): implement dedicated logging for Telepagos events and update logging configuration
This commit is contained in:
@@ -31,10 +31,13 @@ AUTH_LOGIN_LOCK_MINUTES=15
|
||||
AUTH_LOGIN_RATE_LIMIT_PER_MINUTE=10
|
||||
AUTH_LOGIN_IP_RATE_LIMIT_PER_MINUTE=30
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_CHANNEL=daily
|
||||
LOG_STACK=single
|
||||
LOG_DEPRECATIONS_CHANNEL=null
|
||||
LOG_LEVEL=debug
|
||||
LOG_DAILY_DAYS=14
|
||||
TELEPAGOS_LOG_LEVEL=info
|
||||
TELEPAGOS_LOG_DAYS=30
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
|
||||
@@ -81,9 +81,7 @@ class TelepagosIntegrationService extends BaseIntegrationService
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$data = $this->handleResponse($response, 'authentication', [
|
||||
'username' => $username,
|
||||
]);
|
||||
$data = $this->handleResponse($response, 'authentication');
|
||||
|
||||
$token = $data['token'] ?? null;
|
||||
$expiresAtStr = $data['expires_at'] ?? null;
|
||||
@@ -110,7 +108,12 @@ class TelepagosIntegrationService extends BaseIntegrationService
|
||||
$response = $this->client()->$method($endpoint, $data);
|
||||
|
||||
if ($response->status() === 401) {
|
||||
Log::info('Telepagos 401 Unauthorized. Refreshing token and retrying...');
|
||||
Log::channel('telepagos')->info('Telepagos request returned 401; refreshing token and retrying.', [
|
||||
'method' => strtoupper($method),
|
||||
'endpoint' => $endpoint,
|
||||
'client_id' => $this->clientContext?->id,
|
||||
'integration_code' => $this->integrationCode,
|
||||
]);
|
||||
|
||||
$this->clearToken();
|
||||
|
||||
@@ -175,9 +178,11 @@ class TelepagosIntegrationService extends BaseIntegrationService
|
||||
{
|
||||
if ($response->failed() || $response->json('status') !== 'ok') {
|
||||
$errorMessage = $response->json('message') ?? $response->body();
|
||||
Log::error("Telepagos {$actionDescription} failed: {$errorMessage}", array_merge([
|
||||
Log::channel('telepagos')->error("Telepagos {$actionDescription} failed: {$errorMessage}", array_merge([
|
||||
'response_status' => $response->status(),
|
||||
'response_body' => $response->json() ?? $response->body(),
|
||||
'response_body' => $this->sanitizeForLog($response->json() ?? $response->body()),
|
||||
'client_id' => $this->clientContext?->id,
|
||||
'integration_code' => $this->integrationCode,
|
||||
], $context));
|
||||
|
||||
throw new Exception("Telepagos {$actionDescription} failed: {$errorMessage}");
|
||||
@@ -186,6 +191,30 @@ class TelepagosIntegrationService extends BaseIntegrationService
|
||||
return $response->json() ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove credentials and tokens before serializing provider responses.
|
||||
*/
|
||||
protected function sanitizeForLog(mixed $value): mixed
|
||||
{
|
||||
if (! is_array($value)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$sensitiveKeys = ['authorization', 'password', 'token', 'access_token', 'refresh_token'];
|
||||
|
||||
foreach ($value as $key => $item) {
|
||||
if (in_array(strtolower((string) $key), $sensitiveKeys, true)) {
|
||||
$value[$key] = '[REDACTED]';
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$value[$key] = $this->sanitizeForLog($item);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cached token.
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,11 @@ class TelepagosWebhookService
|
||||
*/
|
||||
public function handleWebhook(Client $client, string $cashinId): void
|
||||
{
|
||||
Log::channel('telepagos')->info('Telepagos webhook received.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
]);
|
||||
|
||||
$telepagosService = new TelepagosIntegrationService;
|
||||
$telepagosService->forClient($client);
|
||||
|
||||
@@ -43,7 +48,10 @@ class TelepagosWebhookService
|
||||
$cuit = $details['data']['buyer']['cuit'] ?? $details['buyer']['cuit'] ?? null;
|
||||
|
||||
if (! $cuit) {
|
||||
Log::warning("Telepagos webhook: CUIT not found for Transferencia cashin {$cashinId}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: CUIT not found for transfer.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -66,13 +74,21 @@ class TelepagosWebhookService
|
||||
$compra = $purchases->count() === 1 ? $purchases->first() : null;
|
||||
|
||||
if (! $compra) {
|
||||
Log::warning("Telepagos webhook: Expected one matching purchase for client {$client->code}, DNI {$dni}, amount {$amount} and cashin {$cashinId}; found {$purchases->count()}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Expected exactly one matching purchase.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'amount' => $amount,
|
||||
'matches' => $purchases->count(),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
} elseif (in_array((int) $operationId, $qrOperationIds, true)) {
|
||||
if (! $qrOrderId) {
|
||||
Log::warning("Telepagos webhook: qr_order_id not found for QR cashin {$cashinId}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: qr_order_id not present in provider response.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -80,7 +96,11 @@ class TelepagosWebhookService
|
||||
$telepagosQr = TelepagosQr::where('qr_order_id', $qrOrderId)->first();
|
||||
|
||||
if (! $telepagosQr) {
|
||||
Log::warning("Telepagos webhook: QR {$qrOrderId} not found in database for cashin {$cashinId}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: QR not found in database.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'qr_order_id' => $qrOrderId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -88,13 +108,21 @@ class TelepagosWebhookService
|
||||
$compra = $telepagosQr->compra;
|
||||
|
||||
if (! $compra) {
|
||||
Log::warning("Telepagos webhook: Purchase not found for QR {$qrOrderId}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Purchase not found for QR.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'qr_order_id' => $qrOrderId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $client->tenants()->where('codigo', $compra->tenant_codigo)->exists()) {
|
||||
Log::warning("Telepagos webhook: Purchase {$compra->id} does not belong to client {$client->code}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Purchase does not belong to client.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'purchase_id' => $compra->id,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -102,7 +130,12 @@ class TelepagosWebhookService
|
||||
if (! in_array($compra->status, [
|
||||
Purchase::STATUS_PENDING_PAYMENT,
|
||||
], true)) {
|
||||
Log::warning("Telepagos webhook: Purchase {$compra->id} is not awaiting payment confirmation");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Purchase is not awaiting payment confirmation.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'purchase_id' => $compra->id,
|
||||
'purchase_status' => $compra->status,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -110,12 +143,22 @@ class TelepagosWebhookService
|
||||
$totalAmount = $this->normalizeAmount($compra->getTotalAmount());
|
||||
|
||||
if ($amount !== $totalAmount) {
|
||||
Log::warning("Telepagos webhook: Amount mismatch. Cashin amount: {$amount}, Purchase amount: {$totalAmount}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Amount mismatch.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'purchase_id' => $compra->id,
|
||||
'cashin_amount' => $amount,
|
||||
'purchase_amount' => $totalAmount,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
Log::warning("Telepagos webhook: Unknown operation_id {$operationId} for cashin {$cashinId}");
|
||||
Log::channel('telepagos')->warning('Telepagos webhook: Unknown operation_id.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'operation_id' => $operationId,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -138,9 +181,18 @@ class TelepagosWebhookService
|
||||
$this->checkoutService->confirmPaidPurchase($compra);
|
||||
});
|
||||
|
||||
Log::info("Telepagos webhook: Successfully processed cashin {$cashinId} for purchase {$compra->id}");
|
||||
Log::channel('telepagos')->info('Telepagos webhook processed successfully.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'purchase_id' => $compra->id,
|
||||
'transaction_id' => $paymentData['transaction_id'],
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
Log::error('Telepagos webhook error: '.$e->getMessage());
|
||||
Log::channel('telepagos')->error('Telepagos webhook processing failed.', [
|
||||
'client_code' => $client->code,
|
||||
'cashin_id' => $cashinId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ Gestiona integraciones externas disponibles y su configuración por cliente. Un
|
||||
- Consulta y configuración por cliente bajo `/clients/{client}/integrations`.
|
||||
- `POST /webhooks/telepagos/{client}` para notificaciones del proveedor.
|
||||
|
||||
## Logging de Telepagos
|
||||
|
||||
Los eventos de autenticación, QR, consultas de cuenta y procesamiento de webhooks se escriben en el canal diario `telepagos`, separado del log general. Los archivos se generan en `storage/logs/telepagos-YYYY-MM-DD.log`; el nivel y la retención se configuran con `TELEPAGOS_LOG_LEVEL` y `TELEPAGOS_LOG_DAYS`. Tokens y credenciales se eliminan del contexto antes de registrar respuestas del proveedor.
|
||||
|
||||
## Dependencias y reglas
|
||||
|
||||
Se integra con `Client`, `Tenant` y con el checkout de `Purchase`. `Notification` utiliza `MailService`. El tenant conserva el contexto operativo y de branding, pero nunca es dueño de credenciales. Las credenciales no se exponen en respuestas ni logs; los webhooks deben validar su contrato antes de alterar una compra.
|
||||
|
||||
@@ -163,6 +163,11 @@ class PurchaseController extends Controller
|
||||
try {
|
||||
$accountInfo = $telepagosService->getAccountInfo();
|
||||
|
||||
Log::channel('telepagos')->info('Telepagos account information retrieved.', [
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'purchase_id' => $compra->id,
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'payment_method' => 'transfer',
|
||||
'transfer_data' => [
|
||||
@@ -174,9 +179,10 @@ class PurchaseController extends Controller
|
||||
],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Unable to retrieve TelePagos account information.', [
|
||||
Log::channel('telepagos')->error('Unable to retrieve TelePagos account information.', [
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'purchase_id' => $compra->id,
|
||||
'exception' => $e,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
@@ -191,7 +197,11 @@ class PurchaseController extends Controller
|
||||
$telepagosService->forTenant($tenant->codigo);
|
||||
|
||||
try {
|
||||
Log::info("Generating QR for purchase ID: {$compra->id}, amount: {$totalAmount}");
|
||||
Log::channel('telepagos')->info('Generating Telepagos QR.', [
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'purchase_id' => $compra->id,
|
||||
'amount' => $totalAmount,
|
||||
]);
|
||||
$qrResponse = $telepagosService->generateQr(
|
||||
$totalAmount,
|
||||
'Compra',
|
||||
@@ -202,10 +212,17 @@ class PurchaseController extends Controller
|
||||
'qr_order_id' => (string) ($qrResponse['qr_order_id'] ?? ''),
|
||||
'qr_code' => $qrResponse['qr_code'] ?? '',
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Unable to generate TelePagos QR code.', [
|
||||
|
||||
Log::channel('telepagos')->info('Telepagos QR generated successfully.', [
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'purchase_id' => $compra->id,
|
||||
'exception' => $e,
|
||||
'qr_order_id' => $telepagosQr->qr_order_id,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('telepagos')->error('Unable to generate TelePagos QR code.', [
|
||||
'tenant_code' => $tenant->codigo,
|
||||
'purchase_id' => $compra->id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
|
||||
@@ -18,7 +18,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('LOG_CHANNEL', 'stack'),
|
||||
'default' => env('LOG_CHANNEL', 'daily'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -73,6 +73,14 @@ return [
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'telepagos' => [
|
||||
'driver' => 'daily',
|
||||
'path' => storage_path('logs/telepagos.log'),
|
||||
'level' => env('TELEPAGOS_LOG_LEVEL', 'info'),
|
||||
'days' => env('TELEPAGOS_LOG_DAYS', 30),
|
||||
'replace_placeholders' => true,
|
||||
],
|
||||
|
||||
'slack' => [
|
||||
'driver' => 'slack',
|
||||
'url' => env('LOG_SLACK_WEBHOOK_URL'),
|
||||
|
||||
@@ -285,15 +285,21 @@ class IntegrationServiceTest extends TestCase
|
||||
'https://api.telepagos.com.ar/v2/auth/token' => Http::response([
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid credentials',
|
||||
'token' => 'must-not-be-logged',
|
||||
], 401),
|
||||
]);
|
||||
|
||||
Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos authentication failed: Invalid credentials', \Mockery::on(function ($context) {
|
||||
return $context['username'] === 'tele_user'
|
||||
return ! array_key_exists('username', $context)
|
||||
&& $context['response_status'] === 401
|
||||
&& $context['response_body'] === ['status' => 'error', 'message' => 'Invalid credentials'];
|
||||
&& $context['response_body'] === [
|
||||
'status' => 'error',
|
||||
'message' => 'Invalid credentials',
|
||||
'token' => '[REDACTED]',
|
||||
];
|
||||
}));
|
||||
|
||||
$service = new TelepagosIntegrationService('telepagos');
|
||||
@@ -442,6 +448,7 @@ class IntegrationServiceTest extends TestCase
|
||||
], 422),
|
||||
]);
|
||||
|
||||
Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos QR generation failed: Importe inválido', \Mockery::on(function ($context) {
|
||||
@@ -555,6 +562,7 @@ class IntegrationServiceTest extends TestCase
|
||||
], 404),
|
||||
]);
|
||||
|
||||
Log::shouldReceive('channel')->once()->with('telepagos')->andReturnSelf();
|
||||
Log::shouldReceive('error')
|
||||
->once()
|
||||
->with('Telepagos get cash-in details failed: Cashin no encontrado', \Mockery::on(function ($context) {
|
||||
|
||||
Reference in New Issue
Block a user