From 64c1260a9fba5c88cb86ffdfa3d991be69cad92b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Faleiros?= Date: Tue, 29 Sep 2026 14:35:47 -0300 Subject: [PATCH] feat: report Mercado Pago's notifications in the account check Without access to the Mercado Pago panel, "Verificar conta" now also says how many payment notifications arrived and passed the signature in the last 24 hours, how many were refused by it, and the last one received: the PIX payments created in test mode notify the webhook, so this shows whether Mercado Pago reaches the server. Co-Authored-By: Claude Opus 5.5 --- app/api/operator.py | 13 ++++++++++++- web/kanban.js | 5 ++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/app/api/operator.py b/app/api/operator.py index 6492c85..7908785 100644 --- a/app/api/operator.py +++ b/app/api/operator.py @@ -310,7 +310,18 @@ def mercadopago_check(user=Depends(operator)): raise HTTPException(409, 'Mercado Pago não está configurado') from ..mercadopago_probe import check audit('mercadopago_check', operator=user) - return check(payment.access_token) + result = check(payment.access_token) + # Whether Mercado Pago's notifications reach this server and pass the + # signature: every accepted one is recorded, a refused one is audited. + with db.connect() as c: + received = c.execute('''SELECT count(*) AS n, max(received_at) AS last FROM dtf_local.payment_events + WHERE provider='mercadopago' AND received_at > now()-interval '24 hours' ''').fetchone() + last = c.execute('''SELECT received_at,status,outcome FROM dtf_local.payment_events + WHERE provider='mercadopago' ORDER BY received_at DESC LIMIT 1''').fetchone() + refused = c.execute('''SELECT count(*) AS n FROM dtf_local.security_events + WHERE event='payment_webhook_rejected' AND created_at > now()-interval '24 hours' ''').fetchone() + result['webhooks'] = {'accepted_24h': received['n'], 'refused_24h': refused['n'], 'last': last} + return result @router.get('/api/operator/tiny/callback') diff --git a/web/kanban.js b/web/kanban.js index 3517abe..5e8a330 100644 --- a/web/kanban.js +++ b/web/kanban.js @@ -679,7 +679,10 @@ function renderIntegrations(){ const conta=r.account.error?'conta: '+r.account.error:'conta '+r.account.nickname+' ('+r.account.id+') · '+ (r.account.test_user?'usuário de teste':'não é usuário de teste'); const bin=Array.isArray(r.bin)?(r.bin.length?r.bin.map(o=>o.method+' '+o.type+' · '+o.issuer+' · até '+Math.max(...o.installments)+'x').join('; '):'cartão de teste não reconhecido'):'cartão: '+r.bin.error; - say('Mercado Pago: credencial '+(r.token==='test'?'de teste':'de produção')+' · '+conta+' · '+bin,false,60000); + const w=r.webhooks||{}; + const avisos='avisos nas últimas 24 h: '+w.accepted_24h+' aceitos, '+w.refused_24h+' recusados pela assinatura'+ + (w.last?' · último em '+new Date(w.last.received_at).toLocaleString('pt-BR')+' ('+w.last.status+')':' · nenhum recebido ainda'); + say('Mercado Pago: credencial '+(r.token==='test'?'de teste':'de produção')+' · '+conta+' · '+bin+' · '+avisos,false,60000); },'btn ghost')]:[]; cards.push(integration('Mercado Pago',mp==='mercadopago'?'Ativo':'Não configurado',mp==='mercadopago'?'ok':'off', mp==='mercadopago'?'PIX e cartão ativos.':'Aguardando credenciais.',mpActions));