update nice visuel

This commit is contained in:
gpatruno
2026-07-25 20:04:34 +02:00
parent 3a8871cbae
commit 810e4ac922
78 changed files with 6742 additions and 3116 deletions
+138
View File
@@ -0,0 +1,138 @@
import { prisma } from './db.js';
import { formatFiat, paymentsProvider } from './shopCatalog.js';
import { getPurchasableOsuPack } from './shopOsu.js';
/**
* Create a pending osu top-up order.
* Mock provider completes client-side via /confirm.
* Stripe provider (when configured) would return a clientSecret.
*/
export async function createOsuCheckout(userId, packId) {
const pack = await getPurchasableOsuPack(packId);
if (!pack) {
const err = new Error('Unknown or disabled osu pack');
err.status = 400;
throw err;
}
const provider = paymentsProvider();
const order = await prisma.paymentOrder.create({
data: {
userId: Number(userId),
packId: pack.id,
osuAmount: pack.osu,
priceCents: pack.priceCents,
currency: pack.currency,
status: 'pending',
provider,
meta: JSON.stringify({
packName: pack.name,
displayPrice: formatFiat(pack.priceCents, pack.currency),
}),
},
});
if (provider === 'stripe') {
// Placeholder for Stripe PaymentIntent wiring.
// Set PAYMENTS_PROVIDER=stripe + STRIPE_SECRET_KEY when ready.
const err = new Error(
'Stripe checkout is configured but not wired yet — set PAYMENTS_PROVIDER=mock for local buys'
);
err.status = 501;
throw err;
}
return {
order: serializeOrder(order),
pack: {
id: pack.id,
name: pack.name,
osu: pack.osu,
priceCents: pack.priceCents,
currency: pack.currency,
displayPrice: formatFiat(pack.priceCents, pack.currency),
},
mode: 'mock',
/** Client calls POST /api/shop/osu/confirm with this id in mock mode */
confirmable: true,
};
}
/**
* Complete a mock checkout and credit osu (idempotent).
*/
export async function confirmMockOsuPayment(userId, orderId) {
const id = String(orderId || '');
if (!id) {
const err = new Error('Missing order id');
err.status = 400;
throw err;
}
return prisma.$transaction(async (tx) => {
const order = await tx.paymentOrder.findUnique({ where: { id } });
if (!order || order.userId !== Number(userId)) {
const err = new Error('Order not found');
err.status = 404;
throw err;
}
if (order.status === 'paid') {
const user = await tx.user.findUnique({ where: { id: userId } });
return { order: serializeOrder(order), user, alreadyPaid: true };
}
if (order.status !== 'pending') {
const err = new Error(`Order is ${order.status}`);
err.status = 400;
throw err;
}
if (order.provider !== 'mock') {
const err = new Error('This order must be completed by the payment provider');
err.status = 400;
throw err;
}
const now = new Date();
const updatedOrder = await tx.paymentOrder.update({
where: { id: order.id },
data: { status: 'paid', paidAt: now },
});
const user = await tx.user.update({
where: { id: userId },
data: { osuBalance: { increment: order.osuAmount } },
});
await tx.transaction.create({
data: {
userId,
type: 'osu_purchase',
amount: String(order.osuAmount),
meta: JSON.stringify({
orderId: order.id,
packId: order.packId,
priceCents: order.priceCents,
currency: order.currency,
provider: order.provider,
}),
},
});
return { order: serializeOrder(updatedOrder), user, alreadyPaid: false };
});
}
export function serializeOrder(order) {
return {
id: order.id,
packId: order.packId,
osuAmount: order.osuAmount,
priceCents: order.priceCents,
currency: order.currency,
status: order.status,
provider: order.provider,
displayPrice: formatFiat(order.priceCents, order.currency),
createdAt: order.createdAt,
paidAt: order.paidAt || null,
};
}