動態優惠碼
透過專屬連結選擇 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-Hant/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 建立與購買轉換:流量統計
- 設定測試與正式環境驗證資訊:正式環境設定與金鑰