import { type NextRequest, NextResponse } from "next/server"; import { requireAuth } from "@/lib/auth/middleware"; import { getListingById } from "@/lib/prisma/queries/listings"; import { createNotification } from "@/lib/prisma/queries/notifications"; import { createPurchaseIntention, getPendingIntentionByBuyerAndListing, getPurchaseIntentionsByListing, } from "@/lib/prisma/queries/purchase-intentions"; import { trackEvent } from "@/lib/prisma/queries/telemetry"; import { secureLogger } from "@/lib/secure-logger"; /** * POST /api/listings/[id]/intentions * Cria uma nova intenção de compra */ export async function POST( request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { try { const authResult = await requireAuth(request); if (authResult instanceof NextResponse) return authResult; const auth = authResult; const { id } = await params; const listingId = parseInt(id, 10); if (Number.isNaN(listingId)) { return NextResponse.json( { success: false, error: "ID de anúncio inválido" }, { status: 400 }, ); } const body = await request.json(); const { payment_method, buyer_message, custom_offer_amount, custom_offer_currency, } = body; if ( !payment_method || !["pix", "tibia_coins", "gold_coins", "trade"].includes(payment_method) ) { return NextResponse.json( { success: false, error: "Método de pagamento inválido" }, { status: 400 }, ); } // Buscar listing const listing = await getListingById(listingId); if (!listing) { return NextResponse.json( { success: false, error: "Anúncio não encontrado" }, { status: 404 }, ); } // Validar se não é o vendedor if (listing.seller_id === auth.userId) { return NextResponse.json( { success: false, error: "Você não pode criar uma intenção de compra para seu próprio anúncio", }, { status: 403 }, ); } // Validar se está ativo if (listing.listing_status !== "active") { return NextResponse.json( { success: false, error: "Anúncio não está disponível para compra" }, { status: 400 }, ); } // Validar se é leilão (leilões não podem ter intenções, apenas lances) if (listing.is_auction) { return NextResponse.json( { success: false, error: "Este item está em leilão. Use a funcionalidade de lance.", }, { status: 400 }, ); } // Validar método de pagamento disponível const availableMethods: string[] = []; if (listing.price_pix) availableMethods.push("pix"); if (listing.price_tibia_coins) availableMethods.push("tibia_coins"); if (listing.price_gold_coins) availableMethods.push("gold_coins"); if (listing.trade_item_id) availableMethods.push("trade"); if (!availableMethods.includes(payment_method)) { return NextResponse.json( { success: false, error: `Método de pagamento "${payment_method}" não está disponível para este anúncio`, available_methods: availableMethods, }, { status: 400 }, ); } let normalizedOfferValue: number | null = null; let normalizedOfferCurrency: string | null = null; if (custom_offer_amount !== undefined && custom_offer_amount !== null) { if (!["pix", "tibia_coins", "gold_coins"].includes(payment_method)) { return NextResponse.json( { success: false, error: "Ofertas personalizadas só são permitidas para métodos monetários (PIX, Tibia Coins ou Gold Coins).", }, { status: 400 }, ); } const numericOffer = Number(custom_offer_amount); if (Number.isNaN(numericOffer) || numericOffer <= 0) { return NextResponse.json( { success: false, error: "Valor da oferta personalizada deve ser numérico e maior que zero.", }, { status: 400 }, ); } normalizedOfferCurrency = custom_offer_currency && ["pix", "tibia_coins", "gold_coins"].includes(custom_offer_currency) ? custom_offer_currency : payment_method; normalizedOfferValue = normalizedOfferCurrency === "pix" ? Number(numericOffer.toFixed(2)) : Math.floor(numericOffer); } // Verificar se já existe uma intenção pendente const existingIntention = await getPendingIntentionByBuyerAndListing( listingId, auth.userId, ); if (existingIntention) { // Se já existe e ainda pode trocar o método if (existingIntention.payment_method_change_count < 1) { // Permite atualizar o método via endpoint específico return NextResponse.json( { success: false, error: "Você já possui uma intenção de compra pendente para este anúncio", existing_intention_id: existingIntention.intention_id, can_change_payment_method: true, payment_method_change_count: existingIntention.payment_method_change_count, message: "Use o endpoint PATCH /api/purchase-intentions/[intentionId]/payment-method para trocar o método de pagamento", }, { status: 400 }, ); } else { // Já atingiu o limite de trocas return NextResponse.json( { success: false, error: "Você já possui uma intenção de compra pendente para este anúncio e atingiu o limite de trocas de método de pagamento", existing_intention_id: existingIntention.intention_id, }, { status: 400 }, ); } } // Criar intenção de compra const intention = await createPurchaseIntention({ listing_id: listingId, buyer_id: auth.userId, payment_method: payment_method as any, buyer_message: buyer_message || undefined, custom_offer_amount: normalizedOfferValue ?? undefined, custom_offer_currency: normalizedOfferCurrency as any, }); // Criar notificação para o vendedor await createNotification({ user_id: listing.seller_id, notification_type: "purchase", title: "Nova intenção de compra!", message: normalizedOfferValue && normalizedOfferCurrency ? `Alguém fez uma oferta de ${normalizedOfferCurrency === "pix" ? `R$ ${normalizedOfferValue.toFixed(2)}` : `${normalizedOfferValue.toLocaleString()} ${normalizedOfferCurrency === "tibia_coins" ? "TC" : "GP"}`} pelo item "${listing.item_name}".` : `Alguém manifestou interesse em comprar seu item "${listing.item_name}" por ${payment_method.toUpperCase()}`, related_id: listingId, send_whatsapp: false, }); // Registrar telemetria await trackEvent({ event_type: "purchase_intention_created", ad_id: listingId, user_id: auth.userId, metadata: { payment_method, listing_price_pix: listing.price_pix ? Number(listing.price_pix) : null, listing_price_tc: listing.price_tibia_coins || null, listing_price_gp: listing.price_gold_coins ? Number(listing.price_gold_coins) : null, custom_offer_amount: normalizedOfferValue, custom_offer_currency: normalizedOfferCurrency, }, }); secureLogger.info("✅ Intenção de compra criada com sucesso", { userId: auth.userId, listingId, intentionId: intention.intention_id, paymentMethod: payment_method, }); return NextResponse.json( { success: true, intention, message: "Intenção de compra criada com sucesso. O vendedor será notificado.", }, { status: 201 }, ); } catch (error) { secureLogger.error("❌ Erro ao criar intenção de compra", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, }); return NextResponse.json( { success: false, error: "Erro ao criar intenção de compra" }, { status: 500 }, ); } } /** * GET /api/listings/[id]/intentions * Lista intenções de compra de um anúncio (apenas vendedor) */ export async function GET( request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { try { const authResult = await requireAuth(request); if (authResult instanceof NextResponse) return authResult; const auth = authResult; const { id } = await params; const listingId = parseInt(id, 10); if (Number.isNaN(listingId)) { return NextResponse.json( { success: false, error: "ID de anúncio inválido" }, { status: 400 }, ); } // Buscar listing para verificar se o usuário é o vendedor const listing = await getListingById(listingId); if (!listing) { return NextResponse.json( { success: false, error: "Anúncio não encontrado" }, { status: 404 }, ); } // Apenas o vendedor pode ver as intenções if (listing.seller_id !== auth.userId) { return NextResponse.json( { success: false, error: "Você não tem permissão para ver as intenções deste anúncio", }, { status: 403 }, ); } // Buscar parâmetros de filtro const { searchParams } = new URL(request.url); const status = searchParams.get("status") as any; const limit = parseInt(searchParams.get("limit") || "50", 10); // Buscar intenções const intentions = await getPurchaseIntentionsByListing(listingId, { status, limit, }); // Registrar telemetria await trackEvent({ event_type: "purchase_intention_viewed", ad_id: listingId, user_id: auth.userId, metadata: { intentions_count: intentions.length, }, }); return NextResponse.json( { success: true, intentions, count: intentions.length, }, { status: 200 }, ); } catch (error) { secureLogger.error("❌ Erro ao buscar intenções de compra", { error: error instanceof Error ? error.message : "Erro desconhecido", stack: error instanceof Error ? error.stack : undefined, }); return NextResponse.json( { success: false, error: "Erro ao buscar intenções de compra" }, { status: 500 }, ); } }