diff --git a/app/api/webhooks/abacatepay/route.ts b/app/api/webhooks/abacatepay/route.ts index eea8e0b..4fedf0d 100644 --- a/app/api/webhooks/abacatepay/route.ts +++ b/app/api/webhooks/abacatepay/route.ts @@ -17,45 +17,78 @@ export async function POST(request: NextRequest) { console.log("🔔 Webhook recebido:", JSON.stringify(event, null, 2)); - // Capturar todos os tipos de eventos de pagamento + // Verificar se é evento de pagamento confirmado const isPaidEvent = - event.type === "pix.paid" || - event.type === "billing.paid" || - event.type === "payment.paid" || - event.data?.status === "PAID"; + event.event === "billing.paid" || + event.event === "pix.paid" || + event.data?.pixQrCode?.status === "PAID"; if (isPaidEvent) { - console.log("✅ Doação confirmada via webhook:", event.data.id); + // 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: event.data.id, - amount: event.data.amount, - customer: event.data.customer, - paidAt: event.data.paidAt || new Date().toISOString(), + pixId: pixQrCode.id, + amount: pixQrCode.amount, + fee: payment?.fee, + method: payment?.method, + anonymous: isAnonymous, + paidAt: new Date().toISOString(), createdAt: new Date().toISOString(), - metadata: event.data.metadata, + 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 (event.data.customer?.email) { + // if (!isAnonymous && donationRecord.customer?.email) { // await sendThankYouEmail({ - // to: event.data.customer.email, - // donorName: event.data.customer.name, - // amount: event.data.amount, + // to: donationRecord.customer.email, + // donorName: donationRecord.customer.name, + // amount: donationRecord.amount, // }); // } // TODO: Atualizar estatísticas - // await updateDonationStats(event.data.amount); + // await updateDonationStats(donationRecord.amount); } else { - console.log("ℹ️ Evento de webhook (não é pagamento):", event.type); + console.log("ℹ️ Evento de webhook (não é pagamento):", event.event); } return NextResponse.json({ success: true }); diff --git a/components/donation-card.tsx b/components/donation-card.tsx index 8497d24..348c474 100644 --- a/components/donation-card.tsx +++ b/components/donation-card.tsx @@ -15,6 +15,7 @@ import { Lock, User, CheckCircle, + CheckCircle2, } from "lucide-react"; import { Dialog, @@ -56,6 +57,8 @@ export function DonationCard({ const [copied, setCopied] = useState(false); const [error, setError] = useState(null); const [timeRemaining, setTimeRemaining] = useState(null); + const [paymentConfirmed, setPaymentConfirmed] = useState(false); + const [showThankYouModal, setShowThankYouModal] = useState(false); const icons = { coffee: Coffee, @@ -92,11 +95,39 @@ export function DonationCard({ } }; + // Verificar pagamento via localStorage + const checkPaymentConfirmation = () => { + const savedPixId = localStorage.getItem("currentPixId"); + const confirmedPixIds = localStorage.getItem("confirmedPixIds"); + + if (savedPixId && confirmedPixIds) { + try { + const confirmed = JSON.parse(confirmedPixIds); + if (confirmed.includes(savedPixId)) { + console.log("✅ Pagamento confirmado!"); + setPaymentConfirmed(true); + setTimeout(() => { + setShowPixModal(false); + setShowThankYouModal(true); + // Limpar + localStorage.removeItem("currentPixId"); + }, 1000); + clearInterval(timerInterval); + } + } catch (e) { + console.error("Erro ao verificar pagamento:", e); + } + } + }; + // Atualizar timer imediatamente updateTimer(); - // Atualizar timer a cada segundo - const timerInterval = setInterval(updateTimer, 1000); + // Atualizar timer e verificar pagamento a cada 2 segundos + const timerInterval = setInterval(() => { + updateTimer(); + checkPaymentConfirmation(); + }, 2000); return () => clearInterval(timerInterval); }, [pixData?.expiresAt, pixData?.pixId]); @@ -165,6 +196,8 @@ export function DonationCard({ brCodeBase64: data.brCodeBase64, expiresAt: data.expiresAt, }); + // Salvar pixId no localStorage para verificação + localStorage.setItem("currentPixId", data.pixId); setShowPixModal(true); } else { setError(data.error || "Erro ao processar doação"); @@ -239,6 +272,8 @@ export function DonationCard({ brCodeBase64: data.brCodeBase64, expiresAt: data.expiresAt, }); + // Salvar pixId no localStorage + localStorage.setItem("currentPixId", data.pixId); setShowDonorForm(false); setShowPixModal(true); } else { @@ -588,6 +623,67 @@ export function DonationCard({ )} + + {/* Modal de Agradecimento */} + + +
+ {/* Ícone de sucesso animado */} +
+
+
+
+ +
+
+
+ + {/* Título e mensagem */} +
+

+ Pagamento Confirmado! 🎉 +

+

+ Obrigado pela sua contribuição! Cada doação nos ajuda a manter o + projeto funcionando. +

+
+ + {/* Mensagem especial */} +
+

+ Sua doação faz toda a diferença para nossa comunidade. Muito + obrigado pelo apoio! ❤️ +

+
+ + {/* Botões */} +
+ + +
+
+ +
); } diff --git a/lib/payment-helper.ts b/lib/payment-helper.ts new file mode 100644 index 0000000..da01618 --- /dev/null +++ b/lib/payment-helper.ts @@ -0,0 +1,42 @@ +// Helper para gerenciar confirmações de pagamento via localStorage +// Este arquivo será usado pelo webhook para notificar o frontend + +export function confirmPayment(pixId: string) { + if (typeof window === "undefined") return; + + try { + // Obter array de pixIds confirmados + const confirmedStorage = localStorage.getItem("confirmedPixIds"); + let confirmed: string[] = confirmedStorage + ? JSON.parse(confirmedStorage) + : []; + + // Adicionar novo pixId se ainda não estiver na lista + if (!confirmed.includes(pixId)) { + confirmed.push(pixId); + localStorage.setItem("confirmedPixIds", JSON.stringify(confirmed)); + console.log("✅ Pagamento confirmado via helper:", pixId); + } + } catch (error) { + console.error("Erro ao confirmar pagamento:", error); + } +} + +export function isPaymentConfirmed(pixId: string): boolean { + if (typeof window === "undefined") return false; + + try { + const confirmedStorage = localStorage.getItem("confirmedPixIds"); + const confirmed: string[] = confirmedStorage + ? JSON.parse(confirmedStorage) + : []; + return confirmed.includes(pixId); + } catch { + return false; + } +} + +// Expor função global para o webhook (se necessário) +if (typeof window !== "undefined") { + (window as any).confirmPaymentPix = confirmPayment; +}