需求提要
過去,許多軟體採用「前 X 天免費試用」的策略,試用期結束後便自動收費或讓功能失效。然而,隨著 AI 功能的普及,產品端為了控制成本,開始對免費用戶設定 token 使用量上限。這也引發新的思考:既然主要成本來自 AI token,而其他高花費資源(如 CPU、執行個體數量、儲存空間)也能透過技術手段設限,是否能進一步調整策略,推出「永久免費」模式?
這樣不僅有助於 更精準的成本管理,同時也是一種 有效的行銷手段。在執行這項策略轉換的同時,我們也會針對促銷活動與品牌推廣,提供 額外的點數,以吸引新用戶並增加曝光度。
現況
我們目前的 free trial 方案為期 X 天,並在此期間提供使用者 4,000 點數,點數有效期限同樣為 X 天。然而,在檢查資料庫後發現,有部分 free trial 使用者由於帳號建立時間過久,或是當時未完成 email 驗證,系統並未正確儲存 free trial 的過期日期。
對於這些缺少過期日期的舊有資料,我們只能依據 created_at 欄位的時間,推算並補齊其應有的 free trial 期限,以確保後續邏輯與計算的正確性。
隱性需求
我們的 Credits System 採用 transaction table 的設計,因此系統會盡可能保留每一筆交易紀錄,以便日後追溯與還原點數異動的全貌。
在 升級(upgrade)流程 中,系統會產生兩筆紀錄:
- 負值紀錄(negative record):用來抵銷原本剩餘的 credits,並且沿用(繼承)原先的過期日(expiry date)。
- 正值紀錄(positive record):用戶在升級後獲得的新 credits,並帶有新的屬性設定與使用規則。
同時,credits 的消耗(consumption) 也遵循同樣的原則 —— 其 expiry date 會直接繼承被扣除的那筆 credits 的過期日,以確保計算規則的一致性與可追溯性。
然而,這套設計在面對 新功能(尤其是 extra credits)時,可能會產生潛在問題。
由於 升級流程產生的負值紀錄 具有高度的專一性:
- 如果前一筆交易的 reference 屬性是
XX_start
,那麼對應的負值紀錄就必須標記為XX_end
,以確保交易關聯清晰。
但這樣的嚴格對應邏輯,導致新發放的 XX_extra credits(例如:free account 推廣活動的額外點數,或日後其他行銷促銷所需的 credits)無法被正確扣抵,進而影響點數的管理與後續功能的擴充彈性。
解方
原始需求
由於 Free Account 使用者 不應該有過期日,我們需要調整 User 模組的邏輯:
- 對於未付費的使用者,在檢查帳號是否過期時,應直接 跳過過期日驗證。
- 資料庫中的 expiry date 欄位 仍然保留,並且部分程式碼也暫時保留,目的是 減少 regression test 的工作量(尤其是在單元測試 coverage 還未完全覆蓋的 backend 區塊)。
- Free Account 的 Credits 其
expiry_date
需統一改為NULL
,代表這些 credits 為 永久有效。 - 額外提供一支 script,用於發放 extra credits(行銷/推廣用途)。
隱藏需求
當 Free Account 升級為付費帳號 時,還需要執行額外邏輯:
- 先建立一筆
xxx_end
的 consumption record,用於結束 Free Account 時期的點數計算。 - 接著,將該使用者所有
expiry_date = NULL
的 永久 credits,統一更新為升級當下的時間,使這些點數開始計算過期,符合付費帳號規則。
Quote
在進行任何功能調整時,必須同時考量顯性需求與隱藏需求。
忽略隱藏需求,短期看似省時,但後續可能出現一連串預期外的改動:
- 測試時發現沒處理到的邏輯 → 返工加班
- 原本沒列入估算的額外修正 → 工時失控
- 緊急補洞時只能拿肝來換時間,造成團隊壓力
因此,在設計階段就應該 主動盤點所有可能受影響的流程、邏輯與例外情況,並將這些「隱藏需求」提早納入工時規劃,才能避免日後反覆返工、開發節奏被打亂,甚至影響後續的 sprint 排程與團隊士氣。