跳到主要内容

05 — 发现 / 商品流 / 订单(三个周边面)

主线(checkout)前面有「怎么知道这家店能用 ACP、怎么选商品」,后面有「下单后订单怎么追踪」。这一章把三个周边面讲清:Discovery、Product Feeds、Orders + Webhook,外加 Cart 一瞥。

5.1 Discovery:无鉴权的预检

问题: agent 拿到一个域名,它怎么知道「这家支不支持 ACP?API 在哪?支持哪个版本?」如果靠「直接 POST 一个 checkout 看它崩不崩」,既浪费请求又难判断(rfcs/rfc.discovery.md:13-21)。

答案: 一个 RFC 8615 风格的 well-known 文档 GET /.well-known/acp.json,无需鉴权、可缓存(rfcs/rfc.discovery.md:94-105)。它解决一个 bootstrapping 问题:只给域名,agent 一次请求就拿到 api_base_url、版本、支持的服务与传输(:73-77)。

文档主要字段(rfcs/rfc.discovery.md:111-163):

字段内容
protocol.version / supported_versions当前版本 + 全部支持版本(按时序,最旧在前)
api_base_urlREST API 基址,agent 拼资源路径
transportsrest / mcp(MCP 传输绑定见 SEP #135)
capabilities.servicescheckout / orders / delegate_payment / carts(+ feeds)
capabilities.extensions支持的扩展(各带 spec/schema URL)
capabilities.intervention_types3ds / biometric / address_verification

Discovery vs 能力协商——别搞混。 这是全章最该记的对照(rfcs/rfc.discovery.md:269-278):

方面Discovery(.well-known)能力协商(POST /checkout_sessions)
范围商家级、稳定会话级、随单变
鉴权不需要需要 Bearer
内容版本/服务/扩展/传输支付方式/handler/介入交集
可缓存可(小时~天)仅本会话
回答的问题「这能用 ACP 吗?API 在哪?」「这一单具体啥能用?」

所以 discovery 不替代内联能力协商——读了 well-known,该带的 capabilities 还得带(:278)。

一个刻意的安全非目标: 当 Seller Platform(如 Stripe)替很多商家托管同一个 well-known 时,文档不得接受或返回 merchant_id——因为它无鉴权,暴露商家身份会让任何人枚举出平台上有哪些商家(rfcs/rfc.discovery.md:52:301-303)。

5.2 Product Feeds:方向反过来的推送

问题: agent 怎么知道「这家店卖什么、该把哪个确切的 item id 传进 checkout」?靠爬网页既脆又拿不到 variant id(rfcs/rfc.product_feeds.md:27-40)。

最反直觉的设计——调用方向是反的。 Product Feed API 由 agent 托管,商家/平台调 agent 来推送目录、回读 agent 当前已知的状态。agent 绝不反过来调商家的 feed 端点(rfcs/rfc.product_feeds.md:155-160:509-512)。这是个「推送(push)」模型,不是「拉取(pull)」。

端点(都在 agent 侧)(rfcs/rfc.product_feeds.md:242-247):

操作方法端点
建 feedPOST/feeds
取 feed 元数据GET/feeds/{id}
取当前商品集GET/feeds/{id}/products
增量 upsert 商品PATCH/feeds/{id}/products

数据模型分两层:Product(分组)含一个或多个 Variant(可购买单元,通常就是传进 checkout 的那个 id)(rfcs/rfc.product_feeds.md:176-202)。两种发布模式:离线全量替换(metadata.json + products.jsonl)或 API 增量 upsert(按 Product.id,漏掉的不变)(:222-255)。

贯穿 feeds 的一条铁律:feed 不是权威。 feed 的价格/库存只是发现期信号;checkout 永远是权威,即使两者不一致(rfcs/rfc.product_feeds.md:204-220:625-630)。agent 必须把 checkout 响应当真相,且不得把 feed 的价/货当作保证。这和 01 章的 authoritative cart 是同一套世界观。

5.3 Orders:在最小订单上「渐进富化」

checkout 完成时只回一个最小订单(id/checkout_session_id/permalink_url)。Orders RFC(rfcs/rfc.orders.md)在它上面加可选字段,回答下单后的问题:东西到哪了、退款了没。

设计哲学是渐进富化(progressive enrichment):所有新字段可选,商家系统支持到哪就加到哪(rfcs/rfc.orders.md:54-79)。

富化后的订单加了(:109-127):line_items[](带三段式数量)、fulfillments[](履约)、adjustments[](退款/退货/争议)、totals[]

两个值得记的精华:

  • 三段式数量模型(rfcs/rfc.orders.md:81-96):ordered(下单时,不变)/ current(当前有效,可因取消/退货下降)/ fulfilled(已履约,递增)。行项状态可由它推导:current==0→removed、fulfilled==current→fulfilled、0<fulfilled<current→partial、否则 processing。状态不是独立存的,是算出来的。
  • total 的语义锁死(rfcs/rfc.orders.md:299-308):type:"total" 永远是 checkout 时的原始扣款额,减退款;退款单独用 amount_refunded 表示。这样 agent 能无歧义地说「您这单 $670.99,已退 $325.16」,而不是把两个数搅在一起。RFC 明确:agent 不应自己拿 total 减 adjustment 去算净额。

fulfillments[] 而非 shipments[],是为了同时覆盖 shipping/pickup/digital 三种履约(rfcs/rfc.orders.md:97-105)。

5.4 Webhook:订单事件怎么推回 agent

下单后,商家通过 webhook 把订单生命周期事件推给 agent 的接收端(spec/2026-04-17/openapi/openapi.agentic_checkout_webhook.yaml)。两个要点:

  • 全量推送:data 必须是完整 Order 对象,不是增量 delta;可选字段尽量带全(rfcs/rfc.orders.md:466-470)。webhook 的 EventDataOrder 直接 $ref 整个 Order schema(:472-477)。
  • HMAC 签名防重放:每个 webhook 带 Merchant-Signature: t=<unix>,v1=<64hex>,签名体是 timestamp + "." + raw_body,算法 HMAC-SHA256;缺失/格式错/时间窗超(推荐 300s 容差)/验签失败 → 回 401(webhook YAML 的 descriptionMerchant-Signature 参数)。

注意一个迁移点:旧的 refunds[] 字段已被移除,统一用 adjustments[](type: refund 等)表达退款/退货/争议(rfcs/rfc.orders.md:479-487)。

5.5 Cart 一瞥:checkout 之前的轻量篮子

Cart(rfcs/rfc.cart.md)是个可选服务,补「还没决定买、只是逛逛加篮子」这个常见场景。它刻意做得轻:无支付、无状态机、无能力协商(:52-60)。

Cart vs Checkout 的边界很清楚(rfcs/rfc.cart.md:71-81):cart 给估算总价(可缺税),checkout 给权威定价;cart 没有 complete,要购买就 GET /carts/{id} 拿内容去建 checkout。一个好用的设计:continue_url 支持购物车在 agent 与人之间「交接」——人在商家网店里建的篮子,agent 能接手结算(:65-67)。

5.6 本章要带走的三句话

  • Discovery 是无鉴权预检(商家级、可缓存),不替代会话级能力协商;平台托管时禁带 merchant_id 防枚举。
  • Product Feeds 的调用方向是反的——agent 托管、商家推送;feed 永远不权威,checkout 才是。
  • Orders 用三段式数量推导状态、把 total 锁成原始扣款额(退款另算);webhook 全量推送 + HMAC 防重放。