# Heicode Manager PayPal 支付接入与计费关系说明 更新时间:2026-05-28 ## 1. 结论 Heicode Manager 目前已经有余额充值、套餐订阅、订单、回调、补单、模型调用扣费和订阅扣费的基础能力。当前要接 PayPal,优先不要重做计费体系,而是在现有支付网关抽象中新增 `paypal` 支付提供方。 建议分两期: | 阶段 | 范围 | 说明 | |---|---|---| | 第一期 | PayPal Checkout 一次性支付 | 覆盖余额充值和 Heicode 内部订阅套餐购买。支付成功后沿用现有 `top_ups`、`subscription_orders`、`user_subscriptions` 逻辑。 | | 第二期 | PayPal 原生自动续订 | 只有业务明确需要自动续费时再做。需要新增 PayPal product / plan / subscription 映射、续费 webhook、取消和过期同步。 | 当前更稳妥的方案是第一期:用户通过 PayPal 支付一笔钱,Manager 在回调确认成功后给用户增加余额或开通一个 Heicode 内部订阅套餐。 ## 2. 现有计费对象 | 对象 | 当前代码/表 | 作用 | |---|---|---| | 钱包余额 | `users.quota` | 用户模型调用的可用余额。 | | 充值订单 | `top_ups` | 记录一次余额充值订单,成功后增加用户 `quota`。 | | 订阅套餐 | `subscription_plans` | 管理员配置套餐价格、周期、总额度、重置周期、升级分组。 | | 订阅订单 | `subscription_orders` | 用户购买套餐时生成订单,支付成功后创建用户订阅。 | | 用户订阅 | `user_subscriptions` | 用户已购买的套餐实例,模型调用可优先从订阅额度扣。 | | 订阅预扣记录 | `subscription_pre_consume_records` | 模型调用时对订阅额度做预扣、结算、失败退款。 | | 模型调用日志 | `logs`、`task` 相关表 | 记录模型、用量、扣费、请求状态。 | ## 3. 充值后模型费用怎么走 余额充值和模型扣费是两个独立阶段。 ### 3.1 充值阶段 用户发起充值: ```text 用户选择充值金额 -> Manager 创建支付订单 -> PayPal 创建 checkout order -> 用户跳转 PayPal 支付 -> PayPal 回调 Manager -> Manager 验证回调和订单状态 -> top_ups 标记 success -> users.quota 增加对应额度 -> 记录充值日志 ``` 现有非 PayPal 代码里,充值金额换算逻辑大致是: ```text 支付金额 = 用户选择的充值数量 * Price * TopupGroupRatio * AmountDiscount ``` 如果系统展示类型是 tokens,则会先按 `QuotaPerUnit` 换算成金额单位。当前生产公开状态中 `quota_display_type=USD`,`quota_per_unit=500000`,`price=7.3`,说明用户侧余额展示和内部 quota 之间已有换算关系。 PayPal 接入后也应复用同一套换算规则,不应单独发明 PayPal 专属余额单位。 ### 3.2 模型调用扣费阶段 模型调用时不是从 PayPal 扣费,而是从 Heicode / NewAPI 的余额或订阅额度扣。 流程: ```text 客户端/网页发起模型调用 -> Manager / NewAPI relay 计算模型费用 -> 根据用户计费偏好选择资金来源 -> 预扣额度 -> 请求上游模型 -> 根据真实 usage 结算差额 -> 成功记录日志,失败则退款预扣 ``` 当前资金来源有两类: | 资金来源 | 代码标识 | 说明 | |---|---|---| | 钱包余额 | `wallet` | 从 `users.quota` 扣。 | | 订阅额度 | `subscription` | 从 `user_subscriptions.amount_used` 扣。 | 用户计费偏好: | 偏好 | 说明 | |---|---| | `subscription_first` | 默认。优先订阅额度,不足或无订阅时回退钱包。 | | `wallet_first` | 优先钱包余额,不足时回退订阅。 | | `subscription_only` | 只用订阅额度。 | | `wallet_only` | 只用钱包余额。 | 因此 PayPal 只负责把钱变成 Heicode 的余额或订阅权益;后续模型费用仍由现有 NewAPI/Manager 计费链路处理。 ## 4. NewAPI / CodeGW 的订阅模式 产品文档里 CodeGW / NewAPI 的定位是模型网关和计费服务。普通用户不进入 NewAPI 后台,只在 Heicode Manager / 客户端看模型、余额、额度、调用日志。 当前代码里的“订阅”不是 PayPal 原生订阅,而是 Heicode 内部套餐: ```text subscription_plans -> 用户购买 -> subscription_orders -> 支付成功 -> user_subscriptions -> 模型调用扣订阅额度 ``` 套餐可配置: | 字段 | 说明 | |---|---| | `price_amount` | 套餐展示价格。 | | `duration_unit` / `duration_value` | 套餐有效期,如月、年、天。 | | `total_amount` | 套餐总额度,0 可表示不限量。 | | `quota_reset_period` | 额度重置周期,如 daily / weekly / monthly / never。 | | `upgrade_group` | 购买后升级用户分组。 | | `max_purchase_per_user` | 单用户购买上限。 | PayPal 接入时有两种做法: | 做法 | 推荐度 | 说明 | |---|---:|---| | PayPal 一次性支付购买内部套餐 | 高 | 最小改动,支付成功后创建 `user_subscriptions`。适合当前系统。 | | PayPal 原生订阅自动续费 | 中 | 需要维护 PayPal subscription 状态和续费 webhook,复杂度更高。 | 建议第一期先实现“一次性支付购买内部套餐”。这样不改变 NewAPI 订阅扣费逻辑,也不要求 PayPal 成为系统账本。 ## 5. Agent 运行费用是否在项目设计文档里 有,但目前文档里表达的是“预算和用量上限”,不是已经完整落地的独立收费账本。 相关设计口径: | 文档口径 | 含义 | |---|---| | `budget.max_tokens` | 本次 Agent / Agent 部署允许消耗的 token 上限。 | | `budget.max_cost_usd` | 本次 Agent / Agent 部署允许消耗的美元成本上限。 | | `budget.max_duration_sec` | 本次 Agent / Agent 部署允许运行的时间上限。 | | `billing_context.provider = newapi` | 表示模型调用费用应映射到 NewAPI 用户、Token、Group 或 quota。 | | `agent_runtime` | 表示子 Agent 角色、模型 profile、实例数,不能和 NewAPI 扣费对象混在一起。 | 产品文档明确:子 Agent 的运行模型属于 Agent 平台部署配置,不等同于 CodeGW 后台模型供应商配置。NewAPI 负责模型网关、余额、用量、日志和扣费;Agent 平台负责真实执行和运行态。 所以当前要分成两类费用: | 费用类型 | 当前是否有闭环 | 说明 | |---|---|---| | 模型调用费用 | 有 | 通过 Manager/NewAPI 的 quota、subscription、usage 日志扣费。 | | Agent 运行预算 | 有字段和展示/回调基础 | deployment payload 和事件里有 budget,但真实成本依赖 Agent Manager / Runtime 回传。 | | Agent 基础设施费用 | 未形成用户账本 | CPU、内存、Pod、运行时资源成本目前不是 Manager 本地自动扣费项。 | 如果后续要把 Agent 运行也收费,需要 Agent Manager 回传真实用量: ```json { "deployment_id": "dep_xxx", "agent_instance_id": "agi_backend_001", "usage": { "model_tokens": 32000, "model_cost_usd": 4.21, "runtime_seconds": 930, "cpu_core_seconds": 1200, "memory_mb_seconds": 2048000 }, "billing_source": "newapi", "correlation_id": "corr_xxx" } ``` 没有 Runtime 真实回传前,Manager 不能把页面上的预算估算当成真实扣费依据。 ## 6. PayPal 接入建议接口 PayPal 官方当前推荐一次性结账使用 Orders v2 API:创建订单后,用户批准,再 capture 订单。订阅自动续费使用 PayPal Subscriptions API 和订阅 webhook。 ### 6.1 钱包充值 新增接口: ```http POST /api/user/paypal/pay ``` 请求: ```json { "amount": 20, "success_url": "https://code.xinghanlab.com/console/topup?pay=success", "cancel_url": "https://code.xinghanlab.com/console/topup?pay=cancel" } ``` 响应: ```json { "message": "success", "data": { "approval_url": "https://www.paypal.com/checkoutnow?token=ORDER_ID", "order_id": "PAYPAL_ORDER_ID", "trade_no": "PPUSR1NO..." } } ``` 回调: ```http POST /api/paypal/webhook ``` 处理规则: 1. 验证 PayPal webhook 签名。 2. 根据 PayPal order / capture id 找到本地 `top_ups.trade_no` 或 provider reference。 3. 确认支付状态为完成。 4. 幂等加锁,避免重复加余额。 5. 将 `top_ups.status` 改为 success。 6. 增加 `users.quota`。 7. 记录充值日志。 ### 6.2 内部订阅套餐购买 新增接口: ```http POST /api/subscription/paypal/pay ``` 请求: ```json { "plan_id": 3, "success_url": "https://code.xinghanlab.com/console/topup?pay=success", "cancel_url": "https://code.xinghanlab.com/console/topup?pay=cancel" } ``` 响应: ```json { "message": "success", "data": { "approval_url": "https://www.paypal.com/checkoutnow?token=ORDER_ID", "order_id": "PAYPAL_ORDER_ID", "trade_no": "SUBPPUSR1NO..." } } ``` 支付成功后复用现有逻辑: ```text CompleteSubscriptionOrder(trade_no, provider_payload, "paypal", "") ``` 最终效果: ```text subscription_orders.status = success -> 创建 user_subscriptions -> 可按 billing_preference 从订阅额度扣模型费用 ``` ## 7. 需要新增或调整的数据字段 最小实现可以复用现有字段: | 表 | 字段 | 用途 | |---|---|---| | `top_ups.payment_provider` | `paypal` | 标识支付提供方。 | | `top_ups.payment_method` | `paypal` | 标识支付方式。 | | `top_ups.trade_no` | 本地订单号 | 本地幂等主键。 | | `subscription_orders.payment_provider` | `paypal` | 标识订阅订单来自 PayPal。 | | `subscription_orders.provider_payload` | PayPal 回调摘要 | 保存脱敏后的回调信息。 | 建议新增字段或配置: | 类型 | 名称 | 说明 | |---|---|---| | Option | `PaypalClientId` | PayPal REST app client id。 | | Option | `PaypalClientSecret` | PayPal REST app secret,保存时不回显。 | | Option | `PaypalWebhookId` | PayPal webhook 验签需要。 | | Option | `PaypalEnvironment` | `sandbox` / `live`。 | | Option | `PaypalCurrency` | 默认 `USD`。 | | Option | `PaypalMinTopUp` | PayPal 最小充值金额。 | | Model const | `PaymentMethodPaypal` | 值为 `paypal`。 | | Model const | `PaymentProviderPaypal` | 值为 `paypal`。 | 如果第二期做 PayPal 原生自动续订,再新增: | 字段 | 说明 | |---|---| | `subscription_plans.paypal_plan_id` | PayPal 端 plan id。 | | `user_subscriptions.provider_subscription_id` | PayPal subscription id。 | | `user_subscriptions.auto_renew` | 是否自动续费。 | | `subscription_orders.provider_order_id` | PayPal order/capture/subscription 关联 ID。 | ## 8. 前端页面变化 钱包充值页: 1. 充值方式增加 PayPal。 2. 点击 PayPal 后调用 `/api/user/paypal/pay`。 3. 成功后打开 `approval_url`。 4. 回到 `success_url` 后刷新余额和充值记录。 套餐订阅页: 1. 购买弹窗增加 PayPal。 2. 点击后调用 `/api/subscription/paypal/pay`。 3. 支付成功后刷新我的订阅。 管理员设置页: 1. 支付设置增加 PayPal 配置块。 2. Secret 类型字段保存后不回显原文。 3. 展示 webhook 地址:`https://code.xinghanlab.com/api/paypal/webhook`。 ## 9. 安全和幂等要求 | 要求 | 说明 | |---|---| | webhook 必须验签 | 不能只信任前端 return URL。 | | 本地订单必须先创建 | 不能收到 PayPal 回调才创建订单。 | | 回调必须幂等 | 同一 `trade_no` 或 PayPal capture id 重复通知不能重复加余额或重复开通订阅。 | | 支付金额必须二次校验 | PayPal 回调金额、币种必须等于本地订单金额、币种。 | | 支付提供方必须匹配 | 防止用其他支付网关回调完成 PayPal 订单。 | | 密钥不进日志 | `client_secret`、webhook 签名、access token 不得写日志。 | | 退款先不自动扣回 | 第一期可只记录 PayPal refund/dispute 事件,由管理员人工处理;后续再做自动扣减。 | ## 10. 当前项目还缺什么 | 项 | 当前状态 | PayPal 接入需要做 | |---|---|---| | 钱包充值闭环 | 已有,非 PayPal | 新增 PayPal provider/controller/service。 | | 内部订阅闭环 | 已有,非 PayPal | 新增 PayPal 购买入口并复用 `CompleteSubscriptionOrder`。 | | 模型费用扣费 | 已有 | 不需要因 PayPal 重写。 | | NewAPI/CodeGW 用户侧余额和日志 | 已有基础 | PayPal 只影响充值入口,不影响模型计费规则。 | | Agent 运行预算 | 有字段和回调基础 | 真实收费需要 Agent Manager 回传真实 usage。 | | Agent 基础设施收费 | 未闭环 | 需要单独设计资源计价规则和 Runtime 用量回传。 | | PayPal 管理配置 | 未实现 | 新增系统设置项和前端配置。 | | PayPal webhook | 未实现 | 新增 `/api/paypal/webhook`。 | ## 11. 建议实施顺序 1. 新增 PayPal 配置项和支付 provider 常量。 2. 实现 PayPal OAuth access token 获取和 Orders v2 create/capture 查询封装。 3. 实现余额充值 PayPal 下单接口。 4. 实现 PayPal webhook 验签和充值订单完成。 5. 实现内部订阅套餐 PayPal 下单接口。 6. webhook 成功后复用 `CompleteSubscriptionOrder`。 7. 前端充值页和套餐购买弹窗增加 PayPal。 8. 增加单测:金额校验、重复 webhook、provider mismatch、订单状态异常。 9. Sandbox 冒烟:创建订单、支付、回调、余额增加、订阅开通、模型调用扣费。 10. 生产上线前配置 live client、secret、webhook id、currency、回调域名。 ## 12. 对外口径 可以这样向上级说明: ```text Heicode Manager 当前已有余额和订阅计费闭环。PayPal 接入不改变模型调用扣费规则,只作为新的收款渠道接入。 用户通过 PayPal 支付后,Manager 将支付结果转换为 Heicode 钱包余额或内部订阅套餐。模型调用仍由 NewAPI/CodeGW 计费链路按余额或订阅额度扣费。 项目设计文档中存在 Agent 运行预算字段,例如 token、美元成本和运行时长上限,但这目前是部署预算和审计约束,不等同于已完成的 Agent 基础设施收费账本。若要对 Agent 运行单独收费,需要 Agent Manager 回传真实模型用量、运行时长、CPU/内存等数据后再纳入扣费。 ``` ## 13. 参考资料 - Heicode 产品资料包:`docs/product-package/` - NewAPI / CodeGW 边界:`docs/heicode-runtime-auth-newapi-secret-design.md` - Agent 请求契约:`docs/integration/agent-platform-request-contract.md` - PayPal Orders v2:`https://developer.paypal.com/docs/api/orders/v2/` - PayPal Subscriptions:`https://developer.paypal.com/docs/subscriptions/reference/` - PayPal Webhook 事件:`https://developer.paypal.com/api/rest/webhooks/event-names`