feat(billing): charge personal reports with durable settlement

This commit is contained in:
Jesse_Chen
2026-08-31 04:08:20 +08:00
parent aaaa5fb73c
commit a73d996f65
9 changed files with 286 additions and 14 deletions
+28
View File
@@ -24,6 +24,8 @@ import {
} from "@/lib/personal-report-service";
import { createSupabasePersonalReportJobService } from "@/lib/personal-report-job-service";
import { createAdminSupabaseClient } from "@/lib/supabase/admin";
import { authorizeUsage, completeUsage, releaseUsage } from "@/lib/consultation-billing";
import { resolveFeaturePricing } from "@/lib/feature-pricing";
import { isSupabaseConfigurationError } from "@/lib/supabase/config";
import { createServerSupabaseClient } from "@/lib/supabase/server";
@@ -195,6 +197,32 @@ export async function POST(request: Request) {
},
persistence,
model: defaultModel,
billing: {
reserve: async ({ userId: billingUserId, requestId, modelId }) => {
const pricing = await resolveFeaturePricing(admin, "report.full", modelId);
return authorizeUsage(admin, {
userId: billingUserId, requestId, featureKey: "report.full",
requestedModelId: modelId, creditCost: pricing.credit_cost,
});
},
complete: async ({ userId: billingUserId, requestId, usage }) => {
if (!defaultModel) return false;
const costMicrousd = Math.round((
usage.inputTokens * (defaultModel.inputCostMicrousdPerMillion ?? 0)
+ usage.outputTokens * (defaultModel.outputCostMicrousdPerMillion ?? 0)
) / 1_000_000);
const settled = await completeUsage(admin, billingUserId, requestId, {
eventKey: "report.full", actualModelId: usage.actualModelId,
modelConfigVersion: usage.modelConfigVersion, inputTokens: usage.inputTokens,
outputTokens: usage.outputTokens, costMicrousd, durationMs: usage.durationMs,
});
return settled.success;
},
release: async ({ userId: billingUserId, requestId, reason }) => {
const released = await releaseUsage(admin, billingUserId, requestId, reason);
return released.success;
},
},
runWorkflow: (input) => runConsultationWorkflow(input),
createAgent: (model) => createPersonalReportAgent(model as Parameters<typeof createPersonalReportAgent>[0]),
skillSnapshot: resolveSkillSnapshot(),