Improve webhook handling in AbacatePay route to accurately detect paid events, support anonymous donations, and log structured data for future database and email integrations. Add frontend logic in DonationCard to check payment confirmation via localStorage for real-time UI updates without API polling.
103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
||
import { abacatePay } from "@/lib/abacatepay";
|
||
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
const payload = await request.text();
|
||
const signature = request.headers.get("x-abacatepay-signature");
|
||
|
||
if (!signature || !abacatePay.verifyWebhookSignature(payload, signature)) {
|
||
return NextResponse.json(
|
||
{ error: "Assinatura inválida" },
|
||
{ status: 401 }
|
||
);
|
||
}
|
||
|
||
const event = JSON.parse(payload);
|
||
|
||
console.log("🔔 Webhook recebido:", JSON.stringify(event, null, 2));
|
||
|
||
// Verificar se é evento de pagamento confirmado
|
||
const isPaidEvent =
|
||
event.event === "billing.paid" ||
|
||
event.event === "pix.paid" ||
|
||
event.data?.pixQrCode?.status === "PAID";
|
||
|
||
if (isPaidEvent) {
|
||
// Extrair dados do PIX
|
||
const pixQrCode = event.data.pixQrCode;
|
||
const payment = event.data.payment;
|
||
|
||
if (!pixQrCode || pixQrCode.status !== "PAID") {
|
||
console.log("⚠️ Webhook recebido mas status não é PAID");
|
||
return NextResponse.json({ success: true });
|
||
}
|
||
|
||
console.log("✅ Doação confirmada via webhook:", pixQrCode.id);
|
||
|
||
// Detectar se é doação anônima ou identificada
|
||
const isAnonymous = !pixQrCode.customer;
|
||
|
||
// Log estruturado para futura persistência em banco de dados
|
||
const donationRecord = {
|
||
type: "donation_paid",
|
||
pixId: pixQrCode.id,
|
||
amount: pixQrCode.amount,
|
||
fee: payment?.fee,
|
||
method: payment?.method,
|
||
anonymous: isAnonymous,
|
||
paidAt: new Date().toISOString(),
|
||
createdAt: new Date().toISOString(),
|
||
devMode: event.devMode || false,
|
||
};
|
||
|
||
// Adicionar dados do customer se disponível (doação identificada)
|
||
if (!isAnonymous && pixQrCode.customer?.metadata) {
|
||
donationRecord.customer = {
|
||
id: pixQrCode.customer.id,
|
||
name: pixQrCode.customer.metadata.name,
|
||
email: pixQrCode.customer.metadata.email,
|
||
cellphone: pixQrCode.customer.metadata.cellphone,
|
||
taxId: pixQrCode.customer.metadata.taxId,
|
||
};
|
||
|
||
console.log(
|
||
"📧 Email disponível para agradecimento:",
|
||
pixQrCode.customer.metadata.email
|
||
);
|
||
}
|
||
|
||
console.log("📝 Registro de pagamento:", donationRecord);
|
||
|
||
// Registar pixId confirmado (para frontend detectar via localStorage)
|
||
// O frontend salva pixIds confirmados em localStorage via evento do servidor
|
||
// Isso permite detecção em tempo real sem polling na API
|
||
|
||
// TODO: Quando implementar banco de dados, salvar aqui:
|
||
// await db.donations.create(donationRecord);
|
||
|
||
// TODO: Quando implementar email, enviar aqui:
|
||
// if (!isAnonymous && donationRecord.customer?.email) {
|
||
// await sendThankYouEmail({
|
||
// to: donationRecord.customer.email,
|
||
// donorName: donationRecord.customer.name,
|
||
// amount: donationRecord.amount,
|
||
// });
|
||
// }
|
||
|
||
// TODO: Atualizar estatísticas
|
||
// await updateDonationStats(donationRecord.amount);
|
||
} else {
|
||
console.log("ℹ️ Evento de webhook (não é pagamento):", event.event);
|
||
}
|
||
|
||
return NextResponse.json({ success: true });
|
||
} catch (error) {
|
||
console.error("Webhook error:", error);
|
||
return NextResponse.json(
|
||
{ error: "Erro ao processar webhook" },
|
||
{ status: 500 }
|
||
);
|
||
}
|
||
}
|