- wrap root layout with QueryProvider for React Query - replace market UnderConstruction placeholder with MarketClient and real data fetching - implement search, pagination, and sort via search params using getActiveListings - add necessary client components and queries - update dependencies (e.g., prisma, pkg-types)
167 lines
5.4 KiB
TypeScript
167 lines
5.4 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { secureLogger } from "@/lib/secure-logger";
|
|
import { requireAuth } from "@/lib/auth/middleware";
|
|
import { getListingById } from "@/lib/prisma/queries/listings";
|
|
import { createNotification } from "@/lib/prisma/queries/notifications";
|
|
import {
|
|
createPurchaseIntention,
|
|
hasPendingIntention,
|
|
} from "@/lib/prisma/queries/purchase-intentions";
|
|
import { trackEvent } from "@/lib/prisma/queries/telemetry";
|
|
|
|
/**
|
|
* POST /api/listings/[id]/purchase
|
|
* Cria uma intenção de compra para um item anunciado
|
|
*/
|
|
export async function POST(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } }
|
|
) {
|
|
try {
|
|
const authResult = await requireAuth(request);
|
|
if (authResult instanceof NextResponse) return authResult;
|
|
const auth = authResult;
|
|
|
|
const listingId = parseInt(params.id, 10);
|
|
if (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 } = body; // 'pix' | 'tc' | 'gp' | 'trade'
|
|
|
|
if (!payment_method) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "Método de pagamento é obrigatório" },
|
|
{ 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 comprar 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 ser comprados diretamente, apenas com 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");
|
|
|
|
// Normalizar método de pagamento
|
|
let normalizedMethod = payment_method;
|
|
if (payment_method === "tc") normalizedMethod = "tibia_coins";
|
|
if (payment_method === "gp") normalizedMethod = "gold_coins";
|
|
|
|
if (!availableMethods.includes(normalizedMethod)) {
|
|
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 }
|
|
);
|
|
}
|
|
|
|
// Verificar se já existe uma intenção pendente
|
|
const alreadyHasPending = await hasPendingIntention(listingId, auth.userId);
|
|
if (alreadyHasPending) {
|
|
return NextResponse.json(
|
|
{ success: false, error: "Você já possui uma intenção de compra pendente para este anúncio" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Criar intenção de compra ao invés de mudar status diretamente
|
|
const intention = await createPurchaseIntention({
|
|
listing_id: listingId,
|
|
buyer_id: auth.userId,
|
|
payment_method: normalizedMethod as any,
|
|
buyer_message: buyer_message || undefined,
|
|
});
|
|
|
|
// Criar notificação para o vendedor
|
|
await createNotification({
|
|
user_id: listing.seller_id,
|
|
notification_type: "purchase",
|
|
title: "Nova intenção de compra!",
|
|
message: `Alguém manifestou interesse em comprar seu item "${listing.item_name}" por ${normalizedMethod.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: normalizedMethod,
|
|
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,
|
|
},
|
|
});
|
|
|
|
secureLogger.info("✅ Intenção de compra criada com sucesso", {
|
|
userId: auth.userId,
|
|
listingId,
|
|
intentionId: intention.intention_id,
|
|
paymentMethod: normalizedMethod,
|
|
});
|
|
|
|
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 }
|
|
);
|
|
}
|
|
}
|
|
|