Skip to content

订阅管理 API

用户订阅接口,包括 Driver V2 稳定只读摘要,以及网页端查询、变更套餐、取消订阅退款、 取消续订和恢复续订。

路由前缀/billing

源码apps/backend/src/routes/billing-subscription.ts


认证

JWT 用户会话可以访问全部下列接口。OAuth 按最小权限拆分:

  • billing:read:只能调用 GET /billing/summary,且只签发给独立环境变量白名单中的官方 Driver V2 client。
  • orders:checkout:保留网页/订单链路的查询和四个写接口,不能读取新的 summary。
Authorization: Bearer <JWT>
Authorization: Bearer wno-...

billing:read V1 只读取用户首次创建的默认个人团队,不支持团队切换。旧查询接口允许 owner 或 accepted member;写入接口仍要求 owner/admin。


端点

网页 Driver 购买资格

GET /billing/driver-offer?teamId=<当前团队ID>,仅 JWT 用户会话可用;OAuth 不支持此接口。必填 teamId,只允许读取本人默认个人团队的资格,响应设置 Cache-Control: no-store。接口不创建订单、不调用支付渠道。

成功返回 { success: true, data },data 包含 teamIddefaultTeamSlugstatuscloudAccesspurchasecloudAccess 为已付权益摘要,停售时仍可读取有效期。非本人默认团队返回 wrong_team 和本人默认团队 slug,不查询其他团队权益;无默认团队返回 no_team

statusavailable 时,purchase 包含当前数据库实际 productCodepriceCodecurrencyamountMinorbillingPeriod,网页用户主动点击后传入现有订阅支付流程。其余状态 not_on_salepilot_unavailablesubscription_existspurchase_conflict 均返回 purchase: null。缺少团队参数返回 400,非 JWT 返回 403,数据库或有效销售价格配置异常返回固定脱敏错误 driver_subscription_unavailable / 503。缺失或未启用的销售配置返回 not_on_sale

此接口仅供 /o/:teamSlug/settings/billing 的独立 Driver 卡片使用,不依赖平台 billing.plans。下单及支付发起时仍复核资格,不能把只读查询结果当作付款授权。

1. 获取 Driver V2 只读订阅摘要

GET /billing/summary?teamId=<optional>

teamId 仅为未来多团队合同预留。V1 不传时稳定选择 teams.owner_user_id 属于当前用户、按 created_at ASC, id ASC 排序的首个个人团队;传入时必须等于该团队,否则统一返回 404。

响应只包含当前套餐、成交价快照、计费周期、取消语义、权益可用性标志、最近 3 条订阅账单摘要和网页管理入口,不返回 provider data、支付凭据、卡信息或地址。

json
{
  "success": true,
  "data": {
    "teamId": "uuid",
    "plan": { "code": "pro", "name": "Pro" },
    "price": { "amountMinor": 999, "currency": "USD", "source": "subscription_snapshot" },
    "billingPeriod": "monthly",
    "status": "active",
    "currentPeriodStart": "2026-08-01T00:00:00.000Z",
    "currentPeriodEnd": "2026-09-01T00:00:00.000Z",
    "renewal": {
      "willRenew": false,
      "cancelAtPeriodEnd": true,
      "cancelEffectiveAt": "2026-09-01T00:00:00.000Z",
      "canResume": true
    },
    "entitlements": { "status": "unavailable", "items": [] },
    "recentBillingRecords": [],
    "manageUrl": "https://app.example.com/o/personal-team/settings/billing"
  }
}

状态枚举固定为 none|active|canceled|past_due|trialing|unknown。历史订阅缺少可靠成交币种时 price=null;不会用当前目录价伪造成交价。manageUrl 不携带 OAuth token。

错误码: 默认个人团队不存在、传入其它团队或任何不可访问团队时均返回 404 BILLING_TEAM_NOT_FOUND;服务端配置或数据库读取失败返回 500 BILLING_SUMMARY_FAILED

2. 获取原有订阅与积分余额

GET /billing/subscription

获取当前团队的订阅详情和积分余额。

查询参数:

参数类型必填说明
teamIdstring团队 ID;未提供时稳定回退到默认个人团队

示例请求:

bash
curl -X GET "https://block2-api.wainao.chat/billing/subscription?teamId=xxx" \
  -H "Authorization: Bearer <JWT>"

响应 200 OK

json
{
  "success": true,
  "data": {
    "subscription": {
      "id": "uuid",
      "planCode": "pro",
      "billingPeriod": "monthly",
      "status": "active",
      "monthlyCredits": 100000,
      "currentPeriodStart": "2026-03-01T00:00:00.000Z",
      "currentPeriodEnd": "2026-04-01T00:00:00.000Z",
      "cancelAtPeriodEnd": false,
      "canceledAt": null,
      "scheduledPlanCode": null
    },
    "balance": {
      "totalCredits": 85000,
      "grantCredits": 80000,
      "purchaseCredits": 5000,
      "periodEnd": "2026-04-01T00:00:00.000Z",
      "periodGrantedCredits": 100000
    }
  }
}

提示

subscriptionbalance 可能为 null(例如新团队尚未创建订阅记录时)。

错误码:

HTTP错误码说明
400NO_TEAM用户不在任何团队中
500服务端错误

3. 变更套餐

POST /billing/subscription/change-plan

申请变更订阅计划。升级时返回支付指引(需先创建支付订单);降级时设置 scheduledPlanCode,在当前计费周期末自动生效。

请求体:

字段类型必填说明
teamIdstring团队 ID
targetPlanCodestring目标计划代码(如 profree
billingPeriodstring计费周期(如 monthlyyearly

示例请求:

bash
curl -X POST "https://block2-api.wainao.chat/billing/subscription/change-plan" \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{"teamId": "xxx", "targetPlanCode": "pro", "billingPeriod": "monthly"}'

响应 200 OK

json
{
  "success": true,
  "data": {
    "action": "downgrade_scheduled",
    "scheduledPlanCode": "free",
    "effectiveAt": "2026-04-01T00:00:00.000Z"
  }
}

错误码:

HTTP错误码说明
400BAD_REQUEST缺少 teamId
400缺少 targetPlanCode 或 billingPeriod
400NO_TEAM用户不在该团队中
403FORBIDDEN无权限(非 owner/admin)
500服务端错误

4. 取消订阅并退款

POST /billing/subscription/cancel

这是“立即取消”合同,不是只停自动续费。当前周期开始未满 7 天时,服务端会固定当前 周期 A 和所有已付款未来周期,先关闭尚可支付的旧入口,再先 future、后 current 地逐笔 全额退款。正好满 7 天即不退款。订单周期无法精确证明时不会猜测或发起部分退款。

网页默认个人团队账单页的独立 Driver 卡片提供此操作,已付有效且停售时仍可申请。必须二次确认,不自动触发或重试;团队或账号变化会关闭旧确认。界面仅以 200status=succeeded 显示完成,202 中的 processingpartially_failedfailed 分别展示实际进度或联系支持的提示,不伪装成退款成功。

billing:read 只有只读权限,不能调用本接口;JWT 或 orders:checkout 仍需通过团队 owner/admin 权限检查。

请求体:

字段类型必填说明
teamIdstring团队 ID;V1 为默认个人团队
reasonstring取消原因

处理中返回 202 Accepted,全部同步完成返回 200 OK

json
{
  "success": true,
  "data": {
    "batchId": "00000000-0000-0000-0000-000000000001",
    "status": "processing",
    "currentPeriodStart": "2026-08-01T00:00:00.000Z",
    "itemCount": 2,
    "items": []
  }
}

常见拒绝包括:当前周期已满 7 天、当前付款周期无法证明、存在无法归属的已收款未来 订单。以上情况均不会调用支付渠道。


5. 取消续订

POST /billing/subscription/cancel-renewal

取消自动续费。订阅在当前周期结束后不再续期。

请求体:

字段类型必填说明
teamIdstring团队 ID

示例请求:

bash
curl -X POST "https://block2-api.wainao.chat/billing/subscription/cancel-renewal" \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{"teamId": "xxx"}'

响应 200 OK

json
{
  "success": true,
  "data": {
    "status": "active",
    "cancelAtPeriodEnd": true,
    "effectiveAt": "2026-04-01T00:00:00.000Z",
    "canResume": true,
    "idempotent": false
  }
}

错误码:

HTTP错误码说明
400BAD_REQUEST缺少 teamId
400NO_TEAM用户不在该团队中
403FORBIDDEN无权限(非 owner/admin)
404subscription_not_found订阅不存在
409subscription_not_cancelable当前订阅状态不能取消续订
500服务端错误

6. 恢复续订

POST /billing/subscription/resume-renewal

仅在订阅仍为 active、尚未到期且处于 pending cancel 时恢复自动续费。已经到期或终态取消时返回 409 subscription_not_resumable,不会静默重新激活。

请求体:

字段类型必填说明
teamIdstring团队 ID

示例请求:

bash
curl -X POST "https://block2-api.wainao.chat/billing/subscription/resume-renewal" \
  -H "Authorization: Bearer <JWT>" \
  -H "Content-Type: application/json" \
  -d '{"teamId": "xxx"}'

响应 200 OK

json
{
  "success": true,
  "data": {
    "status": "active",
    "cancelAtPeriodEnd": false,
    "effectiveAt": null,
    "canResume": false,
    "idempotent": false
  }
}

错误码:

HTTP错误码说明
400BAD_REQUEST缺少 teamId
400NO_TEAM用户不在该团队中
403FORBIDDEN无权限(非 owner/admin)
404subscription_not_found订阅不存在
409subscription_not_resumable已到期或终态订阅不能恢复
500服务端错误

消费余额校验

消费扣费在数据库事务中校验正数金额、分项一致性和整笔费用是否足额。余额不足会拒绝扣费,不会把余额扣成负数;拒付不写成功消费流水、不增加 API Key 消耗。重复请求复用原有成功记录,不重复收费。

若提示 balance_inconsistent,表示历史总余额与赠送/购买分项不符,需要根据原流水审计修复,不能通过额外充值或重试掩盖。该错误与正常的 insufficient_credits(可用余额不足)不同。

取消后迟到收款说明

取消完成后,旧付款订单的成功通知仍按原订单退款归属处理。已确认的迟到收款只补退资金, 不重新发放权益,也不影响后来新购的订阅。重复通知与恢复任务复用原批次和在途退款号。 渠道状态无法确认时保留待核对,不能把关单请求成功视为退款或到账成功。

取消中的付款入口待核对

付款入口尚无权威终态时,取消响应保持 processing 并返回 warnings,不得展示成退款完成。payment_entry_unconfirmed 表示渠道意图已记录但入口查询未确认,legacy_payment_intent_unknown 表示历史付款意图未知;原因持久保存于原退款明细 last_error,管理员可按订单检索。一次查无订单不能证明未来没有迟到收款,确认收款后仍按原批次补退。部署时 SUBSCRIPTION_PAYMENT_INTENT_CLOSURE_ENABLED 默认关闭,只有共库节点旧支付进程和请求全部耗尽、并获配置批准后才能启用新单的本地关单证明;历史未知订单不会回填。

AI Workflow Editor