> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aftersell.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 在达到门槛时自动添加免费赠品

> 一个 Cart SDK 用例：当购物车总额超过门槛时添加免费赠品，总额跌破门槛时将其移除。

监控购物车总额，让免费赠品与之保持同步：当顾客的消费超过门槛时添加赠品，跌回门槛以下时将其移除。

<Note>
  如果需要由商家配置、支持层级、按市场规则和进度条的版本，请使用[奖励](/zh/aftersell/cart/rewards-block)区块，无需编写代码。本方案适用于区块无法表达的规则。
</Note>

<div id="the-snippet">
  ## 代码片段
</div>

粘贴到 **Cart settings → Custom script → Initialization**，并设置两个常量：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const FREE_GIFT_VARIANT_ID = 1234567890; // the gift product's variant id
const THRESHOLD_CENTS = 5000;            // $50.00, since totals are in cents

function reconcileGift(state) {
  const gift = state.items.find((line) => line.variantId === FREE_GIFT_VARIANT_ID);
  const qualifies = state.totalPrice >= THRESHOLD_CENTS;

  if (qualifies && !gift) {
    window.aftersell.cart.actions.addItem(FREE_GIFT_VARIANT_ID, 1);
  } else if (!qualifies && gift) {
    window.aftersell.cart.actions.removeItem(gift.key);
  }
}

window.aftersell.cart.events.on('cart_loaded', reconcileGift);
window.aftersell.cart.events.on('cart_updated', reconcileGift);
```

<div id="how-it-works">
  ## 工作原理
</div>

**`reconcileGift`** 描述购物车*应该*处于的状态，然后最多做一次更改来达到该状态：

* 满足条件且赠品不在购物车中 → [`addItem`](/zh/aftersell/cart/sdk-actions#additemvariantid-quantity) 添加它。
* 不满足条件且赠品在购物车中 → [`removeItem`](/zh/aftersell/cart/sdk-actions#removeitemkey) 通过该行的 `key` 移除它。
* 其他情况 → 什么都不做。

它同时在 [`cart_loaded`](/zh/aftersell/cart/sdk-events#cart_loaded)（这样页面加载时已经满足条件的购物车也会获得赠品）和 [`cart_updated`](/zh/aftersell/cart/sdk-events#cart_updated)（这样它会对之后的每一次变更做出反应）上运行。由于 `cart_loaded` 会向较晚订阅者重放，无论你的脚本何时运行，这都能正常工作。

<Warning>
  \*\*`!gift` / `gift` 守卫条件是阻止循环的关键。\*\*添加赠品会触发 `cart_updated`，从而再次运行 `reconcileGift`，第二次执行时会发现赠品已经存在，所以什么都不做。去掉守卫条件就会陷入无限循环。参见[两条规则](/zh/aftersell/cart/sdk-events#the-two-rules)。
</Warning>

请注意，添加赠品会**提高购物车总额**，因此接近商品价格的门槛可能会来回震荡：添加 \$0 的赠品是安全的，但带价格的赠品可能会单靠自己把购物车推过门槛。请让赠品保持免费，或与排除赠品后的总额进行比较。

<div id="adapting-it">
  ## 灵活调整
</div>

**按商品数量而非消费金额判断：**

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const qualifies = state.itemCount >= 3;
```

**按购物车中是否包含特定商品判断：**

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const qualifies = state.items.some((line) => line.productId === TRIGGER_PRODUCT_ID);
```

\*\*仅在特定国家/地区启用。\*\*读取 [`context`](/zh/aftersell/cart/sdk-overview#context)，它在购物车加载之前就可用：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const ELIGIBLE = ['US', 'CA'];

// Only register the handlers for eligible countries.
if (ELIGIBLE.includes(window.aftersell.cart.context.customer_country)) {
  window.aftersell.cart.events.on('cart_loaded', reconcileGift);
  window.aftersell.cart.events.on('cart_updated', reconcileGift);
}
```

**将赠品排除在门槛之外**，使其无法自己撑住门槛：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
const subtotalWithoutGift = state.items.reduce(
  (sum, line) => (line.variantId === FREE_GIFT_VARIANT_ID ? sum : sum + line.finalLinePrice),
  0
);
const qualifies = subtotalWithoutGift >= THRESHOLD_CENTS;
```

**标记赠品，让它看起来像赠品。**[行转换](/zh/aftersell/cart/sdk-hooks#registerlinetransform)可以更改行的标题、变体标题、隐藏状态和内部属性——因此可以给它重新命名：

```js theme={"theme":{"light":"snazzy-light","dark":"github-dark"}}
window.aftersell.cart.hooks.registerLineTransform((line) => {
  if (line.variantId === FREE_GIFT_VARIANT_ID) {
    line.setTitle('Free gift 🎁');
  }
});
```

<Note>
  行转换**无法隐藏某行的数量控件**——它仅有的 setter 是 `setTitle`、`setVariantTitle`、`setHidden` 和 `setInternalProperties`。如果顾客更改了赠品数量，请改为在 `cart_updated` 处理函数中进行纠正。
</Note>

<div id="where-to-go-next">
  ## 下一步
</div>

* **[奖励区块](/zh/aftersell/cart/rewards-block)**：无代码版本，支持层级。
* **[操作](/zh/aftersell/cart/sdk-actions)**：`addItem`、`removeItem` 及其他操作。
* **[事件](/zh/aftersell/cart/sdk-events)**：守卫条件为何重要。
