Convert billing to 1-to-100 credits
This commit is contained in:
+21
-6
@@ -6,6 +6,8 @@ const {
|
||||
listModelPrices,
|
||||
loadPriceCache,
|
||||
creditUserBalance,
|
||||
creditsToCreditUnits,
|
||||
formatCreditsFromCents,
|
||||
pool,
|
||||
validateUsername,
|
||||
validatePassword,
|
||||
@@ -156,14 +158,18 @@ function registerAdminRoutes(router) {
|
||||
|
||||
router.post("/admin/users/:id/credit", requireAuth, requireAdmin, async (req, res) => {
|
||||
const targetUserId = Number(req.params.id);
|
||||
const { amountCents } = req.body;
|
||||
if (!amountCents || amountCents <= 0) return res.status(400).json({ error: "积分必须大于 0" });
|
||||
const { amountCredits, amountCents } = req.body;
|
||||
const creditUnits =
|
||||
amountCredits !== undefined && amountCredits !== null && amountCredits !== ""
|
||||
? creditsToCreditUnits(amountCredits)
|
||||
: Number(amountCents);
|
||||
if (!creditUnits || creditUnits <= 0) return res.status(400).json({ error: "积分必须大于 0" });
|
||||
|
||||
try {
|
||||
const newBalance = await creditUserBalance(
|
||||
targetUserId,
|
||||
amountCents,
|
||||
`管理员 ${req.user.username} 发放 ${Math.floor(amountCents / 100)} 积分`,
|
||||
creditUnits,
|
||||
`管理员 ${req.user.username} 发放 ${formatCreditsFromCents(creditUnits)} 积分`,
|
||||
);
|
||||
res.json({ success: true, newBalanceCents: newBalance });
|
||||
} catch (err) {
|
||||
@@ -547,6 +553,8 @@ function registerAdminRoutes(router) {
|
||||
name,
|
||||
description = "",
|
||||
priceCents,
|
||||
credits,
|
||||
amountCredits,
|
||||
creditsCents = 0,
|
||||
imageQuota = 0,
|
||||
videoQuota = 0,
|
||||
@@ -572,7 +580,9 @@ function registerAdminRoutes(router) {
|
||||
name,
|
||||
description,
|
||||
Number(priceCents),
|
||||
Number(creditsCents || 0),
|
||||
credits !== undefined || amountCredits !== undefined
|
||||
? creditsToCreditUnits(credits ?? amountCredits)
|
||||
: Number(creditsCents || 0),
|
||||
Number(imageQuota || 0),
|
||||
Number(videoQuota || 0),
|
||||
Number(textQuota || 0),
|
||||
@@ -599,6 +609,8 @@ function registerAdminRoutes(router) {
|
||||
name,
|
||||
description,
|
||||
priceCents,
|
||||
credits,
|
||||
amountCredits,
|
||||
creditsCents,
|
||||
imageQuota,
|
||||
videoQuota,
|
||||
@@ -623,7 +635,10 @@ function registerAdminRoutes(router) {
|
||||
updates.push(`price_cents = $${idx++}`);
|
||||
params.push(Number(priceCents));
|
||||
}
|
||||
if (creditsCents !== undefined) {
|
||||
if (credits !== undefined || amountCredits !== undefined) {
|
||||
updates.push(`credits_cents = $${idx++}`);
|
||||
params.push(creditsToCreditUnits(credits ?? amountCredits));
|
||||
} else if (creditsCents !== undefined) {
|
||||
updates.push(`credits_cents = $${idx++}`);
|
||||
params.push(Number(creditsCents));
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ const {
|
||||
getUserEnterpriseId,
|
||||
getEnterpriseName,
|
||||
preauthorizeCall,
|
||||
creditsToCreditUnits,
|
||||
formatCreditsFromCents,
|
||||
} = require("../billing");
|
||||
const wechatPay = require("../paymentWechat");
|
||||
const alipay = require("../paymentAlipay");
|
||||
@@ -793,6 +795,8 @@ module.exports = {
|
||||
getUserEnterpriseId,
|
||||
getEnterpriseName,
|
||||
preauthorizeCall,
|
||||
creditsToCreditUnits,
|
||||
formatCreditsFromCents,
|
||||
wechatPay,
|
||||
alipay,
|
||||
crypto,
|
||||
|
||||
@@ -2,6 +2,7 @@ const {
|
||||
requireAuth,
|
||||
requireEnterpriseAdmin,
|
||||
distributeCredits,
|
||||
creditsToCreditUnits,
|
||||
getEnterpriseFinancials,
|
||||
getEnterpriseName,
|
||||
pool,
|
||||
@@ -302,25 +303,33 @@ function registerEnterpriseRoutes(router) {
|
||||
});
|
||||
|
||||
router.post("/enterprise/distribute", requireAuth, requireEnterpriseAdmin, async (req, res) => {
|
||||
const { userId, amountCents, distributions } = req.body;
|
||||
const { userId, amountCredits, amountCents, distributions } = req.body;
|
||||
|
||||
try {
|
||||
if (distributions && Array.isArray(distributions)) {
|
||||
for (const d of distributions) {
|
||||
if (!d.userId || !d.amountCents || d.amountCents <= 0) {
|
||||
const creditUnits =
|
||||
d.amountCredits !== undefined && d.amountCredits !== null && d.amountCredits !== ""
|
||||
? creditsToCreditUnits(d.amountCredits)
|
||||
: Number(d.amountCents);
|
||||
if (!d.userId || !creditUnits || creditUnits <= 0) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "每条分发记录必须包含有效的 userId 和 amountCents" });
|
||||
.json({ error: "每条分发记录必须包含有效的 userId 和 amountCredits" });
|
||||
}
|
||||
await distributeCredits(req.user.enterpriseId, d.userId, d.amountCents, req.user.id);
|
||||
await distributeCredits(req.user.enterpriseId, d.userId, creditUnits, req.user.id);
|
||||
}
|
||||
res.json({ success: true, count: distributions.length });
|
||||
} else if (userId && amountCents) {
|
||||
if (amountCents <= 0) return res.status(400).json({ error: "分发积分必须大于0" });
|
||||
} else if (userId && (amountCredits || amountCents)) {
|
||||
const creditUnits =
|
||||
amountCredits !== undefined && amountCredits !== null && amountCredits !== ""
|
||||
? creditsToCreditUnits(amountCredits)
|
||||
: Number(amountCents);
|
||||
if (!creditUnits || creditUnits <= 0) return res.status(400).json({ error: "分发积分必须大于0" });
|
||||
const result = await distributeCredits(
|
||||
req.user.enterpriseId,
|
||||
userId,
|
||||
amountCents,
|
||||
creditUnits,
|
||||
req.user.id,
|
||||
);
|
||||
res.json({ success: true, ...result });
|
||||
@@ -349,7 +358,7 @@ function registerEnterpriseRoutes(router) {
|
||||
u.username,
|
||||
u.balance_cents AS current_balance_cents,
|
||||
COUNT(acl.id) AS total_calls,
|
||||
COALESCE(SUM(CASE WHEN acl.cost_estimate IS NOT NULL THEN CAST(ROUND((acl.cost_estimate * 100)::numeric) AS INTEGER) ELSE 0 END), 0) AS total_cost_cents,
|
||||
COALESCE(SUM(CASE WHEN acl.cost_estimate IS NOT NULL THEN CAST(ROUND((acl.cost_estimate * 10000)::numeric) AS INTEGER) ELSE 0 END), 0) AS total_cost_cents,
|
||||
MAX(acl.created_at) AS last_active
|
||||
FROM users u
|
||||
LEFT JOIN api_call_logs acl ON acl.user_id = u.id AND acl.status = 'success'
|
||||
|
||||
+3
-3
@@ -136,7 +136,7 @@ function registerUserRoutes(router) {
|
||||
CASE
|
||||
WHEN billing_refunded = 1 THEN 0
|
||||
WHEN cost_cents > 0 THEN cost_cents
|
||||
WHEN status = 'completed' AND type = 'image' THEN 20
|
||||
WHEN status = 'completed' AND type = 'image' THEN 2000
|
||||
WHEN status = 'completed' AND type = 'video' THEN 500
|
||||
ELSE 0
|
||||
END
|
||||
@@ -162,7 +162,7 @@ function registerUserRoutes(router) {
|
||||
resolution = params.resolution || params.quality || params.ratio || null;
|
||||
if (row.status === "completed") {
|
||||
if (row.type === "image") {
|
||||
estimatedCents = 20;
|
||||
estimatedCents = 2000;
|
||||
} else if (row.type === "video") {
|
||||
const dur = params.duration || 5;
|
||||
const res = String(params.resolution || params.quality || "").toUpperCase();
|
||||
@@ -209,7 +209,7 @@ function registerUserRoutes(router) {
|
||||
CASE
|
||||
WHEN billing_refunded = 1 THEN 0
|
||||
WHEN cost_cents > 0 THEN cost_cents
|
||||
WHEN status = 'completed' AND type = 'image' THEN 20
|
||||
WHEN status = 'completed' AND type = 'image' THEN 2000
|
||||
WHEN status = 'completed' AND type = 'video' THEN 500
|
||||
ELSE 0
|
||||
END
|
||||
|
||||
Reference in New Issue
Block a user