动态优惠码
通过专属链接选择 Stripe Coupon,在首页展示对应优惠,并在创建 Checkout 时自动应用。
Saavo 内置了链接型动态优惠功能。用户打开 /offer/:id 后,服务器会记住这次优惠选择,首页随即展示对应的价格和促销文案;用户购买指定套餐时,系统会自动把预先配置的 Stripe Coupon 带入 Checkout。
当前 config/products.ts 没有配置任何动态优惠,因此功能入口虽然已经存在,但默认不会产生优惠。要使用这项功能,需要先在 Stripe 创建 Coupon,再把 Coupon ID 和公开活动 ID 配置到具体套餐的 couponOffers 中。
这里的“优惠码”不需要用户输入
动态优惠通过专属链接选择,用户不会在网站上手动输入代码。链接中的 id 是网站公开的活动标识;真正提交给 Stripe 的是服务器配置中的 coupon。
工作流程
一次完整流程如下:
用户打开 /offer/autumn-20-off
→ 服务器确认活动存在且尚未过期
→ 写入 30 分钟有效的 HttpOnly Cookie
→ 跳转到当前语言的首页
→ 首页显示该套餐的活动价格和文案
→ 用户创建 Checkout
→ 系统向 Stripe 提交活动对应的 Coupon ID
→ Checkout 创建成功后清除优惠 Cookie项目默认支持以下功能:
| 功能 | 当前行为 |
|---|---|
| 活动入口 | GET /offer/:id,支持语言前缀 |
| 选择记录 | saavo_coupon_offer Cookie,有效期 30 分钟 |
| 首页展示 | 可覆盖指定套餐的价格、标签、卖点和购买区文案 |
| Checkout | 自动应用匹配活动的 Stripe Coupon |
| 到期控制 | 可通过 expiresAt 设置活动截止时间 |
| 无效活动 | 清除旧选择并跳转首页,不显示错误页 |
响应会设置 Cache-Control: private, no-store,避免带有个人优惠选择的页面被共享缓存。
在 Stripe 中创建优惠券
先在 Stripe Dashboard 创建 Coupon,并确定折扣比例或金额、币种、适用产品、持续时间和兑换限制。保存后记下 Coupon ID,例如 coupon_launch_20_off。
这里需要的是 Stripe Coupon ID,不是给用户输入的 Promotion Code。项目只负责把已有 Coupon 应用到 Checkout,不会通过配置自动创建、更新或停用 Stripe Coupon。
网站的活动截止时间与 Stripe Coupon 的有效规则相互独立。即使 expiresAt 已到期,Stripe 中的 Coupon 仍可能继续有效;反过来,Stripe Coupon 已失效但网站活动仍有效时,Checkout 创建会失败。两边必须分别配置并保持一致。
配置动态优惠活动
在 config/products.ts 中找到需要参与活动的套餐,为它增加 couponOffers:
license: {
type: 'recurring',
interval: 'month',
intervalCount: 1,
priceId: 'price_hobby_monthly_replace_me',
salePrice: '$19',
// 其余套餐配置省略
couponOffers: [
{
id: 'autumn-20-off',
coupon: 'coupon_launch_20_off',
expiresAt: Date.parse('2026-10-01T00:00:00Z'),
salePrice: '$15.20',
listPrice: '$19',
discountLabel: { value: '限时 8 折' },
badgeLabel: { value: '秋季优惠' },
valueHint: { value: '活动截止至 2026 年 9 月 30 日' },
},
],
},couponOffers 可以配置多项活动,但每个活动只能属于一个套餐。
必填配置
| 字段 | 说明 |
|---|---|
id | 公开活动 ID,用在 /offer/:id 链接中 |
coupon | 创建 Checkout 时提交的 Stripe Coupon ID |
id 在整个产品目录中必须唯一,最长 64 个字符,只能使用小写字母、数字和单个连字符,例如 autumn-20-off。
同一套餐中的每个动态活动必须使用不同的 Coupon,并且不能与套餐默认的 coupon 相同。配置不符合这些要求时,项目会在启动或构建阶段报错。
可选配置
expiresAt 是毫秒级 Unix 时间戳,表示活动的排他截止时间:当前时间达到该值时,活动立即失效。不配置则长期有效。
活动还可以覆盖以下展示字段:
title、description、features;salePrice、listPrice、discountLabel、billingLabel;savingsLabel、badgeLabel、valueHint;presentation中的购买标题、说明、事实列表、按钮、规格和亮点。
这些字段只改变页面展示,不会修改 Stripe Price、实际折扣或用户最终获得的权限。展示金额必须由你根据 Coupon 规则计算并填写,项目不会自动用折扣比例换算 salePrice。
为了避免活动链接改变产品结构,动态优惠不能覆盖以下内容:
priceId、套餐类型和计费周期;- 角色、权益和试用期;
- 是否推荐、是否默认、是否允许重复购买;
- 套餐启用状态或嵌套的
couponOffers。
创建和投放活动链接
完成配置并部署后,可以直接投放活动链接:
https://example.com/offer/autumn-20-off指定语言时使用相应前缀:
https://example.com/zh-Hans/offer/autumn-20-off访问成功后,用户会被重定向到相同语言的首页。活动 ID 不存在、已过期或格式错误时,系统同样跳转首页,同时清除浏览器中原有的动态优惠选择。
当前配置只有截止时间,没有开始时间。如果活动需要定时开始,应在开始时间再发布链接或部署对应配置,不能通过尚未提供的 startsAt 字段提前安排。
优惠选择何时失效
saavo_coupon_offer Cookie 具有以下属性:
HttpOnly:前端脚本无法读取或修改;SameSite=Lax:允许用户从外部营销链接进入网站;Secure:生产环境仅通过 HTTPS 发送;Path=/:首页和 Checkout 都能读取;Max-Age=1800:选择保存 30 分钟。
服务器每次使用时都会重新核对活动是否仍然存在且未过期,不能通过手工伪造 Cookie 使用未配置的 Coupon。
不同情况的处理方式如下:
| 情况 | 使用的优惠 | Cookie 处理 |
|---|---|---|
| 活动与当前套餐匹配 | 动态活动的 coupon | Checkout 创建成功后清除 |
| 没有活动选择 | 套餐默认 coupon,没有则不自动应用 | 不写入 |
| 活动不存在或已过期 | 回退到套餐默认配置 | 立即清除 |
| 活动属于另一个套餐 | 当前套餐使用默认配置 | 暂时保留 |
| Stripe 拒绝 Coupon,Checkout 创建失败 | 未创建 Checkout | 保留,便于修正后重试 |
优惠是在 Checkout Session 创建成功后被消费,而不是付款成功后才消费。用户进入 Stripe 页面后放弃付款,网站中的优惠 Cookie 也已经清除,但已创建的 Checkout Session 仍保留该 Coupon。
与其他优惠方式的区别
套餐本身可以设置固定 coupon,表示每次购买该套餐都自动使用同一个 Stripe Coupon。动态活动匹配时,会用活动的 coupon 覆盖这个默认值。
config/payment.ts 中的 stripe.enablePromoCodes 控制 Stripe Checkout 是否允许用户手动输入 Promotion Code。只要当前 Checkout 已经带有固定 Coupon——无论来自套餐默认值还是动态活动——项目就不会同时开启 Promotion Code 输入框,因为 Stripe 不允许两种方式同时提交。
选择优惠方式时可按以下原则处理:
- 面向所有购买者的长期折扣:使用套餐级
coupon; - 通过广告、邮件或合作渠道投放专属价格:使用
couponOffers; - 让用户自行输入公开或私发代码:开启
enablePromoCodes,并且不要为该 Checkout 预设 Coupon。
当前限制
动态优惠目前只接入首页产品展示和常规支付 Checkout。OAuth2 授权流程中的升级 Checkout 仍读取套餐默认 Coupon,不会使用 /offer/:id 选择的动态活动。
首页的 JSON-LD 商品结构化数据也仍使用默认套餐价格,不包含针对单次请求的动态展示价格。
项目也没有内置以下能力:
- 自动创建或停用 Stripe Coupon;
- 活动开始时间、领取次数或每位用户的使用次数限制;
- 动态优惠活动管理后台;
- 按活动 ID 统计访问、Checkout 和成交转化。
Stripe 回调同步的交易记录会保存实际折扣金额,但不会把动态活动 ID 作为独立营销归因字段。如果需要活动级报表,应另外设计归因与统计方案,不能只依赖 saavo_coupon_offer Cookie。
该 Cookie 在用户主动打开优惠链接时写入,不受当前 Cookie 同意组件的分类开关控制。上线前应根据实际用途更新 Cookie 政策,并在Cookie 同意管理中核对说明。
上线检查
- Stripe 中的 Coupon 已创建,ID 与
couponOffers[].coupon完全一致。 - Coupon 的折扣、币种、适用产品、持续时间和限制符合套餐规则。
- 活动
id全局唯一,并符合小写字母、数字和连字符格式。 -
expiresAt使用毫秒时间戳,时区和截止时刻已经核对。 - 页面展示价格与 Stripe Checkout 的实际金额一致。
- 活动链接会跳回正确语言的首页,并显示预期文案。
- 购买正确套餐时自动应用活动 Coupon,购买其他套餐时不会误用。
- Checkout 创建失败时不会清除优惠选择,创建成功后会清除。
- 固定 Coupon 与
enablePromoCodes的组合符合产品预期。 - Cookie 政策已说明
saavo_coupon_offer的用途和有效期。
常见问题
接下来
- 配置产品、Price 和 Stripe:计费与套餐
- 核对优惠选择 Cookie:Cookie 同意管理
- 统计 Checkout 创建和购买转化:访问统计
- 配置测试与生产环境凭据:生产配置与密钥