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
+16 -3
View File
@@ -887,6 +887,7 @@ export async function updateAdminUser(
balanceCents: number;
spaceQuotaBytes: number;
role: 'user' | 'admin';
billingFormula: 'A' | 'B';
}>,
): Promise<PortalUser> {
const result = await portalFetch<{ user: PortalUser }>(`/admin-api/users/${userId}`, {
@@ -1409,16 +1410,28 @@ export async function syncSubscriptionPlansToProduction(): Promise<PlanSyncResul
return result.sync;
}
export async function getBillingConfig(): Promise<BillingAdminConfigResponse> {
return portalFetch('/admin-api/billing/config');
export async function getBillingConfig(formula: 'A' | 'B' = 'A'): Promise<BillingAdminConfigResponse> {
const q = formula === 'B' ? '?formula=B' : '';
return portalFetch(`/admin-api/billing/config${q}`);
}
export async function updateBillingConfig(
config: BillingAdminConfig,
formula: 'A' | 'B' = 'A',
): Promise<BillingAdminConfigResponse> {
return portalFetch('/admin-api/billing/config', {
method: 'PUT',
body: JSON.stringify({ config }),
body: JSON.stringify({ config, formula }),
});
}
export async function assignBillingFormula(
userIds: string[],
formula: 'A' | 'B',
): Promise<{ ok: boolean; updated: number; formula: 'A' | 'B'; message?: string }> {
return portalFetch('/admin-api/billing/formula-assignments', {
method: 'POST',
body: JSON.stringify({ userIds, formula }),
});
}