# Quefa 代理商技术接入开发文档 版本:v1 生产接入版(2026-09-27) 接口前缀:`/v1`;沙箱与生产使用不同域名、`partner_id`、密钥和数据。 ## 0. 十分钟接入地图 按以下顺序接入,不要跳过资金核验或直接把密钥放进浏览器: | 阶段 | 代理商动作 | 完成信号 | |---|---|---| | 1. 资金核验 | 在工作台提交采购充值,等待平台确认实际到账 | 采购余额大于 0 | | 2. API 开通 | 提交接入用途,审核通过后创建应用密钥 | API 状态显示“已开通” | | 3. 订单联调 | 服务端签名调用商品、下单和查单接口 | 获得 `order_id` 并查到支付状态 | | 4. 交付验收 | 跳转托管充值入口,或服务端调用自建兑换接口 | 充值进入明确终态 | | 5. 回调验收 | 校验 Webhook 签名并按事件 ID 去重 | 测试通知稳定返回 2xx | 生产接口地址为 `https://你的生产域名/v1`,沙箱接口地址由平台单独提供。代理商后端应把地址、`partner_id`、`key_id` 和 `client_secret` 放入环境配置;切换环境时必须整套切换,禁止混用。 可视化开发者中心:`https://你的生产域名/developers`。机器可读规范:`https://你的生产域名/developers/openapi.yaml`。 ## 1. 接入准备 2026-09-27 更新:签名调用前须在工作台完成采购充值到账核实、API 申请和平台审核。预发/演示密钥不免审,未开通返回 403 api_access_required。余额采购按供货价扣余额,平台代收不重复扣;开通后余额用完仍可查询。最新流程见第 16 份指南。 Quefa 为每个代理商开通:`partner_id`、应用 `app_id`、`key_id`、只显示一次的 `client_secret`、可选出口 IP 白名单、商品授权、供货价/销售上限、Webhook 地址与密钥。请勿在浏览器或移动端保存 `client_secret`,签名必须由代理商服务端完成。 调用拓扑必须是“买家浏览器 → 代理商服务端 → Quefa API”。浏览器只能调用代理商自己的后端,不能直接调用 Quefa;完整可运行示例见 `examples/partner-demo/`。 平台收款模式下,买家付款进入 Quefa,平台确认支付和执行零售退款。授权代理也可自行接支付,再使用 Quefa 采购余额支付供货价;此时平台订单 paid 仅表示采购款已扣,客户零售退款由代理自行处理。无论哪种模式,都不向 Quefa 配置代理支付宝私钥。API 凭证与支付凭证独立。自建兑换页、双模式字段与等级申请详见第 16 份开发指南。 金额请求/响应使用两位小数字符串,币种当前为 `CNY`。所有时间为 RFC 3339 UTC;签名时间戳为 Unix 秒。 ## 2. 请求签名 必需请求头: ```http X-Partner-Id: pt_demo X-Key-Id: key_demo_01 X-Timestamp: 1790323200 X-Nonce: 8c86fc93-0830-4ba7-b3f3-c292cf4d83f4 X-Signature: 64位小写十六进制 Idempotency-Key: checkout_20260925_0001 Content-Type: application/json ``` `Idempotency-Key` 对 POST/PUT/PATCH/DELETE 必填,对 GET 为空字符串。将查询参数按“键和值分别 RFC 3986 百分号编码、按键再按值升序、使用 `&` 连接”生成 `CANONICAL_QUERY`,不要将 `+` 当作空格。签名串: ```text METHOD\nPATH\nCANONICAL_QUERY\nTIMESTAMP\nNONCE\nKEY_ID\nIDEMPOTENCY_KEY\nSHA256_HEX(RAW_BODY) ``` 然后计算: ```text signature = lowercase_hex(HMAC-SHA256(client_secret, canonical_string_utf8)) ``` 文档示例的固定测试向量(时间戳、Nonce、请求体与三个示例程序一致)应得到: ```text cb9368071f43126f6f09a327b878a91f8fb3d1a008bda30f37f3ae4709721861 ``` 空请求体的 SHA-256 为 `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`。JSON 必须先序列化一次,签名与 HTTP 发送使用完全相同的字节。服务端允许 ±300 秒;Nonce 10 分钟内不得复用。服务端返回 `X-Request-Id`,报障时请提供该值。 ## 3. 幂等规则 - 同一代理商租户、路由和 `Idempotency-Key`,请求摘要一致:返回首次响应,`idempotent: true`。 - 同键但请求不同:HTTP 409 `idempotency_conflict`。 - 网络超时:使用相同幂等键重试;不要创建新键。 - 建议键由“业务类型 + 代理商订单号/退款号”构成,不含用户隐私。 - Quefa 至少保留幂等记录 7 天,财务动作永久以业务唯一号防重。 ## 4. 商品 `GET /v1/products` 返回当前代理商已授权且可售商品、Quefa 供货价、销售上限、币种和单笔数量上限。不得使用其他代理商看到的商品或价格。 `fulfillment_mode` 只有两种:`direct` 表示付款后进入 Quefa 直接充值;`cdk` 表示付款后由 Quefa 生成 `QF-` 开头的兑换码。代理商不需要、也不会获得底层供应商账号、域名、订单号或原始卡密。 ```json { "data": [{ "product_code": "chatgpt_plus_1m", "name": "ChatGPT Plus 月卡", "supply_price": "110.00", "max_sale_price": "159.00", "currency": "CNY", "max_quantity": 1, "available": true, "fulfillment_mode": "direct" }] } ``` ## 5. 开单 `POST /v1/orders`: ```json { "merchant_order_no": "M202609250001", "product_code": "chatgpt_plus_1m", "quantity": 1, "sale_amount": "135.00", "notify_url": "https://merchant.example.com/quefa/webhook", "metadata": {"cart_no": "C10086"} } ``` 成功返回 `order_id`、支付状态、二维码内容/图片地址、过期时间、价格快照、`fulfillment_mode` 和 `fulfillment_url`。`metadata` 仅存代理商自己的非敏感标识,禁止传 Session、密码或证件信息。 `notify_url` 必须与 Quefa 为该代理商预登记的 Webhook 地址完全一致,不能按订单临时指定任意地址;生产必须使用 HTTPS,HTTP 仅允许隔离沙箱本机联调。 二维码对应 Quefa 自有支付宝收款。代理商只需在自己的页面展示二维码并轮询订单状态,不需要拼装支付宝请求、接收支付宝回调或维护支付密钥。买家付款后,代理商应把买家跳转或引导到 `fulfillment_url`。该地址是 Quefa 面向客户的充值页,不是内部后台,也不会跳转到底层供应页面。 ## 6. 查单与支付 `GET /v1/orders?payment_status=&cursor=&limit=` 分页查询当前代理商订单,默认每页 20 条、最大 100 条。`next_cursor` 为 `null` 表示没有下一页。代理商仍应在自己的数据库保存 `merchant_order_no ↔ order_id` 映射。 `GET /v1/orders/{order_id}` 返回订单价格快照、支付状态和退款金额口径;履约尝试与退款详情分别调用对应接口查询。支付状态: - `pending`:等待付款; - `paid`:支付通道已确认; - `expired`:未付款且已过期; - `closed`:已关闭; - `partially_refunded`:已有部分/补差退款; - `refunded`:可退金额已全部退回。 代理商必须以该接口或验签后的 Webhook 为准,不得以用户页面跳转判断到账。建议前 2 分钟每 3 秒查询,之后逐步降低频率,总时长不超过订单有效期。代理商不需要登录或查询 Quefa 的支付宝后台。 ## 7. 充值履约 推荐流程是由代理商页面展示付款入口;确认 `payment_status=paid` 后,直接打开订单的 `fulfillment_url`。账号 Session、Access Token 或邮箱凭据由客户在 Quefa 页面提交,不经过代理商服务端。 ### 7.1 直接充值 `fulfillment_mode=direct` 时,客户打开 `fulfillment_url`,选择凭据类型并提交。Quefa 加密保存短期凭据、创建异步充值任务并在页面轮询结果。代理商通过 `GET /v1/orders/{order_id}/fulfillments` 或 Webhook 获取 `queued/running/succeeded/failed/cancelled` 状态。 如果代理商经双方安全评审后确实需要服务端代提交,可调用下列接口;普通商城不要使用此方式,以免接触客户敏感凭据。 `POST /v1/orders/{order_id}/fulfillments` 仅允许已支付且未被退款锁定的订单: ```json { "session_data": { "user": {"email": "buyer@example.com"}, "accessToken": "仅示例,禁止使用真实值" }, "customer_confirmed_email": true } ``` 该接口只接受 `direct` 商品,成功受理返回 `fulfillment_id` 和 `queued`。 ### 7.2 Quefa CDK `fulfillment_mode=cdk` 时,付款确认后 Quefa 异步生成 `voucher_code`,格式为 `QF-xxxxx-xxxxx-xxxxx-xxxxx`。代理商可以把该码交付给客户,也可以让客户直接打开订单 `fulfillment_url`;通用兑换入口为 `/redeem`。 客户只能看到并提交 Quefa `QF-` 码。底层卡密在 Quefa 服务端加密保存,兑换成功后立即清除,任何代理商 API、客户页面、Webhook 和日志都不得返回底层卡密。 ### 7.3 状态与失败码 | 失败码 | 建议处理 | |---|---| | `session_invalid` | 让用户重新登录并提交新 Session | | `account_has_subscription` | 更换无有效订阅的账号或联系客服 | | `region_unsupported` | 更换受支持地区账号 | | `payment_blocked` | 联系客服人工核查,不要重复提交同一 Session | | `verification_timeout` | 稍后使用新幂等键重新提交 | | `service_unavailable` | Quefa 自动重试;代理商保持查询 | | `other` | 携带 `request_id/fulfillment_id` 联系客服 | 状态未知时代理商只查单,不要重复创建履约。失败尝试是否允许再次提交由商品策略决定。 ## 8. 退款与补差 `POST /v1/orders/{order_id}/refunds`: ```json { "merchant_refund_no": "R202609250001", "type": "full", "amount": "135.00", "reason": "用户取消" } ``` `type` 为 `full/partial/price_adjustment`。`price_adjustment` 表示补差退款,不改变履约成功事实,但降低代理商销售差价与待结算额。申请状态:`requested/approved/processing/succeeded/failed/rejected/cancelled`。接口只受理,不承诺同步出款;Quefa 运营审核后,使用 Quefa 自有支付宝执行原路退款。代理商不能自行调用支付宝退款;存在进行中或成功履约时可能进入人工复核。 `GET /v1/refunds/{refund_id}` 查询最终结果。退款成功后以 Quefa 支付通道确认金额为准。 ## 9. 账单与结算 - `GET /v1/ledger?from=&to=&cursor=`:代理商可见台账,包含买家向 Quefa 的付款、Quefa 供货价、退款、补差、代理商差价、待结算和结算划转;不返回支付密钥、Quefa 内部真实履约成本和 Quefa 毛利。 - `GET /v1/settlements`:结算单列表。 - `GET /v1/settlements/{settlement_id}`:结算汇总、明细、调整与打款信息。 结算单状态:`draft/reviewing/confirmed/paying/paid/failed`。结算对象是 Quefa 应付给代理商的销售差价,不是代理商向 Quefa 支付货款。封存后的结算单不会因后来退款而修改;相关退款在下一结算期显示为负向调整。若当期为负余额,将结转至后续周期或按合同另行处理。 ## 10. Webhook 事件包括:`order.paid`、`order.expired`、`cdk.issued`、`fulfillment.succeeded`、`fulfillment.failed`、`fulfillment.cancelled`、`refund.succeeded`、`refund.rejected`、`settlement.created`、`settlement.paid`、`webhook.test`。 ```http X-Quefa-Event: order.paid X-Quefa-Event-Id: evt_01... X-Quefa-Delivery: dlv_01... X-Quefa-Timestamp: 1790323200 X-Quefa-Signature: t=1790323200,v1= ``` 签名内容为 `timestamp + "." + 原始请求体字节`,使用独立 `webhook_secret` 做 HMAC-SHA256。先校验时间戳与签名,再以 `event_id` 作为业务去重键、`delivery_id` 作为投递尝试标识,最后返回 HTTP 2xx。建议先持久化后异步处理,5 秒内响应。投递失败按约 1、5、15、60、360 分钟及后续退避重试;代理商仍应定期主动对账。 ## 11. 错误格式与常用错误码 ```json { "error": { "code": "order_not_found", "message": "订单不存在", "request_id": "req_01...", "retryable": false } } ``` | HTTP | 错误码 | 含义/动作 | |---|---|---| | 400 | `invalid_request` | 修正字段,不重试原请求 | | 401 | `invalid_signature` | 检查规范串、密钥和原始 body | | 401 | `timestamp_out_of_range` | 同步 NTP 后重试 | | 409 | `nonce_replayed` | 生成新 Nonce;业务幂等键不变 | | 403 | `ip_not_allowed` | 联系 Quefa 更新白名单 | | 403 | `product_not_authorized` | 代理商未获商品权限 | | 404 | `order_not_found` | 订单不存在或不属于当前代理商 | | 409 | `idempotency_conflict` | 同键请求内容不同,需排查 | | 409 | `invalid_state_transition` | 当前状态不允许该动作 | | 422 | `price_out_of_range` | 售价低于供货价或高于上限 | | 429 | `rate_limited` | 按 `Retry-After` 退避 | | 503 | `temporarily_unavailable` | 使用原幂等键退避重试 | 仅当 `retryable: true` 或 HTTP 429/502/503/504 时自动重试。建议指数退避 1、2、4、8、16 秒并加入 0~30% 抖动;支付/履约/退款未知结果优先查询,不盲目重提。 ## 12. 沙箱与联调验收 沙箱使用 Quefa 提供的模拟支付和模拟充值服务,所有商品、订单、金额与生产隔离。代理商无需准备支付宝沙箱账号,不得把生产 API 密钥用于沙箱。验收项: 1. 正确签名成功;错误签名、过期时间戳、重放 Nonce 均被拒绝。 2. 同幂等键同请求重放一致;同键异请求返回冲突。 3. 商品授权和价格边界正确。 4. 创建订单、模拟支付、查单、Webhook 验签完成。 5. direct 充值成功、Quefa CDK 签发/兑换、Session 无效、服务超时与结果未知场景完成。 6. 普通退款、补差退款、重复退款和退款审核完成。 7. 验证 135/110/10 示例得到代理商差价 15 元。 8. 验证结算后退款只进入下一期负向调整。 9. 使用另一代理商的订单/退款/结算 ID 访问全部失败。 10. 双方保存联调记录、联系人、出口 IP、回调地址和上线回滚方案。 机器可读定义见 `openapi/openapi.yaml`,Node.js/PHP/Python 签名示例见 `examples/signing/`,完整商城联调见 `examples/partner-demo/` 和 `docs/10-代理商沙箱联调指南.md`。