跳转到内容

使用自己的购物车 API

默认情况下,SDK 直接调用 Shopify Storefront API 读写购物车,无需配置 adapter。仅当你的购物车读写必须经过站点自有 API 或 BFF(例如需要服务端鉴权、风控校验,或在写车时执行附加逻辑)时,才需要实现 XcottonCartAdapter 并在 init 时传入。

adapter 包含五个方法,签名均与 Storefront Cart API 一一对应:

方法对应 Storefront Query / Mutation返回值
getCart(cartId)cartCart 或 null
addLines(cartId, lines)cartLinesAdd操作后的完整 Cart
updateLines(cartId, lines)cartLinesUpdate操作后的完整 Cart
removeLines(cartId, lineIds)cartLinesRemove操作后的完整 Cart
updateAttributes(cartId, attributes)cartAttributesUpdate操作后的完整 Cart

其中 cartId 由 SDK 自动传入(取自当前绑定的购物车),adapter 内部无需自行维护当前购物车 ID。

  • 店铺运行的是 SDK v0.3。若需从 v0.2 迁移存量 adapter,请先阅读 从 v0.2 升级到 v0.3。
  • 你的接口支持查询购物车、新增行、更新行、删除行,以及整体替换购物车属性(attributes)。
  • 写入方法返回完整 Cart;getCart 还可以返回 null(字段说明见 Cart)。建议使用 xcotton-types.ts 中的 XcottonCartAdapter 类型对实现做静态类型检查。
  1. 实现上述五个方法,将 SDK 传入的参数原样转发给你的后端接口,并返回操作后的完整购物车。
  2. 在页面启动时通过 init({ adapter, onCartChange }) 注册 adapter(每次页面加载仅注册一次)。即便尚未创建购物车也可先行初始化,PP 商品报价不依赖购物车。
  3. 站点每次写车成功后(包括首次绑定与换车)调用 syncCart(cart.id),并 await 等待完成;SDK 自行判断是首次绑定、换车还是同车刷新。
  4. 在 onCartChange 中仅刷新站点界面,切勿再次调用 syncCart,也不要将其转发为站点写车事件(详见 购物车同步)。

未实现的方法会自动回退到 SDK 内置的默认 Storefront 实现。混用自定义方法与默认实现时需满足以下条件:

  • 所有方法操作的是同一个 Storefront 购物车、使用同一套行 ID,且两边均持有合法凭证。
  • 若你的 BFF 返回的是自定义行 ID(而非 Storefront 原生的 CartLine.id),内置默认方法将无法识别这些 ID,此时必须完整实现所有涉及行操作的方法。
  • 当你实现的自定义方法抛错或 reject 时,SDK 不会回退到默认实现自动重试。
场景规则
新增行使用 merchandiseId(完整的 ProductVariant GID);quantity 可省略,默认为 1
更新行使用当前购物车快照中的 CartLine.id;quantity 与 attributes 至少传入一项。quantity: 0 表示删除,SDK 会把它原样交给 updateLines,你的实现需要按删除处理
删除行优先使用 removeLines
定位目标行切勿按规格 ID 反查行——同一规格可能存在多行,且更新属性后行 ID 可能变化;切勿将更新失败降级为新增行
行属性省略或传 null 表示保留原属性,传 [] 表示清空,传非空数组表示整体替换
购物车属性updateAttributes 总是接收完整的属性数组([] 表示清空)。SDK 内部已合并好业务属性,请直接全量写入,不要再先读后合
属性值校验属性值必须为字符串。重复的属性 key 及非法数量会在请求发出前被 SDK 直接拒绝
场景要求
getCart 查不到购物车返回 null。注意:仅在确认购物车不存在时才可返回 null;空购物车要返回 lines.nodes: [] 的完整 Cart,SDK 对 null 会以 CART_NOT_FOUND 拒绝依赖真实购物车的操作
网络异常、鉴权失败、接口限流必须 reject Promise。切勿返回 null,否则 SDK 会误判为购物车不存在
写入成功返回操作后的完整 Cart。若返回 null 或 undefined 属于违反契约:SDK 会补读一次购物车以恢复状态,但仍会以 ADAPTER_RESPONSE_INVALID 拒绝本次操作
写入失败必须 reject Promise。失败的写请求可能已在服务端部分生效,SDK 不会自动回滚或重放;请关闭你接口层的自动重试
接口限流reject 一个 Error,至少带 status: 429 或 code: 'THROTTLED'(消息含 429 / THROTTLED 也可识别)。带 retryAfterMs(毫秒)时按该时长冷却,否则按默认 30 秒冷却

SDK 会对每次返回的 Cart 执行契约校验。读取返回的 Cart 校验失败时,SDK 会从 adapter 重读一次,重读成功即可恢复;写入返回的 Cart 校验失败时直接拒绝本次操作:

  • cart.id 必须与当前绑定的购物车 ID 完全一致。
  • 购物车总价币种(cost.totalAmount.currencyCode)必须与初始化时的 window.xcottonConfig.currency 一致;默认与自定义 adapter 使用相同规则,不一致时在报价或写车前就被拒绝。
  • 购物车行必须拉取全部分页,满足 lines.pageInfo.hasNextPage === false,且行 ID 不得重复。
  • 金额字段保留 Storefront 原生的 MoneyV2 格式:amount 为十进制字符串,所有金额的 currencyCode 必须与购物车总价币种一致。切勿乘以 100,也不要将 0 元替换成其他金额。
  • 保留 Cart 合同的全部必填字段。报价还会读取配送属性(requiresShipping)、礼品卡标识(isGiftCard)、SKU、标题、vendor、productType、折扣码(discountCodes)及行成本(cost);商品身份(product.id、product.handle)、行 quantity 与 attributes 同样必填。无需返回 v0.2 Ajax Cart 的旧字段。

写入结果按四种状态区分:未发送、明确拒绝且未生效、已确认部分或全部、结果未知。SDK 在能确认时把状态放进错误的 context.writeOutcome。返回 null / undefined 或写入后补页失败时,SDK 只做一次只读补读并拒绝本次操作,不会重放 mutation。

自定义 adapter 自行负责分页,并返回 lines.pageInfo.hasNextPage === false 的完整 Cart。SDK 只校验这份最终快照;跨页车级事实(总价、属性、折扣码)的核对是默认 adapter 补页时的能力,SDK 不会读取自定义 adapter 的中间页。读取快照不完整、分页非法或行 ID 重复时,SDK 会从 adapter 重读一次;这只降低混合快照风险,不代表跨页事务一致。同一购物车的同步与写入由 SDK 串行协调:同步或写入进行中再调用另一个写车入口会以 OPERATION_IN_PROGRESS 拒绝,后到调用不应依赖 adapter 自行排队。

下方示例中,myApi 代表站点的购物车接口模块,示例从 lib/cart.ts 导入它;按你的项目结构调整路径。第二个文件用于替换 快速开始 中的 lib/xcotton.ts,改动是导入 cartAdapter 并在 init 中传入 adapter。

lib/cart-adapter.ts
import type { XcottonCartAdapter } from './xcotton-types'
import { myApi } from './cart'
// SDK 自动传入绑定的 cartId;写入方法返回完整 Cart。
export const cartAdapter: XcottonCartAdapter = {
getCart: (cartId) => myApi.getCart(cartId),
addLines: (cartId, lines) => myApi.addLines(cartId, lines),
updateLines: (cartId, lines) => myApi.updateLines(cartId, lines),
removeLines: (cartId, lineIds) => myApi.removeLines(cartId, lineIds),
updateAttributes: (cartId, attributes) =>
myApi.updateAttributes(cartId, attributes),
}
lib/xcotton.ts
import type { XcottonApi, XcottonCartAdapter } from './xcotton-types'
import { cartAdapter } from './cart-adapter'
import { reloadCartView } from './cart'
const cartChangeListeners = new Set<() => void>()
export function onXcottonCartChange(listener: () => void): () => void {
cartChangeListeners.add(listener)
return () => {
cartChangeListeners.delete(listener)
}
}
// 页面只初始化一次:所有组件共享同一个 Promise 与回调引用。
const handleCartChange = () => {
void reloadCartView().catch(console.error)
// 已确认的购物车事实可能伴随业务同步失败,组件需重新读取候选并处理错误。
for (const listener of cartChangeListeners) listener()
}
let initPromise: Promise<void> | undefined
export function xcottonInit(): Promise<void> {
const sdk = window.xcotton
if (!sdk) return Promise.reject(new Error('[XC] sdk.js has not loaded yet'))
if (initPromise) return initPromise
// 一次性入口:重复调用不会重新初始化;晚到的 cartId 交给 syncXcotton(cartId) 绑定。
initPromise = sdk.init({
adapter: cartAdapter,
onCartChange: handleCartChange,
})
return initPromise
}
// 站点写车后统一交给 SDK:首次绑定、换车、同车刷新都由它判断。
// 先确保会话已初始化,站点不维护绑定身份或同步队列。
export async function syncXcotton(cartId: string): Promise<void> {
const sdk: XcottonApi | undefined = window.xcotton
if (!sdk) throw new Error('[XC] sdk.js has not loaded yet')
await xcottonInit()
await sdk.syncCart(cartId)
}
操作预期结果
将同一规格拆成两行加入购物车,分别更新与删除每次操作仅作用于目标行,不影响另一行
购物车行数超过单页上限返回的 lines.nodes 包含全部商品行,且 hasNextPage 为 false
修改主商品数量关联保障行的数量同步更新
取消某条主行的保障(removeProtection)主商品行保留,该行 _PlanID 被清除、其他属性不变;同组其他主行保障不受影响
cart 模式下清空购物车并同步购物车中不残留费用行
模拟一次写请求响应丢失不产生重复加购,界面不会将失败展示为成功;重新读取购物车后可继续操作
在一次 SDK 同步或写入进行中并行发起另一个写车请求后到请求以 OPERATION_IN_PROGRESS 拒绝,adapter 只收到一次 mutation
默认 adapter 分页读取期间修改购物车属性或总价SDK 丢弃混合结果并有限重读,不会重放写入;自定义 adapter 需自行保证跨页一致
SDK 完成写车后购物车页与抽屉同步刷新,且回调内部的刷新异常得到妥善捕获
错误信息原因
[XC] line.id: duplicate or missing cart lineSDK 要更新或删除的行 ID 不在你返回的购物车中,或在单次请求内重复出现
[XC] updateLines: quantity or attributes required调用更新行时既未传 quantity 也未传 attributes
包含 response does not match bound cart返回的 cart.id 与当前绑定的购物车 ID 不一致
包含 inconsistent currency某处金额的 currencyCode 与购物车总价币种不一致
包含 cart currency does not match the configured market购物车总价币种与 window.xcottonConfig.currency 不一致,被币种守卫拦截
[XC] Cart requests are rate limitedadapter 返回了限流信号(429 / THROTTLED),SDK 已进入冷却,或本次请求正被冷却拒绝
包含 duplicate line 或 duplicate attribute返回的商品行 ID 重复,或同一属性列表中存在重复的 key

完整的接口类型签名见 SDK 参考。