延保险弹窗
当顾客未在 PP 面板中选择方案便直接点击「加入购物车」时,SDK 可弹出方案选择窗供顾客补选;若顾客在弹窗中取消,主商品仍会照常加入购物车。方案弹窗不依赖购物车;商品页有可用方案时即可打开。
弹窗支持两种触发方式:
| 触发方式 | 调用方法 | 是否受客户经理(AM)后台配置控制 |
|---|---|---|
| 加购时自动打开 | pp.addToCart() | 是(后台设为不弹窗时,未选方案直接跳过保障) |
| 通过自定义按钮主动打开 | pp.showModal() | 否(不受后台弹窗开关控制) |
常规加购不需要为弹窗写任何分支:pp.addToCart() 已包含「读取选择 + 按配置弹窗」,顾客取消时返回 protection: 'skipped'。只有需要提前引导顾客选方案时,才使用 pp.showModal()。
- 已按 接入延保险 完成加购接入。本页仅补充弹窗的触发规则,加购调用本身不变。
- 商品页已传入当前规格调用
loadPlans(variantId)。 - 自定义按钮需要站点提供
showSelectedPlan(plan)之类的函数,用于展示已选方案。
- 常规加购:直接调用
pp.addToCart({ addMainProduct }),回调签名为(attributes, context),不需要再传variantId。SDK 会先等待本规格报价:面板中已有选中方案时直接使用,不弹窗;未选择时按后台配置决定是否打开方案弹窗。 - 处理结果:顾客关闭弹窗取消选择时,结果仍会解析为
protection: 'skipped',主商品照常加入且不带保障行。其他结果与 接入延保险 一致。 - 主动打开弹窗:若页面提供独立的「选择保障方案」按钮,可在点击时调用
pp.showModal()(或pp.showModal(context))。顾客确认时返回PPSelection;顾客取消、关闭或按 Escape 时返回null并清空 PDP 当前选择。没有可用方案时 rejectPP_NO_OPTIONS;等待或弹窗期间切换规格 / 离开商品页时 rejectPP_CONTEXT_CHANGED,不按取消处理。 - 复用已选方案:顾客在弹窗中确认的方案会留在面板状态里,随后的
pp.addToCart()会解析到同一个选择,站点不需要自行保存选择或重写主商品属性。
两个入口都锁定调用时的商品上下文。等待报价期间换规格或离页(包括 A→B→A)会 reject PP_CONTEXT_CHANGED;弹窗打开后任何重新报价都会关闭旧弹窗并以 PP_CONTEXT_CHANGED reject,不会让顾客确认过期价格。取消与技术失败分开:只有顾客主动取消才返回 null 并清空 PDP 选择。
选择互斥从等待报价开始,到确认、取消或失败结束。进行中再次调用 showModal() 或 setSelectedPlan() 会立即 reject PP_SELECTION_IN_PROGRESS,不排队、不叠窗。取消后下次加购仍按后台自动弹窗配置判断。返回的 selection 是独立快照,mainLineAttributes 已是 Storefront 的 AttributeInput[],无需再做格式转换。
下面的示例通过 pp.showModal() 在自定义按钮上提前打开方案弹窗。若只需要「加购时按需弹窗」,跳过本页的自定义按钮,直接使用接入页的 pp.addToCart() 即可:
'use client'
import { xcottonInit } from '@/lib/xcotton'import { showSelectedPlan } from '@/lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
export function ChoosePlanButton() { return ( <button type="button" onClick={() => void onChoosePlanClick().catch(console.error)} > Choose protection plan </button> )}import { xcottonInit } from '../lib/xcotton'import { showSelectedPlan } from '../lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
export function ChoosePlanButton() { return ( <button type="button" onClick={() => void onChoosePlanClick().catch(console.error)} > Choose protection plan </button> )}<script setup lang="ts">import { xcottonInit } from '~/lib/xcotton'import { showSelectedPlan } from '~/lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
function onClick() { void onChoosePlanClick().catch(console.error)}</script>
<template> <button type="button" @click="onClick">Choose protection plan</button></template>import { xcottonInit } from '../lib/xcotton'import { showSelectedPlan } from '../lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
export function ChoosePlanButton() { return ( <button type="button" onClick={() => void onChoosePlanClick().catch(console.error)} > Choose protection plan </button> )}<script setup lang="ts">import { xcottonInit } from '../lib/xcotton'import { showSelectedPlan } from '../lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
function onClick() { void onChoosePlanClick().catch(console.error)}</script>
<template> <button type="button" @click="onClick">Choose protection plan</button></template><script lang="ts"> import { xcottonInit } from '$lib/xcotton' import { showSelectedPlan } from '$lib/cart' async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan) }
function onClick() { void onChoosePlanClick().catch(console.error) }</script>
<button type="button" onclick={onClick}>Choose protection plan</button>import { xcottonInit } from '../lib/xcotton'import { showSelectedPlan } from '../lib/cart'async function onChoosePlanClick() { await xcottonInit() const pp = window.xcotton?.pp if (!pp) throw new Error('[XC] PP is not enabled for this store') // 展示已选方案;随后的 pp.addToCart 会解析到同一个选择。 const selection = await pp.showModal() if (selection) showSelectedPlan(selection.plan)}
export function ChoosePlanButton() { return ( <button type="button" onClick={() => void onChoosePlanClick().catch(console.error)} > Choose protection plan </button> )}| 操作 | 预期结果 |
|---|---|
| 在面板中选好方案后点击加购 | 不弹窗,直接携带保障完成加购 |
| 未选方案直接点击加购(后台配置为弹窗) | 自动弹出方案选择窗 |
| 加购时自动弹窗,点击取消、关闭或按 Escape | pp.addToCart() 仍加入主商品,protection 为 'skipped';PDP 当前选择清空 |
自定义按钮的 showModal() 弹窗,点击取消、关闭或按 Escape | 返回 null,清空 PDP 当前选择,不写车 |
| 取消后再次加购(PDP 仍未选中) | 继续按后台配置决定是否弹窗;本次取消不记录「拒绝」 |
自定义按钮调用 showModal() 并确认方案 | 返回 selection,下一次 pp.addToCart 使用同一个方案 |
| 弹窗打开期间切换商品规格或离开商品页 | 弹窗自动关闭,等待中的调用 reject PP_CONTEXT_CHANGED,不清空新上下文的选择 |
弹窗打开期间再次调用 showModal() | reject PP_SELECTION_IN_PROGRESS,不叠加第二个弹窗 |
| 现象 | 原因与处理方法 |
|---|---|
| 未选方案点击加购时未弹出弹窗 | 客户经理(AM)在后台配置了「加购时不弹窗」。如需开启请联系对方调整配置,或使用自定义按钮调用 showModal() |
顾客在弹窗中选了方案,调用却以 PP_CONTEXT_CHANGED 拒绝 | 弹窗打开期间商品规格发生了变化,或触发了 loadPlans(null)。旧弹窗结果已作废,请按最新规格重新打开 |
| 自定义按钮点击后没有任何变化 | window.xcotton.pp 为空:店铺未开启 PP,或 init() 尚未完成。两者都不依赖购物车 |