跳转到内容

Checkout+ 适配模式

在 checkout+ 模式下,面板应配置为不带开关,仅展示报价。顾客通过点击不同的结账按钮来决定是否投保:点击「含保护结账」调用 addSpFee() 写入费用行,点击「不含保护结账」调用 declineSpFee() 移除费用行并记录顾客放弃保护。两个按钮均由站点负责渲染。该模式没有开关意愿:不读写开关缓存,弹窗也不展示意愿按钮。

  • window.xcotton.sp.getMode() 返回 'checkoutPlus'。
  • 已按 快速开始 完成初始化,且在结账按钮可点击前已完成当前购物车的同步。
  • 站点提供 getCheckoutUrl() 和 showCheckoutError(error),职责与 Checkout 适配模式 相同。
  1. 在页面渲染两个结账按钮:「含保护结账」调用 onProtectionCheckoutClick(true),「不含保护结账」调用 onProtectionCheckoutClick(false)。
  2. 在点击处理函数中调用 await window.xcotton.sp.addSpFee()(含保护)或 await window.xcotton.sp.declineSpFee()(不含保护)。
  3. 只有 Promise 成功后才跳转:
    • addSpFee() 成功返回 { added: true, extId };当前订单不可保(例如购物车为空、金额不在可保区间或超过保额上限)时以 SP_NOT_AVAILABLE 拒绝,context.reason 说明具体原因。不要跳转,提示顾客该订单不适用保障,并让顾客改用「不含保护结账」。需要区分是否超过保额上限时,先调用 getQuote():超额时它以 SP_COVERAGE_LIMIT_EXCEEDED 拒绝并带出金额、额度与币种。
    • declineSpFee() 成功返回 { declined: true }。
  4. Promise 拒绝时不得跳转:网络失败或写车失败意味着结账页的保障状态与顾客选择可能不符,应展示错误并留在当前页。若 code 是 OPERATION_IN_PROGRESS,说明其他购物车操作仍在进行,等它结束后重试,不要把它当作写车失败。
  5. 两个按钮共用同一个互斥标记(如 protectionCheckoutPending),防止写入与移除操作并发执行。

注意:「不含保护结账」按钮同样必须经过处理函数,不能直接跳转。declineSpFee() 会同时移除购物车中残留的费用行并记录「顾客放弃保护」;若直接跳转,顾客此前点击「含保护结账」后留下的费用行将无法被清除。

checkout+ 模式没有开关意愿,不要调用 resolveSP()(会被拒绝并指引改用两个写入口),也不要从占位意愿推断本次选择。

components/protection-checkout-button.tsx
'use client'
import { xcottonInit } from '@/lib/xcotton'
import { getCheckoutUrl, showCheckoutError } from '@/lib/cart'
let protectionCheckoutPending = false
async function onProtectionCheckoutClick(withProtection: boolean) {
if (protectionCheckoutPending) return
protectionCheckoutPending = true
try {
await xcottonInit()
const sp = window.xcotton?.sp
if (!sp) throw new Error('[XC] SP is not ready')
if (withProtection) await sp.addSpFee()
else await sp.declineSpFee()
window.location.assign(getCheckoutUrl())
} catch (error) {
showCheckoutError(error)
} finally {
protectionCheckoutPending = false
}
}
async function protectionPrice() {
await xcottonInit()
const sp = window.xcotton?.sp
if (!sp) throw new Error('[XC] SP is not ready')
const quote = await sp.getQuote()
return quote.totalPrice
}
export function ProtectionCheckoutButton() {
return (
<>
<button
type="button"
onClick={() =>
void onProtectionCheckoutClick(true).catch(console.error)
}
>
Checkout with protection
</button>
<button
type="button"
onClick={() =>
void onProtectionCheckoutClick(false).catch(console.error)
}
>
Checkout
</button>
</>
)
}

示例中的 protectionPrice() 为可选辅助函数,演示如何调用 getQuote() 读取保障价格;是否把它渲染到「含保护结账」按钮由站点决定。

操作预期结果
点击「含保护结账」费用行写入购物车,随后跳转至结账页
订单金额超出可保范围后点击「含保护结账」addSpFee() 以 SP_NOT_AVAILABLE 拒绝,展示提示且不跳转
先点击「含保护结账」,返回购物车后再点击「不含保护结账」购物车中的费用行被移除,随后跳转至结账页
模拟一次写车失败Promise 拒绝,页面展示错误且不跳转
第一个按钮请求未结束前点击另一个按钮忽略第二次点击,仅执行第一个请求
现象原因与处理方法
始终不跳转,addSpFee() 报 SP_NOT_AVAILABLE当前购物车无匹配的 SP 报价(例如购物车为空、金额不在可保区间或超过保额上限)。可先调用 getQuote():它成功即代表报价可用,超保额时以 SP_COVERAGE_LIMIT_EXCEEDED 拒绝
调用 resolveSP() 被拒绝,报 INVALID_ARGUMENT(reason: checkoutPlus has no switch intent)checkout+ 没有开关意愿。两个按钮应分别调用 addSpFee() 与 declineSpFee()
跳转结账后购物车中仍无费用行按钮未调用处理函数便直接跳转,或没有等待 Promise。请确保两个按钮均通过 onProtectionCheckoutClick 处理
报错 [XC] SP is not readywindow.xcotton.sp 在购物车同步期间 SP 初始化成功后挂载;同步失败时也可能已挂载。请在 init / syncCart 成功后启用按钮,不要只看 sp 是否存在