智能体自助下单,链路比人类点鼠标长得多:它要过应用权限、店铺结账规则、机器人防护、支付风控四道门。任何一道没开,最终看到的报错都长得很像"结账失败"。下面按排查顺序拆开讲。
报错现象
先对照一下,你遇到的是哪一类。
现象一:服务端调接口直接被拒
```
{"errors":[{"message":"Access denied for cartCreate field. Required access: xxx_write_checkouts","extensions":{"code":"ACCESS_DENIED"}}]}
```
或者:
```
{"errors":"Invalid API key or access token (unrecognized login or wrong password)"}
```
HTTP 状态码是 401 或 403,返回体里通常点名了缺哪个权限。
现象二:浏览器智能体卡在页面最后一步
智能体把商品加进购物车、填完地址,点"去支付"时页面出现人机验证、无限转圈,或者被弹回购物车页。抓包能看到 403、429,或者响应头里出现 server: cloudflare 加一个挑战页。
现象三:订单"成功"了又被撤
接口返回成功,订单也生成了,但几分钟内变成 Cancelled,cancel_reason 是 fraud;或者后台把订单标成高风险,客户没收到确认邮件。
现象四:Webhook 收不到,或收到了却返回 401
你的回调服务日志里写着 HMAC 校验失败,或者压根没日志——请求根本没到。
影响范围:如果只有走智能体链路的下单失败,人类正常下单不受影响,问题在接入层;如果人类也下不了单,先去查店铺配置,跟智能体无关。
可能原因
按出现概率从高到低:
1. 应用权限范围(scopes)没开,或 token 类型用错。拿 Admin API token 去调 Storefront 接口,或者反过来,是最常见的翻车点。
2. 访问令牌失效或被轮换。应用被卸载重装、token 手动刷新后,旧 token 立刻作废。
3. 智能体流量被机器人防护拦掉。Shopify 侧和 CDN 侧都可能拦,识别依据通常是 UA、IP、请求指纹。
4. 店铺侧结账前置条件不满足。强制登录、地域限制、B2B 目录、库存不足、没有可用配送方式、货币不匹配,都会在最后一步失败。
5. 支付环节被风控拒绝。银行卡 3DS 未通过、发卡行拒绝,或行为像卡测试(同 IP 短时间多张卡)。
6. API 版本升级导致字段弃用。原来能跑的字段在新版本里改名或下线。
7. Webhook 签名校验实现有误。常见是把原始 body 交给 JSON 中间件解析后再算摘要。
8. 速率限制。并发抢购时触发 429,调用方没读 Retry-After 就硬重试,雪崩。
逐条排查与解决
1. 权限范围与 token 类型
先把变量设好:
```bash
export SHOP="your-store"
export API_VERSION="2025-01" # 具体版本以官方文档当前版本为准
export ADMIN_TOKEN="shpat_xxx"
export STOREFRONT_TOKEN="xxx"
```
查当前 token 实际拥有的权限:
```bash
curl -s "https://${SHOP}.myshopify.com/admin/oauth/access_scopes.json" \
-H "X-Shopify-Access-AI 词典:Token">Token: ${ADMIN_TOKEN}" | jq .
```
怎么判断:返回的 scope 列表里没有写订单、写结账相关的那几项,就是这个原因。改应用配置里的 scopes,然后重新走一次授权安装流程,旧 token 不要继续用。
顺便确认 token 用对了地方。Admin 接口带 X-Shopify-Access-Token,Storefront 接口带 X-Shopify-Storefront-Access-Token,两者不通用:
```bash
Admin 探活
curl -s -X POST "https://${SHOP}.myshopify.com/admin/api/${API_VERSION}/graphql.json" \
-H "X-Shopify-Access-Token: ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":"{ shop { name myshopifyDomain } }"}' | jq .
Storefront 探活
curl -s -X POST "https://${SHOP}.myshopify.com/api/${API_VERSION}/graphql.json" \
-H "X-Shopify-Storefront-Access-Token: ${STOREFRONT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":"{ shop { name } }"}' | jq .
```
Admin 通了、Storefront 报 401,说明是 token 类型问题,不是权限问题。
2. 令牌失效
```bash
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST "https://${SHOP}.myshopify.com/admin/api/${API_VERSION}/graphql.json" \
-H "X-Shopify-Access-Token: ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":"{ shop { name } }"}'
```
怎么判断:返回 401 且报 Invalid API key,而你在应用后台刚做过重装或旋转 token,基本可以确定。去后台重新取 token,并把它写进密钥管理服务,别散落在代码里。
3. 机器人防护
先看智能体的请求头长什么样:
```bash
curl -sI "https://${SHOP}.myshopify.com/" | grep -i -E 'server|cf-|x-shopify|set-cookie'
```
再用一个自定义 UA 试一次:
```bash
curl -s -o /dev/null -w "%{http_code}\n" \
-A "Mozilla/5.0 (compatible; YourAgent/1.0; +https://your-domain.example/agent)" \
"https://${SHOP}.myshopify.com/cart"
```
怎么判断:自定义 UA 返回 403 / 503,而浏览器 UA 返回 200,就是被防护拦了。处理办法分两步:一是给智能体一个稳定的、可追溯的 UA,并在防护规则里放行;二是不要只靠 UA 判断身份,UA 谁都能伪造。
更稳的 Agent 身份校验思路,是在你自己的入口层做,而不是指望平台:
- 给智能体签发短时效的 JWT,随请求带在自定义头里,由你的反向代理校验签名和过期时间;
- 校验时同时看签名、来源 IP 段、UA 三者,任一不匹配就降级为人机验证;
- 校验通过后,把会话标识写进结账属性(cart attributes)或订单备注,这样订单落到后台还能追溯到是哪次智能体会话下的。
注意:结账页面能不能带自定义头,取决于平台当前开放的能力,以官方文档当前版本为准。带不进去就退一步——先调你的服务换一个一次性的结账会话标识,再拼进结账链接。
4. 店铺侧的前置条件
用 Storefront 接口查一次购物车是否可结账,把配送和库存问题提前暴露:
```bash
curl -s -X POST "https://${SHOP}.myshopify.com/api/${API_VERSION}/graphql.json" \
-H "X-Shopify-Storefront-Access-Token: ${STOREFRONT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":"query($id:ID!){ cart(id:$id){ id totalQuantity cost{totalAmount{amount currencyCode}} } }","variables":{"id":"gid://shopify/Cart/xxx"}}' | jq .
```
怎么判断:购物车查得到、金额正常,但结账接口报 Checkout does not exist 或同类错误,通常是购物车/结账会话过期,或者店铺要求先登录。检查后台的客户账户设置、市场与地域设置、配送区域是否覆盖智能体填的地址。如果店铺开了强制登录,智能体必须走客户账号授权链路,否则一定失败。
5. 支付与风控
订单已经生成但被自动取消时,去查风险信息:
```bash
curl -s "https://${SHOP}.myshopify.com/admin/api/${API_VERSION}/orders/${ORDER_ID}/risks.json" \
-H "X-Shopify-Access-Token: ${ADMIN_TOKEN}" | jq .
```
怎么判断:返回里出现高风险等级,或订单的 cancel_reason 是 fraud,就是风控拦的。这一步先别急着改代码,先看是不是行为本身像攻击——同一个 IP 在十分钟内试了五张卡、同一张卡换了三个收货地址,任何风控系统都会拦。这时要做的是加限额,而不是关风控。
限额可以放在 Shopify Flow、Shopify Functions 或者你自己的代理层。核心规则几条就够:
- 单个智能体会话 24 小时内下单笔数上限;
- 单个商品单笔数量上限;
- 同一支付方式/收货地址短时间多次尝试就熔断,冷却期内直接拒绝。
给智能体返回结构化错误,别返回一坨 HTML,否则它会不停重试:
```json
{"error":{"code":"AGENT_ORDER_LIMIT_EXCEEDED","retry_after":300,"message":"会话下单次数已达上限,请稍后再试"}}
```
6. API 版本与字段弃用
固定变量的版本号,跑一次全链路回归。怎么判断:报错里出现 Field 'xxx' doesn't exist on type 或弃用警告,就是这个原因。翻官方变更日志,把字段换成新名字,然后统一升级版本变量,别让线上还跑着一个没人记得的旧版本。
7. Webhook 签名校验
关键点是必须用原始请求体算摘要,且用恒定时间比较:
```js
const crypto = require('crypto');
function verify(rawBody, hmacHeader, secret) {
const digest = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('base64');
const a = Buffer.from(digest);
const b = Buffer.from(hmacHeader || '');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
```
手工核对一次:
```bash
printf '%s' "$RAW_BODY" | openssl dgst -sha256 -hmac "$APP_SECRET" -binary | openssl base64
```
怎么判断:手工算出来的值和请求头里的 X-Shopify-Hmac-Sha256 一致,但代码里校验失败,说明框架把 body 改写了——常见于先 express.json() 再验签。把验签中间件挪到 body 解析之前,用 rawBody 选项保留原始字节。
8. 速率限制
```bash
curl -si -X POST "https://${SHOP}.myshopify.com/admin/api/${API_VERSION}/graphql.json" \
-H "X-Shopify-Access-Token: ${ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"query":"{ shop { name } }"}' | grep -i -E 'retry-after|x-request-id'
```
怎么判断:返回 429,或者 GraphQL 响应里 extensions.cost.throttleStatus.currentlyAvailable 掉到很低。读 Retry-After 再退避重试,指数退避加抖动,别固定间隔猛撞。
都不管用时的兜底方案
降级一:智能体只加购,不结账。 让智能体生成购物车链接,交给人类点最后一下"支付"。风控压力全部转移给成熟的人类结账流程,改造成本也低。
降级二:走草稿订单。 智能体提交的商品、地址、金额先落成草稿订单,后台人工或规则审核通过后再发结账发票。适合高客单价、库存紧张的场景。
降级三:收回写权限。 只给智能体读商品、读库存、读价格的权限,一个写操作都不开。用户体验差一点,但绝不会误下单。
最后一招:找平台支持。 提工单时务必带上出问题的 X-Request-Id 响应头、精确到秒的时间戳、请求体和响应体原文。没有 request id 的工单基本问不出结果。
如何预防再次发生
权限做减法。 给智能体单开一个应用,只申请它真正需要的 scopes,别图省事共用人类结账那套凭据。
版本固定加回归。 把 API 版本写成变量,升级前在沙盒店铺跑一遍完整链路:探活 → 加购 → 填地址 → 结账 → 回调验签 → 查订单风险。
幂等键必须有。 用"智能体会话标识 + 购物车内容哈希"做幂等键,防止网络抖动时重复下单——重复订单本身就是风控的触发条件。
监控四个指标。 智能体订单成功率、风控命中率、退款率、429 占比。任何一个突然跳变,先看接入层再查业务。
订单上留痕。 把智能体会话标识写进订单备注或结账属性。事后处理退款和争议时,能一眼分清哪些是智能体下的单——这类订单的退款处理策略往往和人类订单不一样。
定期复核白名单。 给智能体放行的那套 UA、IP 段、签名密钥,至少按季度过一遍,过期的及时清掉。
把错误信息写清楚。 给智能体返回的错误尽量结构化,带 code 和 retry_after。含糊的错误信息会让智能体做出最糟的选择——无脑重试。
