feat(economy): tighten credit earning + deduct on request
Credit economy changes: - creditsPerMinute: 10 -> 2 (45min episode = 90 COOP vs 450) - movieRequestCost: 100 -> 500 (~5.5 episodes per movie) - tvRequestCost: 200 -> 1000 (~11 episodes per season) - tvPerSeasonCost: 50 -> 250 Race condition fix: - Credits now deducted immediately at request time - No more multiple requests before approval - On DECLINED: automatic refund with ADJUSTMENT transaction - On APPROVED: no extra deduction (already paid) Updated schema defaults and all code fallbacks.
This commit is contained in:
@@ -25,9 +25,9 @@ router.get(
|
||||
const settings = await prisma.systemSettings.findFirst();
|
||||
|
||||
res.json({
|
||||
movie: settings?.movieRequestCost || 100,
|
||||
tv: settings?.tvRequestCost || 200,
|
||||
tvPerSeason: settings?.tvPerSeasonCost || 50,
|
||||
movie: settings?.movieRequestCost || 500,
|
||||
tv: settings?.tvRequestCost || 1000,
|
||||
tvPerSeason: settings?.tvPerSeasonCost || 250,
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -55,11 +55,11 @@ router.get(
|
||||
res.json({
|
||||
hasWallet: true,
|
||||
balance: dbBalance,
|
||||
canRequestMovie: dbBalance >= (settings?.movieRequestCost || 100),
|
||||
canRequestTV: dbBalance >= (settings?.tvRequestCost || 200),
|
||||
canRequestMovie: dbBalance >= (settings?.movieRequestCost || 500),
|
||||
canRequestTV: dbBalance >= (settings?.tvRequestCost || 1000),
|
||||
costs: {
|
||||
movie: settings?.movieRequestCost || 100,
|
||||
tv: settings?.tvRequestCost || 200,
|
||||
movie: settings?.movieRequestCost || 500,
|
||||
tv: settings?.tvRequestCost || 1000,
|
||||
},
|
||||
});
|
||||
}),
|
||||
@@ -108,11 +108,11 @@ router.post(
|
||||
// Calculate cost
|
||||
let cost = 0;
|
||||
if (mediaType === "movie") {
|
||||
cost = settings?.movieRequestCost || 100;
|
||||
cost = settings?.movieRequestCost || 500;
|
||||
} else if (mediaType === "tv") {
|
||||
cost = settings?.tvRequestCost || 200;
|
||||
cost = settings?.tvRequestCost || 1000;
|
||||
if (seasons && seasons.length > 1) {
|
||||
cost += (seasons.length - 1) * (settings?.tvPerSeasonCost || 50);
|
||||
cost += (seasons.length - 1) * (settings?.tvPerSeasonCost || 250);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
// Create request in Overseer
|
||||
// Create request in Overseer first
|
||||
const overseerRequest = await overseerClient.post("/request", {
|
||||
mediaType,
|
||||
mediaId,
|
||||
@@ -147,11 +147,37 @@ router.post(
|
||||
},
|
||||
});
|
||||
|
||||
// Deduct credits immediately to prevent race condition
|
||||
const transaction = await prisma.transaction.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
type: "SPEND",
|
||||
amount: cost,
|
||||
requestId: request.id,
|
||||
description: `Request: ${title}`,
|
||||
contentTitle: title,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
totalSpent: { increment: cost },
|
||||
},
|
||||
});
|
||||
|
||||
// Emit real-time spend update
|
||||
io.to(`user:${user.id}`).emit("credits_spent", {
|
||||
amount: cost,
|
||||
title,
|
||||
transaction,
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
request,
|
||||
cost,
|
||||
message: "Request submitted. Credits will be deducted when approved.",
|
||||
message: `Request submitted. ${cost} $COOP deducted.`,
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -182,35 +208,31 @@ router.post(
|
||||
data: { status },
|
||||
});
|
||||
|
||||
// If approved, deduct credits
|
||||
if (status === "APPROVED" && !request.user.totalSpent) {
|
||||
// Note: Actual burning would happen here
|
||||
// For now, we just record the transaction
|
||||
|
||||
const transaction = await prisma.transaction.create({
|
||||
// Credits were already deducted at request time.
|
||||
// On approval: just confirm and link transaction to request.
|
||||
// On decline: refund credits back to user.
|
||||
if (status === "DECLINED") {
|
||||
const refundTransaction = await prisma.transaction.create({
|
||||
data: {
|
||||
userId: request.userId,
|
||||
type: "SPEND",
|
||||
type: "ADJUSTMENT",
|
||||
amount: request.creditsCost,
|
||||
requestId: request.id,
|
||||
description: `Request: ${request.title}`,
|
||||
description: `Refunded: ${request.title}`,
|
||||
contentTitle: request.title,
|
||||
},
|
||||
});
|
||||
|
||||
// Update user stats
|
||||
await prisma.user.update({
|
||||
where: { id: request.userId },
|
||||
data: {
|
||||
totalSpent: { increment: request.creditsCost },
|
||||
totalSpent: { decrement: request.creditsCost },
|
||||
},
|
||||
});
|
||||
|
||||
// Emit update
|
||||
io.to(`user:${request.userId}`).emit("credits_spent", {
|
||||
io.to(`user:${request.userId}`).emit("bonus_received", {
|
||||
amount: request.creditsCost,
|
||||
title: request.title,
|
||||
transaction,
|
||||
reason: `Refunded: ${request.title}`,
|
||||
transaction: refundTransaction,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user