Some checks failed
Deploy / deploy (push) Has been cancelled
Players can now buy AfterCoin with real money (MercadoPago Checkout Pro, $15 MXN/AFC) and redeem AFC for gift cards or cash withdrawals. Admin fulfills redemptions manually. - Bridge: payments + redemptions tables, CRUD routes, PATCH auth - Next.js API: verify-disk, balance, create-preference, webhook (idempotent minting with HMAC signature verification), redeem, payment/redemption history - Frontend: hub, buy flow (4 packages + custom), redeem flow (gift cards + cash out), success/failure/pending pages, history with tabs, 8 components - i18n: full English + Spanish translations - Infra: nginx /api/afc/ → Next.js, docker-compose env vars, .env.example Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
72 lines
2.1 KiB
TypeScript
72 lines
2.1 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { randomUUID } from "crypto";
|
|
import { preferenceClient } from "../lib/mercadopago";
|
|
import { bridgePost, bridgePatch } from "../lib/bridge";
|
|
|
|
const PRICE_MXN = Number(process.env.AFC_PRICE_MXN) || 15;
|
|
const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000";
|
|
|
|
export async function POST(req: NextRequest) {
|
|
try {
|
|
const { diskId, amountAfc } = await req.json();
|
|
|
|
if (!diskId || !amountAfc || amountAfc < 1) {
|
|
return NextResponse.json(
|
|
{ error: "diskId and amountAfc (>=1) required" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
const amountMxn = amountAfc * PRICE_MXN;
|
|
const paymentId = randomUUID();
|
|
|
|
// Create payment record in bridge
|
|
await bridgePost("/api/payments", {
|
|
id: paymentId,
|
|
diskId,
|
|
amountAfc,
|
|
amountMxn,
|
|
});
|
|
|
|
// Create MercadoPago preference
|
|
const preference = await preferenceClient.create({
|
|
body: {
|
|
items: [
|
|
{
|
|
id: paymentId,
|
|
title: `${amountAfc} AfterCoin (AFC)`,
|
|
quantity: 1,
|
|
unit_price: amountMxn,
|
|
currency_id: "MXN",
|
|
},
|
|
],
|
|
external_reference: paymentId,
|
|
back_urls: {
|
|
success: `${BASE_URL}/afc/buy/success?payment_id=${paymentId}`,
|
|
failure: `${BASE_URL}/afc/buy/failure?payment_id=${paymentId}`,
|
|
pending: `${BASE_URL}/afc/buy/pending?payment_id=${paymentId}`,
|
|
},
|
|
auto_return: "approved",
|
|
notification_url:
|
|
process.env.MERCADOPAGO_WEBHOOK_URL ||
|
|
`${BASE_URL}/api/afc/webhook`,
|
|
},
|
|
});
|
|
|
|
// Store the MP preference ID
|
|
await bridgePatch(`/api/payments/${paymentId}`, {
|
|
mp_preference_id: preference.id,
|
|
});
|
|
|
|
return NextResponse.json({
|
|
paymentId,
|
|
initPoint: preference.init_point,
|
|
sandboxInitPoint: preference.sandbox_init_point,
|
|
});
|
|
} catch (e: unknown) {
|
|
const message = e instanceof Error ? e.message : "Unknown error";
|
|
console.error("create-preference error:", e);
|
|
return NextResponse.json({ error: message }, { status: 500 });
|
|
}
|
|
}
|