For AI agents: the complete documentation index is available at https://docs.halo.run/llms.txt, the full documentation bundle is available at https://docs.halo.run/llms-full.txt, and this page is available as Markdown at https://docs.halo.run/developer-guide/shop/integration/fulfillment-callback.md.

履约回调

适用范围

本页适用于 Halo 商城版 2.27.0 及以上版本。

订阅订单行不走物流发货,Halo 不会自行判定订阅权益是否已经交付:订单支付后 Halo 通知接入方交付,接入方交付完成后调用 Halo 的接口回执,Halo 据此记账。

  • ① 在订单支付成功后自动发出一次,运营也可以在控制台订单详情页重新发送。
  • ② 只接受订阅订单行。实物行与虚拟商品行沿用原有发货流程,上报会被拒绝。
  • 上报不会创建发货单,也不会发放卡密或数字资源;虚拟交付见商城 / 虚拟交付。

鉴权准备

  1. 在控制台创建一个专用 Halo 用户,并绑定角色「订单发货上报」(role-template-report-ecommerce-fulfillments)。
  2. 用该用户登录用户中心,创建个人令牌,只勾选该角色。
  3. 在接入方系统中配置令牌,请求时携带 Authorization: Bearer pat_xxx。

该角色只包含两项权限:

API 分组资源权限
console.api.ecommerce.halo.runordersget、list
console.api.ecommerce.halo.runorders/fulfillment-reportscreate
先授角色,再签令牌

个人令牌只能申请签发者已有的角色,因此必须先在控制台给该用户授权,再用它创建令牌。请勿把管理员令牌交给接入方服务。

交付请求通知(出站)

订单支付成功后,只要订单中还有未履约的订阅行,Halo 就会投递一次 FULFILLMENT_REQUESTED:

POST {你的回调地址}
X-Halo-Event: FULFILLMENT_REQUESTED
X-Halo-Signature-256: sha256={hex}
X-Halo-Webhook-Id: {uuid}

data.order 与 ORDER_PAID 完全一致,包含订单行列表;订阅行的标识字段见订阅 Webhook。

  • 收到该事件表示「Halo 希望你交付这个订单」,不代表订单已经发货。
  • 该事件在支付后自动发送一次。自动重试与 Webhook 投递记录中的手动重新投递保留原 X-Halo-Webhook-Id;在订单详情页重新发送履约请求会产生新事件和新 ID,不能仅凭投递 ID 避免重复交付。
  • 漏收时可以请运营在控制台订单详情页重新发送,也可以核对订单后直接上报,Halo 不要求必须先收到通知。

发货上报(入站)

POST /apis/console.api.ecommerce.halo.run/v1alpha1/orders/{id}/fulfillment-reports
Authorization: Bearer pat_xxx
Content-Type: application/json

{id} 是订单 ID,即 Webhook 载荷中的 data.order.id,不是订单编号。

字段类型必填说明
items数组否上报的订单行。省略、传空数组或使用空请求体表示上报该订单全部未履约的订阅行
items[].orderItemId数字是订单行 ID,取自载荷中的 data.order.items[].id,必须大于 0
items[].quantity数字是本次交付数量,必须大于 0
externalReference字符串否接入方单据号,最长 128 字符,仅用于审计留痕

请求示例:

curl -X POST \
  'https://demo.halo.run/apis/console.api.ecommerce.halo.run/v1alpha1/orders/10/fulfillment-reports' \
  -H 'Authorization: Bearer pat_1234567890abcdef' \
  -H 'Content-Type: application/json' \
  -d '{"items":[{"orderItemId":1001,"quantity":1}],"externalReference":"SUB-2026-0001"}'

响应 200:

{
  "orderId": 10,
  "orderCode": "ORD-20260917-001",
  "fulfillmentStatus": "FULFILLED",
  "items": [
    {
      "orderItemId": 1001,
      "quantity": 1,
      "appliedQuantity": 1,
      "fulfilledQuantity": 1
    }
  ]
}
字段说明
fulfillmentStatus上报后的订单履约状态:PENDING(还有未履约行)/ PROCESSING / FULFILLED。该状态按订单的全部订单行(实物与订阅)计算
items[].quantity本次上报的数量
items[].appliedQuantity本次实际记账的数量;重复上报已全部履约的行时为 0
items[].fulfilledQuantity记账后该行的累计已履约数量

整单上报(省略 items)时,目标为该订单所有还有剩余数量的订阅行,已经履约完成的行不会出现在响应中;显式上报的行都会出现在响应里,本次未记账的行 appliedQuantity 为 0。

幂等与重试

场景行为
订单行已全部履约,再次上报相同或更少数量(网络重试)返回 200,appliedQuantity 为 0,不重复计数,也不写订单时间线
订单行还有剩余,上报数量不超过剩余数量累加本次 quantity,返回 200;相同参数再次上报仍会计数
订单行还有剩余,上报数量超过剩余数量返回 409,需核对实际交付与已记账数量后再决定是否补报
并发上报同一订单服务端串行化记账,不会超额
部分上报不是幂等操作

quantity 表示本次新增的交付数量,不是累计数量。例如订单行购买了 3 份,首次上报 1 份已经记账但响应丢失,再次上报 1 份会把累计已履约数量变为 2。externalReference 只用于审计,不是服务端幂等键;并发串行化也只能防止超额,不能识别重复报告。

建议:

  • 交付成功后立即上报。超时、断网等结果不确定的情况,不要直接重发原参数;先查询订单,用 items[].fulfilledQuantity 与本地实际交付记录核对,只补报确认尚未记账的数量。
  • 以「订单 ID + 订单行 ID」组织本地交付与上报记录,同一订单行串行处理;分批交付时分别记录每批数量和上报结果,避免重复交付或重复记账。
  • 查询时若前一次上报仍在处理中,或有其他上报方并发写入,暂时读到的数量不能证明首次请求未生效;应先确认请求结果或交由运营对账,不要根据一次查询立即重试。
  • 收到 409 时不要盲目重试,也不要直接把数量改成剩余数量;先核对实际交付与已记账数量。

错误处理

错误响应遵循 RFC 7807(application/problem+json),detail 为可读原因并会随 Accept-Language 变化,请按 HTTP 状态码判断处理方式。

HTTP含义是否可重试
400请求体校验失败(orderItemId 与 quantity 必须大于 0,externalReference 不超过 128 字符)否,修正请求
400订单中没有订阅行否
400items 中同一订单行重复出现否
400orderItemId 不属于该订单否
400orderItemId 不是订阅行(实物或虚拟商品行)否
401个人令牌无效、已撤销或已过期否,更换令牌
403令牌缺少上报角色否,补齐角色
404订单不存在否
409订单未支付或已取消否
409上报数量超过剩余可履约数量对账确认后补报
409并发记账冲突(订单行可能已被超额上报)查询订单后重试

对账与运维

  • 查询订单:GET /apis/console.api.ecommerce.halo.run/v1alpha1/orders/{id}(同一令牌可读),用 items[].quantity、items[].fulfilledQuantity 与 fulfillmentStatus 核对是否还有未履约行。
  • 查看时间线:运营可以在控制台订单详情页看到「请求发货」「接入方上报发货」「订单履约状态已更新」等记录,上报写入的内容包含接入方的 externalReference。
  • 漏收通知:请运营在订单详情页重新发送发货请求,或核对订单后直接上报。
  • 人工兜底:订阅订单行不能在控制台手动发货,只能由接入方上报。
上报之后

上报只会更新订单行的已履约数量、刷新订单履约状态并写入一条订单时间线:不会创建发货单,不会发放卡密或数字资源,也不会触发 FULFILLMENT_SHIPPED / FULFILLMENT_COMPLETED 事件。请以接口响应作为记账结果。