feat(billing): add formula A/B admin UI and subscription renew status

Expose dual metering formula editing with batch user assignment, show
auto-renew and pending-recharge states in subscriptions, and document
billing acceptance checks for production rollout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-22 08:45:43 +08:00
parent a503fa9cbe
commit f6bb02254e
8 changed files with 503 additions and 171 deletions
+21 -5
View File
@@ -660,11 +660,12 @@ export function createAdminApp(services) {
res.json(await skillRuntimeConfigService.getPublicRuntimeConfig());
});
adminApi.get('/billing/config', requireAdmin, async (_req, res) => {
adminApi.get('/billing/config', requireAdmin, async (req, res) => {
if (!billingConfigService?.getAdminConfig) {
return res.status(503).json({ message: '计费公式配置服务未启用' });
}
return res.json(await billingConfigService.getAdminConfig());
const formula = req.query.formula ? String(req.query.formula) : undefined;
return res.json(await billingConfigService.getAdminConfig({ formula }));
});
const updateBillingConfig = async (req, res) => {
@@ -672,9 +673,10 @@ export function createAdminApp(services) {
return res.status(503).json({ message: '计费公式配置服务未启用' });
}
try {
const formula = req.body?.formula ? String(req.body.formula) : undefined;
return res.json(await billingConfigService.updateAdminConfig(
req.body?.config ?? req.body ?? {},
{ updatedBy: req.currentUser.id },
{ updatedBy: req.currentUser.id, formula },
));
} catch (error) {
if (error?.code === 'BILLING_CONFIG_ENV_LOCKED') {
@@ -687,11 +689,25 @@ export function createAdminApp(services) {
adminApi.put('/billing/config', requireAdmin, updateBillingConfig);
adminApi.patch('/billing/config', requireAdmin, updateBillingConfig);
adminApi.get('/billing/runtime', requireAdmin, async (_req, res) => {
adminApi.post('/billing/formula-assignments', requireAdmin, async (req, res) => {
if (!billingConfigService?.assignBillingFormula) {
return res.status(503).json({ message: '计费公式配置服务未启用' });
}
const { userIds, formula } = req.body ?? {};
const result = await billingConfigService.assignBillingFormula(userIds, formula);
if (!result.ok) {
return res.status(400).json(result);
}
return res.json(result);
});
adminApi.get('/billing/runtime', requireAdmin, async (req, res) => {
if (!billingConfigService?.getRuntimeState) {
return res.status(503).json({ message: '计费公式配置服务未启用' });
}
return res.json(await billingConfigService.getRuntimeState());
const formula = req.query.formula ? String(req.query.formula) : undefined;
const userId = req.query.userId ? String(req.query.userId) : undefined;
return res.json(await billingConfigService.getRuntimeState({ formula, userId }));
});
adminApi.get('/system-tests/accounts', requireAdmin, async (_req, res) => {