联盟链接 vs. 折扣码:为什么在 Shopify 上会冲突
顾客先点了联盟伙伴的推荐链接,结账时又用了弹窗送的折扣码。结果要么同一单既付佣金又打折,要么联盟伙伴白忙一场。这篇文章讲清楚 Shopify 为什么原生管不了这件事,以及真正可行的做法。
这个问题在商家社区里反复出现,场景几乎一模一样。你在跑联盟计划——GoAffPro、UpPromote、ReferralCandy 都行——伙伴分享的是推荐链接,不是折扣码。与此同时,你的营销团队在发折扣码:首单 10% 弹窗、邮件流程里的码、包装卡片上的码。
然后顾客周一点了联盟链接,逛了一圈没买;周三从 Instagram 主页回来,拿到弹窗码,下单了。接下来就是两种坏结局,取决于联盟应用怎么归因:
- 联盟伙伴丢掉佣金:应用把订单记给了最后触达的折扣码,而不是推荐链接。你最卖力的伙伴白干了一单。
- 你付了两份钱:同一笔订单既付了推荐佣金又打了折——而这单本来一个激励就够了。
商家的问题很合理:能不能阻止通过联盟链接进店的顾客使用折扣码? 简短回答:Shopify 没有任何原生设置能做到。但有一套可行的架构,而且 Discount Function API 里一个较新的能力,让顾客的结账体验比以前干净得多。
为什么 Shopify 原生做不到
Shopify 的折扣适用条件是基于这些东西的:客户分组、商品、合集、订单最低金额、与其他折扣的组合规则。它永远看不到一件事——顾客是从哪来的。
推荐来源存在另一个系统里:联盟应用的追踪脚本和 Cookie。折扣码是在结账时输入并校验的,到那一步,结账页根本不知道顾客三天前点过某个推荐链接。任何套餐里都没有一条适用规则叫“来自联盟 X”。
所以解法必须打通两个系统:把推荐状态放进购物车,再让结账逻辑对它起作用。
那些看起来对、其实会反噬的做法
先讲清楚常见绕行方案为什么会制造新问题:
把“用了折扣码的订单佣金设为 0%”。 这是大家最常去翻的联盟应用设置。它确实“解决”了重复付费——代价是悄悄转嫁给了联盟伙伴。伙伴第一次发现自己带来了订单却分文未得,就不会再推你了。账目修好了,关系断了。
用 Cart Validation Function 拦截结账。 技术上确实能拦下订单。但顾客看到的是一个语焉不详的错误,和他的折扣码毫无关系。他理解为“这家店坏了”,而不是“这个码对我不适用”。等着收客服工单和弃单吧。
用自动折扣 Function 把折扣清零。 可以在检测到推荐状态时让 Function 返回零折扣。问题是:码被接受了,但总价纹丝不动。在顾客眼里,就是店收走了他的码、什么都没给他。这可能是最差的结局——码校验通过了,他只会怪你,而且没有任何提示解释原因。
三种做法的共同病根:顾客从来没有被清楚地告知“这个码不能用于推荐链接带来的订单”,而联盟伙伴的佣金被当成了事后才想起来的事。
真正可行的架构
经得起推敲的方案分三步:把推荐状态写进购物车、用 Discount Function 读取它、带着明确的提示信息拒掉折扣码。
1. 把推荐状态写成购物车属性
购物车属性(cart attributes)是跟随购物车进入结账的键值对——关键是,Discount Function 能读到它们。一小段主题脚本就能完成:
- 检测联盟的推荐信号(URL 参数,或联盟应用在顾客落地时写入的 Cookie);
- 用 Ajax API 写入购物车:
POST /cart/update.js,带上attributes: { referral_source: "affiliate" }。
这样,推荐状态就存在于结账逻辑能看到的地方了。
2. 把 Discount Function 挂到自动折扣上
折扣码在 Function 介入之前就会被校验——这就是你无法“修改”别人的码的原因。绕行方法:让 Function 跑在一个你自己创建的自动折扣上(通过 discountAutomaticAppCreate)。这个自动折扣不需要真的减钱,它的作用是给 Function 在结账流程里争取一个席位。
Function 的输入查询读两样东西:第一步写入的购物车属性,以及 enteredDiscountCodes——顾客已输入、但尚未生效的折扣码。
3. 用 enteredDiscountCodesReject 拒掉折扣码
如果推荐属性显示“来自联盟”,而顾客输入了你的营销折扣码,Function 返回一个 enteredDiscountCodesReject 操作,指明这些码,并附上提示信息:
{
"enteredDiscountCodesReject": {
"codes": [{ "code": "WELCOME10" }],
"message": "WELCOME10 不能与推荐订单同时使用——推荐优惠已经是你的最优价格。"
}
}顾客在结账页看到的是一条具体、说人话的解释,而不是莫名其妙的失败。联盟伙伴的佣金不受影响,因为推荐追踪根本没被碰过。没有人被重复付费。
三条约束,直接来自 Shopify 官方文档:
- 只有挂在自动折扣上的 Function 才能拒绝折扣码。 挂在码类折扣上的 Function 做不到。
- 只能拒绝仍然“可拒绝”的码——已输入但尚未生效的。所以 Function 必须在输入时基于
enteredDiscountCodes行动,而不是事后补救。 - 拒绝提示会显示在结账页,多语言销售的店铺要做本地化。
enteredDiscountCodesReject 是随 2026-01 版 API 引入的。这就是为什么关于这个问题的老帖子最后都以“没法干净地解决”收尾——现在可以了。
Cookie 有效期这个坑
有一个决定,跳过了一定会咬你一口:联盟推荐 Cookie 会存留几天甚至几周——取决于你在联盟应用里设置的有效期。如果主题脚本写入的推荐属性一直留着,一个三周前点过伙伴链接的顾客,生日邮件里的码被拒了,完全不知道为什么。
要想清楚再选:
- 按会话生效:只在当前会话检测到推荐时拒绝码(结账后或超时后清掉属性)。被误伤的顾客更少,归因会漏一些。
- 按有效期生效:整个 Cookie 有效期内都拒绝。经济账更干净,但你必须让顾客和联盟伙伴知道这条规则存在。
无论选哪个,写下来,并提前告诉联盟伙伴。一个事先知道“推荐订单不叠码”的伙伴会放心地推;一个从顾客投诉邮件里才发现的伙伴不会。
上线前必须测的边界
- Shop Pay 和加速结账——确认拒绝提示在那里也能正常显示。
- 自动折扣与码的组合——如果你还跑着真正的自动折扣,测一下组合规则。
- 正常路径——非推荐顾客必须能正常使用折扣码。
- POS 和草稿订单(如果用到)——Function 在不同端的表现可能不同。
Agent 在这里的位置
上面的检测与拒绝逻辑是一次性搭建。真正持续的工作是另一件事:盯住那些折扣码和推荐同时命中的订单、发现新的冲突模式(新加入的联盟伙伴、新上线的码活动)、在异常变成月底佣金纠纷之前把它们标出来。
这个监测循环正是店铺运营 Agent 能扛下来的事:它盯着订单和归因,带着证据把冲突摆出来,给出处理建议——而“这两样永远不许叠加”这条规则本身,是你作为店主一次性拍板的事。
本文内容按 Shopify Discount Function 官方文档核实,截至 2026 年 7 月。enteredDiscountCodesReject 要求 API 版本 2026-01 或更高。