LLM 集成指南
本页面向为商家实现 Headless SDK 接入的编码助手(LLM),也供复核这份代码的工程师使用。它说明接入前必须从站点确认哪些事实、以哪份接口为准、代码按什么顺序执行、失败如何判断,以及怎样用浏览器和购物车中的实际结果验收。方法签名、类型和常见错误见 SDK 参考,本页不重复。
这份指南怎么用
Section titled “这份指南怎么用”先读 接入前指引 和 快速开始,再按本页把接入拆成可验证的单元。每次只改一个单元,在浏览器中观察结果后再进入下一个。把下面这段任务说明连同站点信息交给编码助手,尖括号内容由你替换。
实现 Xcotton Headless SDK 接入。文档站根地址:<docs-origin>站点仓库:<repository>框架与渲染方式:<Next.js App Router / Remix / Nuxt / SvelteKit / 其他>当前市场:country=<US>,currency=<USD>,language=<en>店铺能力:SP=<开启或关闭>,PP=<开启或关闭>,SP 适配模式=<cart / checkout / checkoutPlus(文档写作 checkout+),或写“从 getMode() 读取”>购物车读写:<Shopify Storefront API / 站点 BFF>已有购物车 ID 的来源:<首次查询、登录后合并、服务端注入等>站点自有函数:<reloadCartView、写主商品、结账跳转、错误展示>先读文档站的以下路径:- /zh/v2/platform-integration/shopify/headless-sdk-prepare/- /zh/v2/platform-integration/shopify/headless-sdk/- /zh/v2/platform-integration/shopify/headless-sdk-cart-sync/- /zh/v2/platform-integration/shopify/headless-sdk-modes/- /zh/v2/platform-integration/shopify/headless-sdk-api/
约束:- 只调用 window.xcotton 的公开方法;不直接写费用行或保障行。- 每次页面加载只调用一次 init;站点独立写车后 await syncCart。pp.addToCart 的回调只写主商品并返回身份,由 SDK 完成同步。- 不实现或改写 SDK 内部逻辑,不伪造站点函数,不用 mock 数据作为交付。- 只提交实际验证过的步骤,并列出未验证项。
交付:加载与初始化、SP 结账路径、PP 商品页与购物车路径(按店铺开启情况)、失败处理。验收:按《LLM 集成指南》的“验收”一节在浏览器中逐项观察。编码助手不应从旧版本、历史报告或内部注释推断接口。公开方法以 SDK 参考 和交付的 xcotton-types.ts 为准;运行时的适配模式、报价和补购候选分别用 sp.getMode()、sp.getQuote()、pp.getCartQuotes() 读取。
开工前向站点确认
Section titled “开工前向站点确认”| 需要确认 | 从哪里得到 | 对代码的影响 |
|---|---|---|
| 店铺域名与 Storefront Public Access Token | 商家后台、站点环境变量 | 写入 window.xcottonConfig 的 shop 与 publicAccessToken;token 必须能读写购物车 |
当前市场的 country、currency、language | 站点市场解析(country、language)与购物车 cost.totalAmount.currencyCode(校验 currency) | 三个值来自当前站点市场;只有 currency 必须与购物车币种一致,language 只影响界面文案;切换市场要新页面、新配置 |
| SP 与 PP 是否开启 | 商家后台配置;window.xcotton.pp 在 init 后即可检查,window.xcotton.sp 要在首次绑车并同步后检查 | 关闭的业务不生成代码;无车启动时 sp 为空不代表 SP 关闭 |
| SP 适配模式 | 首次购物车同步成功且 sp 存在后调用 sp.getMode() | 决定结账按钮调用 resolveSP() 还是 addSpFee() 与 declineSpFee() |
| 购物车读写路径 | 站点架构与既有接口 | 决定使用默认 adapter 还是自定义 adapter |
| 购物车 ID 何时可用 | 站点购物车查询、登录合并逻辑 | 启动时已知就传给 init({ cartId });晚到则走 syncCart(cartId) |
| 站点自有函数 | 站点仓库 | reloadCartView()、写主商品、结账跳转、错误提示都由站点实现,编码助手不能编造实现 |
| 面板容器放在哪里 | 页面布局 | 容器选择器要同时写进 xcottonConfig 和页面;容器可晚挂载,SDK 在节点出现后挂载面板,抽屉与 SPA 路由切换同理 |
| 验收环境 | 测试店、市场、测试商品 | 真实报价和写车需要可用店铺;生产店铺不做破坏性测试 |
如果某项无法从代码或商家处确认,先把它列为待确认项,不要用默认值猜。country、currency、language 由站点按当前市场提供,并随配置请求下发;PP 报价也使用这些值。SDK 的币种守卫只校验购物车及其金额字段的币种。country 与买家国家不一致不会被 SDK 拦截,但会让配置与报价属于错误市场;language 只影响界面文案,不改变结算币种。
顺序固定:先让 SDK 可用,再让购物车同步正确,最后接 SP 与 PP。每一步都要有可观察结果。
- 先配置,再加载脚本。
window.xcottonConfig必须在sdk.js之前声明。SSR 只输出配置和脚本标签,不在服务端访问window;脚本要在框架 hydrate 前执行。观察:typeof window.xcotton?.init === 'function'。 - 放置容器。SP 容器放在购物车页与抽屉,PP 容器放在商品详情页;选择器与配置一致。观察:
document.querySelector(selector)命中,且 SDK 渲染后容器内有面板。 - 只初始化一次。每次页面加载调用一次
await window.xcotton.init(options),options中按需提供cartId、adapter和onCartChange。启动时已有购物车就传cartId,没有就省略。观察:Promise 成功解析;PP 开启时window.xcotton.pp存在;SP 开启且首次绑车同步成功后window.xcotton.sp存在。命名空间存在本身不能证明同步成功。 - 同步站点写车。站点独立完成加购、改数量、删除、清空或换车后,
await window.xcotton.syncCart(cart.id);同步完成前禁用结账和下一次写车。pp.addToCart已包含同步,其主商品回调与该回调触发的站点事件不要再调用syncCart。观察:返回的Cart与页面一致,onCartChange内只刷新界面。 - 按模式接 SP。
cart与checkout在跳转前await sp.resolveSP();checkout+的两个按钮分别await sp.addSpFee()与await sp.declineSpFee()。任何拒绝都不跳转。观察:购物车出现或移除费用行,结账页金额一致。 - 按场景接 PP。商品页规格变化时
await pp.loadPlans(variantId),离开商品页时await pp.loadPlans(null);加购用pp.addToCart({ addMainProduct })。购物车补购先getCartQuotes(),再用offerProtection()或addProtection(),移除用removeProtection({ mainLineId })。观察:主商品只写一次,保障数量等于同规格同方案的被保主行数量之和。 - 收口失败路径。环境初始化失败重载页面;首次同步失败用
syncCart(cartId);操作冲突等当前操作结束;写入结果未知先读车。观察:失败不跳转、不重复写、不自动重试。
在 SSR 框架中,配置和脚本标签由服务端按当前市场输出,SDK 仍在浏览器执行。容器可以在页面渲染后再挂载,SDK 会观察节点并挂载面板;抽屉晚开或 SPA 路由切换也按同一机制恢复。整页刷新会清空 window,需要重新 init;SPA 路由切换保留已初始化的命名空间。离开商品页时调用 loadPlans(null),不要靠页面卸载代替。
购物车读写路径
Section titled “购物车读写路径”默认 adapter 直接调用 Shopify Storefront API,适合大多数站点。只有购物车读写必须经过站点 BFF、服务端鉴权或风控时,才实现 XcottonCartAdapter。选择依据是站点现有接口,不是偏好。
| 场景 | 选择 | 交付要求 |
|---|---|---|
| 站点已有 Storefront token,购物车直接用 Storefront API | 默认 adapter | 不传 adapter;不要包装一层空实现 |
| 所有读写必须经过 BFF | 自定义 adapter | 实现 getCart、addLines、updateLines、removeLines、updateAttributes;返回完整 Cart |
| 只有部分方法走 BFF | 部分覆盖 | 未覆盖方法回退默认实现;两边必须操作同一购物车、同一行 ID;行 ID 不同则必须完整实现 |
自定义 adapter 必须满足以下要求:
- 返回操作后的完整
Cart,包含全部分页与报价字段,lines.pageInfo.hasNextPage为false。 - 保留
cart.id、行 ID、金额与币种的原值;不换算币种,不把0元改成其他金额。 - 网络、鉴权、限流失败必须 reject;不要把失败变成
null。限流错误至少让status: 429、code: 'THROTTLED'或message中的429/THROTTLED之一成立,并尽量带上retryAfterMs;缺省时 SDK 按 30 秒冷却。 - 行属性省略或传
null表示保留,传[]表示清空,传非空数组表示整体替换;updateAttributes接收完整属性数组。 - 不吞错、不自动重试、不伪造行 ID。测试替身只能用于测试,不能进入站点运行路径。
编码助手不得生成一个只返回假数据的 adapter 来让页面看起来能用。站点没有 BFF 时,使用默认 adapter。
邮包险与延保险路径
Section titled “邮包险与延保险路径”SP 的入口由适配模式决定。模式用 sp.getMode() 读取,不要从页面控件或旧版本推断;返回值 'checkoutPlus' 对应文档中的 checkout+。三种模式的差别见 适配模式、Checkout 适配模式 与 Checkout+ 适配模式。
| 顾客动作 | 调用 | 说明 |
|---|---|---|
购物车面板开关(cart) | SDK 内部写车 | 站点不额外写费用行;结账前仍要 resolveSP() |
点击结账(cart、checkout) | await sp.resolveSP() | 成功结果的 action 为 added、removed 或 skipped;拒绝则不跳转 |
含保护结账(checkout+) | await sp.addSpFee() | 返回 { added: true, extId };不可保时拒绝 |
不含保护结账(checkout+) | await sp.declineSpFee() | 清费并写拒绝标记;不要直接跳转 |
| 展示保障价格 | await sp.getQuote() | 只读;零元报价是有效结果,不可保会拒绝 |
| 打开保障说明 | await sp.showModal() | 需要已挂载的 SP 面板 |
不要在 checkout+ 调用 resolveSP();它没有开关意愿,会以 INVALID_ARGUMENT 拒绝。不要自行读写费用行,也不要用 OpenAPI 重复写同一份保障。
PP 分商品页与购物车两段。商品页的报价与选择不依赖购物车;写保障行需要已同步的购物车。场景细节见 接入延保险 与 购物车加购保险按钮。
- 商品页:
const { context, options } = await pp.loadPlans(variantId)。options是本次报价可提交的候选;context可省略,省略时使用调用开始时的全局上下文,显式传入时不能手工构造或跨商品复用。离开商品页调用loadPlans(null)。 - 加购:
await pp.addToCart({ addMainProduct })。回调只写主商品,接收(attributes, context),返回{ cartId, mainLineId };回调内不要调用会取得购物车权限的 SDK 根方法(如init、syncCart、pp.addToCart),也不要读页面当前规格。顾客取消方案弹窗时仍会加主商品,结果protection: 'skipped'。 - 手动链:
getSelectedPlan()、站点写主商品、syncCart(cartId)、addProtection({ mainLineId, selection })。它与addToCart()二选一,不要在同一流程里混用。 - 购物车补购:先
await pp.getCartQuotes(),按mainLineId匹配行;offerProtection({ mainLineId })由 SDK 弹窗,只有一个方案也弹窗;addProtection({ mainLineId, selection })提交站点自绘列表的原始候选。移除保障用removeProtection({ mainLineId }),传主商品行 ID,不传保障行 ID。
不要用商品名或方案名关联行;不要根据 mainVariantId 推断 mainLineId;不要自己拼 PPSelection 或写 _PlanID。可提交的 PPSelection 来自 SDK 当前报价或选择结果:loadPlans() 与 getCartQuotes() 的 options,以及 getSelectedPlan() 与 showModal() 返回的非空结果。具体规则见 SDK 参考。
错误与写入结果
Section titled “错误与写入结果”SDK 公开方法失败为 XcottonError,包括未初始化时的 SDK_NOT_INITIALIZED。站点函数、sdk.js 加载失败或 window.xcotton 尚未存在时的普通异常不适用该守卫;先检查 window.xcotton,对 SDK 错误用 window.xcotton.isXcottonError(error) 收窄,再按 code 分支,其他错误按普通异常处理。message 只用于日志,不要按文案分支。
| code | 含义 | 处理 |
|---|---|---|
SDK_NOT_INITIALIZED | SDK 环境尚未就绪 | 尚未初始化时调用一次 init;已开始时等待共享的初始化 Promise,并处理其失败 |
SDK_INIT_IN_PROGRESS | init() 仍在进行 | 不要再次 init;等待第一次调用的 Promise 完成,站点应共享同一个 Promise |
SDK_ALREADY_INITIALIZED | init() 已结束或已失败;phase 可能是 ready、environment_failed、sync_failed、busy | 不要再次 init;按 phase 重载页面或 syncCart |
OPERATION_IN_PROGRESS | 另一项购物车操作在进行 | 等当前操作结束,再决定是否同步 |
CART_NOT_BOUND | 首次同步尚未完成 | 取得购物车后 syncCart(cartId) |
CART_CURRENCY_MISMATCH | 购物车币种与初始化 currency 不一致 | 切到当前市场的购物车与配置 |
PP_CONTEXT_CHANGED | 商品上下文已切换或报价被替代 | 用最新 loadPlans() 结果,不重试旧调用 |
PP_SELECTION_IN_PROGRESS | 另一 PP 选择或购买流程在进行 | 等当前流程结束 |
PP_NO_OPTIONS | 没有可用方案,或补购开关关闭 | 不弹窗、不写车;按场景隐藏入口 |
PP_ALREADY_PROTECTED | 主行已有实际保障行 | 刷新购物车,隐藏补购入口 |
SP_NOT_AVAILABLE | 当前购物车不可保,原因在 context.reason | 不跳转;按 recovery 提示调整购物车,或继续不含保护 |
SP_COVERAGE_LIMIT_EXCEEDED | 购物车总额超过保额上限 | 不跳转;按 recovery 提示顾客减少金额,或继续不含保护 |
SP_PANEL_REQUIRED | 没有已挂载的 SP 面板 | 先挂载容器,再调用 showModal() |
ADAPTER_RATE_LIMITED | 购物车请求处于冷却期 | 按 context.retryAfterMs 等待;没有该字段时按 30 秒 |
写车失败不一定没有生效。读取 context.writeOutcome:not_sent 表示请求尚未发送,not_applied 表示明确拒绝且未生效,partial 表示已确认部分写入,unknown 表示无法确认写入结果。partial 与 unknown 不要自动重试;先读车核对。pp.addToCart 的 PP_WRITE_CONFIRMATION_FAILED 还带有 stage、mainProduct、protection、writeOutcome:mainProduct 可能是 added,也可能是 not_sent、unknown 等写入状态;不要把 unknown 说成已加入,也不要把回调未执行说成主商品已成功。
结账只以对应 API 的 Promise 为准。onCartChange 是已确认事实的通知,不是成功信号;它可能在失败后仍因补读成功而触发。无车 init() 成功只证明环境就绪;带 cartId 的 init 成功还包含该车的首次同步。
只使用实际返回的 code、context 与可用的 cause。上游没有给出的细分原因、写入结果或回调执行状态,不要推断,也不要在日志或界面里写成已发送、已成功。
验收以观察到的结果为准:购物车中的行、Network 请求、Promise 的 code 与 context、页面跳转。编译通过、类型检查通过或截图能渲染,都不等于接入完成。
| 检查 | 操作 | 通过标准 |
|---|---|---|
| 加载 | 打开页面,查看 Network 与 Console | sdk.js 成功加载;window.xcotton.init 可用;无未处理异常 |
| 初始化 | 打开页面并让多个组件挂载 | init 只执行一次;重复挂载的组件共享同一个 Promise,不会各自触发 init;sdk.js 只加载一次 |
| 命名空间 | PP 开启时检查 window.xcotton.pp;SP 开启且有购物车时检查 window.xcotton.sp | pp 在 init 后存在;sp 在带 cartId 的首次同步后存在;无车启动时 sp 缺失不代表 SP 关闭,按后台配置判断 |
| 同步 | 站点加购、改数量、删除、清空 | 每次独立写车后 await syncCart(cart.id);pp.addToCart 由 SDK 同步;返回的 Cart 与页面一致;onCartChange 不触发同步循环 |
SP cart、checkout | 拨动开关,点击结账 | 费用行按意愿写入或移除;resolveSP() 成功后才跳转;拒绝时不跳转 |
SP checkout+ | 分别点击两个结账按钮 | 含保护写入费用行,不含保护移除并写拒绝标记;resolveSP() 不被调用 |
| PP 商品页 | 切换规格,选中方案,点击加购 | 面板只展示最新规格;主商品写入一次;有选择时保障数量等于同规格同方案的被保主行数量之和;取消弹窗仍加主商品且 protection 为 skipped |
| PP 购物车 | 读取候选,补购,移除 | 候选按 mainLineId 匹配;单方案也弹窗;取消零写入;移除后主商品保留 |
| 失败路径 | 模拟网络失败、限流、上下文切换 | 不跳转、不重复写;writeOutcome 为 partial 或 unknown 时先读车;冷却期内不重试 |
| 视口 | 桌面与移动宽度各走一遍 | 面板、按钮与弹窗可操作;容器在抽屉和页面中都能挂载 |
最后,把未验证项和限制写进交付说明。真实写车只在测试店验证;不要在生产店铺做清空购物车、重复加购或删除保障等破坏性测试。