feat(billing): add usage increment API + wire hand/pipeline execution tracking
Server side: - POST /api/v1/billing/usage/increment endpoint with dimension whitelist (hand_executions, pipeline_runs, relay_requests) and count validation (1-100) - Returns updated usage quota after increment Desktop side: - New saas-billing.ts mixin with incrementUsageDimension() and reportUsageFireAndForget() (non-blocking, safe for finally blocks) - handStore.triggerHand: reports hand_executions after successful run - PipelinesPanel.handleRunComplete: reports pipeline_runs on completion - SaaSClient type declarations for new billing methods Billing pipeline now covers all three dimensions: relay_requests → relay handler (server-side, real-time) hand_executions → handStore (client-side, fire-and-forget) pipeline_runs → PipelinesPanel (client-side, fire-and-forget)
This commit is contained in:
@@ -56,6 +56,53 @@ pub async fn get_usage(
|
||||
Ok(Json(usage))
|
||||
}
|
||||
|
||||
/// POST /api/v1/billing/usage/increment — 客户端上报用量(Hand/Pipeline 执行后调用)
|
||||
///
|
||||
/// 请求体: `{ "dimension": "hand_executions" | "pipeline_runs" | "relay_requests", "count": 1 }`
|
||||
/// 需要认证 — account_id 从 JWT 提取。
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct IncrementUsageRequest {
|
||||
/// 用量维度:hand_executions / pipeline_runs / relay_requests
|
||||
pub dimension: String,
|
||||
/// 递增数量,默认 1
|
||||
#[serde(default = "default_count")]
|
||||
pub count: i32,
|
||||
}
|
||||
|
||||
fn default_count() -> i32 { 1 }
|
||||
|
||||
pub async fn increment_usage_dimension(
|
||||
State(state): State<AppState>,
|
||||
Extension(ctx): Extension<AuthContext>,
|
||||
Json(req): Json<IncrementUsageRequest>,
|
||||
) -> SaasResult<Json<serde_json::Value>> {
|
||||
// 验证维度白名单
|
||||
if !["hand_executions", "pipeline_runs", "relay_requests"].contains(&req.dimension.as_str()) {
|
||||
return Err(SaasError::InvalidInput(
|
||||
format!("无效的用量维度: {},支持: hand_executions / pipeline_runs / relay_requests", req.dimension)
|
||||
));
|
||||
}
|
||||
|
||||
// 限制单次递增上限(防滥用)
|
||||
if req.count < 1 || req.count > 100 {
|
||||
return Err(SaasError::InvalidInput(
|
||||
format!("count 必须在 1~100 范围内,得到: {}", req.count)
|
||||
));
|
||||
}
|
||||
|
||||
for _ in 0..req.count {
|
||||
service::increment_dimension(&state.db, &ctx.account_id, &req.dimension).await?;
|
||||
}
|
||||
|
||||
// 返回更新后的用量
|
||||
let usage = service::get_or_create_usage(&state.db, &ctx.account_id).await?;
|
||||
Ok(Json(serde_json::json!({
|
||||
"dimension": req.dimension,
|
||||
"incremented": req.count,
|
||||
"usage": usage,
|
||||
})))
|
||||
}
|
||||
|
||||
/// POST /api/v1/billing/payments — 创建支付订单
|
||||
pub async fn create_payment(
|
||||
State(state): State<AppState>,
|
||||
|
||||
@@ -13,6 +13,7 @@ pub fn routes() -> axum::Router<crate::state::AppState> {
|
||||
.route("/api/v1/billing/plans/{id}", get(handlers::get_plan))
|
||||
.route("/api/v1/billing/subscription", get(handlers::get_subscription))
|
||||
.route("/api/v1/billing/usage", get(handlers::get_usage))
|
||||
.route("/api/v1/billing/usage/increment", post(handlers::increment_usage_dimension))
|
||||
.route("/api/v1/billing/payments", post(handlers::create_payment))
|
||||
.route("/api/v1/billing/payments/{id}", get(handlers::get_payment_status))
|
||||
// 支付回调(无需 auth)
|
||||
|
||||
Reference in New Issue
Block a user