Template genérico de checkout com ASAAS, parametrizado via env vars. Inclui fluxo completo: checkout → pedido → polling → webhook → admin. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
3.5 KiB
TypeScript
103 lines
3.5 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { createServiceClient } from "@/lib/supabase"
|
|
import { parseWebhookPayload, mapearStatus } from "@/lib/asaas"
|
|
|
|
const N8N_WEBHOOK_URL = process.env.N8N_WEBHOOK_URL ?? ""
|
|
|
|
export async function POST(request: Request) {
|
|
let body: unknown
|
|
|
|
try {
|
|
body = await request.json()
|
|
} catch {
|
|
return NextResponse.json({ error: "Invalid JSON" }, { status: 400 })
|
|
}
|
|
|
|
const supabase = createServiceClient()
|
|
|
|
// Salva log do webhook para auditoria
|
|
await supabase.from("webhook_logs").insert({
|
|
evento: (body as { event?: string }).event ?? "unknown",
|
|
payload: body,
|
|
processado: false,
|
|
})
|
|
|
|
try {
|
|
const event = parseWebhookPayload(body)
|
|
|
|
// Processa apenas eventos de pagamento
|
|
if (!event.event.startsWith("PAYMENT_")) {
|
|
return NextResponse.json({ ok: true })
|
|
}
|
|
|
|
const novoStatus = mapearStatus(event.payment.status)
|
|
const isPago =
|
|
event.payment.status === "RECEIVED" || event.payment.status === "CONFIRMED"
|
|
const paidAt = isPago ? new Date().toISOString() : null
|
|
|
|
const updateData: Record<string, unknown> = { status: novoStatus }
|
|
if (paidAt) updateData.paid_at = paidAt
|
|
|
|
// Atualiza pedido e busca dados completos para notificação
|
|
const { data: pedido } = await supabase
|
|
.from("pedidos")
|
|
.update(updateData)
|
|
.eq("asaas_payment_id", event.payment.id)
|
|
.select("id, valor_centavos, metodo_pagamento, cliente_id, produto_id")
|
|
.single()
|
|
|
|
if (pedido) {
|
|
// Marca webhook como processado
|
|
await supabase
|
|
.from("webhook_logs")
|
|
.update({ processado: true })
|
|
.eq("evento", event.event)
|
|
.order("created_at", { ascending: false })
|
|
.limit(1)
|
|
|
|
// Dispara automação n8n apenas em pagamentos confirmados
|
|
if (isPago && N8N_WEBHOOK_URL) {
|
|
// Busca cliente e produto para enriquecer o payload
|
|
const [{ data: cliente }, { data: produto }] = await Promise.all([
|
|
supabase.from("clientes").select("nome, email, telefone, cpf_cnpj").eq("id", pedido.cliente_id).single(),
|
|
supabase.from("produtos").select("nome, tipo, validade, midia").eq("id", pedido.produto_id).single(),
|
|
])
|
|
|
|
const notificacao = {
|
|
evento: event.event,
|
|
pedido_id: pedido.id,
|
|
asaas_payment_id: event.payment.id,
|
|
valor: (pedido.valor_centavos / 100).toLocaleString("pt-BR", { minimumFractionDigits: 2, style: "currency", currency: "BRL" }),
|
|
metodo: pedido.metodo_pagamento,
|
|
cliente: {
|
|
nome: cliente?.nome ?? "—",
|
|
email: cliente?.email ?? "—",
|
|
telefone: cliente?.telefone ?? "—",
|
|
cpf_cnpj: cliente?.cpf_cnpj ?? "—",
|
|
},
|
|
produto: {
|
|
nome: produto?.nome ?? "—",
|
|
tipo: produto?.tipo ?? "—",
|
|
validade: produto?.validade ?? "—",
|
|
midia: produto?.midia ?? "—",
|
|
},
|
|
pago_em: paidAt,
|
|
link_agendamento: process.env.NEXT_PUBLIC_AFTER_PAYMENT_REDIRECT ?? "/",
|
|
}
|
|
|
|
// Fire-and-forget — não bloqueia a resposta ao ASAAS
|
|
fetch(N8N_WEBHOOK_URL, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(notificacao),
|
|
}).catch((err) => console.error("n8n webhook error:", err))
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ ok: true })
|
|
} catch (error) {
|
|
console.error("Webhook processing error:", error)
|
|
return NextResponse.json({ error: "Processing failed" }, { status: 500 })
|
|
}
|
|
}
|