CR #201 » SCS-026_行銷活動Campaign模型程式設計規格.md
SCS-026 行銷活動(Campaign)模型程式設計規格
| 項目 | 內容 |
|---|---|
| 文件編號 | SCS-026 |
| 版本 |
3.9(★ CR-049 真機缺陷修正:① 回沖業務日——reversed.v1 payload 增可選 postingDate(不升版)+refundCommandJson 帶 postingDate+rewards 缺值 fail-closed 略過(不以 occurredAt 反推,§7.5①);② 回沖欄位層級一致化——消費端一律「recognition 優先、payload 次之」,回沖判定以 txType=REFUND 為權威(原僅讀 payload.refund → 恆 false → 把退款當入點);③ 線格式——producer value-serializer 必須 StringSerializer(JsonSerializer 二次編碼 → eventType=null 靜默略過),已納入架構關卡 A9。★ 真機 E2E 32/32 通過(載體無關/比率+上限截斷/回沖)。前版 3.8(★ §7.16 排除項參數化實作完成:基準僅 PURCHASE(與原行為一致)+exclusion_json 之 includeTxTypes/excludedMccs 覆寫;★ BEAM PMO 裁定「未宣告 clawback_rule → 不回沖」已凍結。前版 3.7(★ §7.15 疊加 D9 與優先序實作完成:campaigns.priority(V7;越小越優先,預設 100)+NON_STACKABLE 擇一(取 priority 最小並排除其他)/全 STACKABLE 全套用;契約加 priority。前版 3.6(★ §7.14 退款/沖正回沖實作完成:posting.transaction.reversed.v1 消費 → 以 reversalOf 反查原指令(JSON_VALUE)→ CLAW_BACK 才回沖(未宣告=不回沖,保守不扣客戶)→ 回沖指令(refund:true)+ cap 用量退還(GREATEST)→ rewards 既有退款分支;ledger 活動歸屬寫入)。前版 3.5(★ R-3b 定案並實作(BEAM PMO 裁定採 (a)):PointsAccountView.tierRank(序之權威在 rewards,QUALIFICATION_SPEND 升冪;停用不佔序;查無 null)+條件鍵改 minLevelRank(數值序——★ 更貼近 BRD §11「Level 2 起 3%」原文)+TierLookupPort.TierInfo;campaign RateResolverTest 7 例+rewards 真機 2 例(序/停用排除)全綠)。前版 3.4(★ §7.13 條件式比率凍結定案並實作:R-1② campaign_plans.benefit_rate_json 承載方案權益率/R-2 生日權威=客戶主檔(外部依賴,已盤點本地無)/R-3 rewards 查詢 API+SAD-002 §5 ACL 加 campaign/R-4 授權側新增結構化 pay_path(不接受 UNKNOWN)/R-5 rateReason 稽核留痕;條件 schema 四鍵 AND)。前版 3.3(★ §7.12 新增「條件式比率待凍結清單 R-1~R-5」——供 BEAM PMO 逐條裁定;每條含 BRD 依據/實查現況(R-1 表已存在/R-2 生日來源未備/R-3 Level 在 rewards 且無 ACL/R-4 授權側無結構化 wallet/R-5 稽核留痕)/待裁定項/凍結後落點。裁定前有條件檔維持 fail-closed)。前版 3.2(★ §7.11 活動回饋率接線+精確上限截斷(真機通過):無條件基準檔判定(BRD 例 1 之 0.5%)→ applicableRate 隨指令下發 → rewards 依比率計點;有條件檔 fail-closed(語意未凍結);精確截斷=「已用量+推定價值>上限」;Oracle JSON 欄比較陷阱(Java 端判定)。campaign 46 例/rewards 159 例全綠)。前版 3.1(★ CR-047 cap 定案並實作(真機通過):定義/用量兩表分工+通用 scope/period 維度+回饋價值(分)記帳+點值商品參數(兩側皆參數)+事前截斷/事後回填;PER_STATEMENT/PER_HOUSEHOLD_MEMBER fail-closed;§3 #17 補 PER_HOUSEHOLD_MEMBER。campaign 43 例/rewards 158 例全綠)。前版 3.0(★ §7.9 cap 設計提案+錄取查詢端點+AC 11 載體無關性驗證:① cap 兩落差實查(campaign_caps 無 household 維度/cap_value_minor vs 點數換算)——待 BEAM PMO 裁定後實作,不臆造;② 新端點 GET /campaigns/{code}/enrollments/{householdId}(含 accruedAmountMinor,契約重生 106 endpoints);③ 載體無關性服務內判定力測試(同戶實體+虛擬跨載體累計合併→一次性指令)。campaign 全測 40 例 BUILD SUCCESSFUL)。前版 2.9(CR-046:新增 §7.5 活動認列輸入契約(設計提案,待 BEAM PMO 核定)——認列來源唯一化、欄位權威映射、歸戶語意更正(household_id ≡ accountId,權威 CC_PARTY.ACCOUNTS)、payPath 權責邊界(認定器屬 campaign、認定規則待 BEAM PMO 核定)、AC 11 驗收前置條件)。前版 2.3(★ 依 BRD-005(客戶提供之台灣信用卡行銷活動 BRD+Business Spec,v1.3,2026-10-16)全面改寫;前版 1.0 僅基於 BEAM PMO 口述六項政策。★ 2026-10-16 BEAM PMO 核定) |
| 狀態 |
★ 已核定(BEAM PMO 2026-10-16)——據以執行 CR-046(campaign-service 新建+BR-16 載體抽象同批上線) |
| 對應需求 |
BRD-005 BR-01~BR-16(16 條)、CR-045 D3(行銷活動軸治理) |
| 權威來源 |
BRD-005(客戶業務單位開立之需求,白紙黑字,取代口述政策成為權威語意) |
| 關聯文件 |
BRD-005、CR-045、SCS-005(產品)、SCS-006(費率/年費)、SCS-010(紅利點數)、SCS-013(清算)、SCS-023(商品組合)、SDD-001 §3、SDD-005、SDD-006
|
本版差異(2.0):★ 從「六項口述政策」升級為「BRD 16 條需求驅動」。
前版 1.0 只涵蓋 BEAM PMO 2026-10-15 口述六項(年費/兌換/滿額禮/疊加/回沖/預算);
BRD 揭示的真實範圍遠大於此——九大維度、18+ 參數化要素、10 個新實體、
9 個信貸結構差異、12 條驗收標準、10 個驗算範例。本版依 BRD 重新界定
參數化邊界、實體模型、服務權責分工與驗收基準。⚠️ 核心設計命題(BRD §11):「每檔活動由參數化要素組成,禁止以程式碼寫死」。
本規格的成敗判準只有一句:「上架新檔只需填參數,不需改程式」(BRD 驗收標準 1)。
1. 對應需求(BRD-005 全 16 條)
| BR | 名稱 | 優先 | cardcore 承接服務(權責) |
|---|---|---|---|
| BR-01 | 活動規則模型 | P0 | campaign-service(新建) |
| BR-02 | 交易認列與排除引擎 | P0 |
campaign-service(規則)+ posting-service(事件供給) |
| BR-03 | 回饋計算與上限截斷 | P0 |
campaign-service(規則判定:門檻/資格/疊加/上限之「算不算」——依 CR-045 D3 釐清)+ rewards-service(點數計算與帳本) |
| BR-04 | 點數帳本(累積/效期/兌換/回沖) | P0 | rewards-service(權威,SCS-010) |
| BR-05 | 正附卡歸戶與合併 | P0 |
card-service(歸戶主檔)+ campaign-service(規則參數) |
| BR-06 | 錄取(登錄)管理 | P1 |
campaign-service(名額原子控管+波次) |
| BR-07 | 新戶首刷禮/滿額禮 | P1 |
campaign-service(門檻)+ rewards-service(發放) |
| BR-08 | 年費減免引擎 | P1 | pricing-service(權威,SCS-006) |
| BR-09 | 額度事件(調額邀約) | P2 |
limit-service + card-service
|
| BR-10 | 卡種/卡組織規則包 | P1 |
product-service(SCS-005/SCS-023) |
| BR-11 | 報表與成本稽核 | P1 |
report-service(SCS-017) |
| BR-12 | 方案與任務(Plan/Quest)引擎 | P1 | campaign-service |
| BR-13 | 支付路徑認定(Pay Path Classifier) | P0 | posting-service 或 campaign-service(認定器,規則表驅動) |
| BR-14 | 通路插件掛載(Channel Adapter Registry) | P0 | product-service 或獨立 channel-service |
| BR-15 | 信貸促銷引擎(利率/費用型回饋) | P1 |
pricing-service(利率)+ campaign-service(活動) |
| BR-16 | 載體抽象(實體卡/虛擬卡號/Token) | P0 |
card-service(INSTRUMENT 主檔)+全服務交易鍵升級 |
⭐ 權責原則(沿用
CR-045D3 治理):計算權威留原服務(rewards 管點數帳本、pricing 管年費與利率、limit 管額度);
campaign-service只做「活動定義、資格、疊加、上限、歸戶單位、錄取、指令下發」,
不接管計算、不持有點數餘額、不定義利率。這是「最大參數化」不變成「巨型泛型表」的關鍵紀律。
2. 服務職責與邊界
2.1 campaign-service 承作(活動引擎)
- 活動主檔與版本化(同
product-service參數版控模式:effective_from/effective_to) - BR-01 全要素之儲存、驗證與版本鏈
- 資格判定(卡別/卡種/客群/新戶定義/正附卡/歸戶單位)
- 疊加互斥(
stackable+exclusion_group+priority) - 上限截斷(每卡/每戶/每期/每月/每年/每歸戶)
- 錄取管理(自動/登錄、波次、名額原子控管、規則型拒絕碼)
- 方案(Plan)與任務(Quest)引擎
- 指令下發:呼叫 rewards(發點)、pricing(年費/利率)、limit(調額)、statement(帳單列示)
2.2 不做(屬他服務,計算權威)
| 不做 | 屬誰 | 依據 |
|---|---|---|
| 點數累積/效期/兌換/回沖/負點 | rewards-service |
BR-04、SCS-010
|
| 年費判定與折抵 | pricing-service |
BR-08、SCS-006
|
| 利率減碼與試算 | pricing-service |
BR-15、SCS-006
|
| 額度佔用/臨調 | limit-service |
BR-09、SCS-002
|
| 交易入帳、分錄、請款 | posting-service |
BRD §3.2 反列 |
| 卡別/卡種/卡組織主檔 | product-service |
BR-10、SCS-005
|
| 歸戶/正附卡主檔 | card-service |
BR-05、SCS-003
|
| 帳單產出與列示 | statement-service |
BR-02 消費到回饋時序 |
3. BR-01 參數化要素全表(★ 本規格之主契約)
鐵則:下表每一欄都是資料,不是程式分支。新增活動 = 新增一列,不改程式、不需發版。
| # | 要素 | BRD 出處 | 型別 | 說明與取值 |
|---|---|---|---|---|
| 1 | campaign_code |
§11 | string PK | 活動代碼(唯一) |
| 2 | activity_period |
§11 | 區間 | 起迄日(含) |
| 3 | apply_period |
§11(信貸 D1) | 區間? | 申請期間(信貸側,與撥款截止分離) |
| 4 | disburse_deadline |
§11(D1) | date? | 撥款截止日(常跨年) |
| 5 | recognition_basis |
§11 | enum |
TXN_DATE|POST_DATE|APPLY_AND_DISBURSE(雙錨點) |
| 6 | eligibility |
§11 | JSON | 卡別/新戶/等級/正附卡;信貸側「全新=往來未達 6 個月(卡/貸/房貸合計)」 |
| 7 | enroll_mode |
§11 | enum |
AUTO|REGISTER
|
| 8 | enroll_windows |
§11 | JSON[] | 波次(起迄+名額);JCB 型 1/11/21 三波 |
| 9 | enroll_quota |
§11 | int? | 名額;原子控管,禁超發 |
| 10 | attribution |
§11(D3) | JSON? | 進件歸因:廣宣連結/專屬代碼/專員欄位/第三方專頁 |
| 11 | channel_condition |
§11 | JSON[] | 指定通路清單(MCC 或 channel_id) |
| 12 | threshold |
§11 | JSON | 單筆/累計金額、次數;多檔門檻(SOGO 型) |
| 13 | threshold_scope |
§11/例5 | enum |
PER_TXN|ACCUM|PER_STORE_PER_DAY(每館每日擇一) |
| 14 | reward_form |
§11 | enum |
CASH|POINTS|MILEAGE|GIFT|LOTTERY|RATE_CUT|FEE_WAIVE
|
| 15 | rate_schedule |
§11 | JSON[] | 條件式比率:依方案/生日月/Level/支付路徑/期間分層 |
| 16 | cap |
§11 | JSON[] | 上限組:每卡/每戶/每期/每月/每年/每歸戶 各自可配 |
| 17 | cap_unit |
§11/例1 | enum |
PER_CARD|PER_HOUSEHOLD|PER_STATEMENT|PER_MONTH|PER_YEAR|PER_HOUSEHOLD_MEMBER(★ CR-047 R5 更正:DDL V1 原即六值,本表原漏列) |
| 18 | exclusions |
§11/§14.3 | JSON[] | 排除項清單;可按活動覆寫(加值事件可翻轉為資格來源) |
| 19 | pay_path_limit |
§11/§6B | enum[] |
PHYSICAL|INTL_3PAY|BANK_OWN|THIRDPARTY|不限 |
| 20 | stacking |
§11/§6C | JSON |
stackable+exclusion_group+priority+疊加模式(併行/擇優/互斥) |
| 21 | payout_schedule |
§11 | string | 當期/次期/N 工作天(AMEX 型 5–150 天) |
| 22 | household_unit |
§11/§14.1 | enum |
PRIMARY(歸正卡)|CARD(單卡獨立,JCB 型) |
| 23 | cost_share |
§11(D4) | JSON[] | 成本承擔方+比例(銀行/通路/卡組織) |
| 24 | external_dependency |
§11/§14.4 | JSON[] | 外部事件依賴(票證加值檔/支付歸屬檔/商店代號檔) |
| 25 | clawback_rule |
§11(例9) | enum | 提前清償回沖:CLAW_BACK|NO_CLAWBACK
|
| 26 | quest_rule |
§11(BR-12) | JSON? | 任務進度規則(子任務、集章、抽獎隊伍) |
| 27 | plan_ref |
§11(BR-12) | FK? | 綁定方案(CUBE 型權益方案) |
| 28 | draw_pool |
§11(D8) | JSON? | 抽獎獎池(券別×名額×中獎上限) |
| 29 | status |
§3.3 | enum | 活動生命週期狀態 |
| 30 | rule_version |
§5 | int | 規則版本(樂觀鎖) |
★ 「最大參數化」的判準:上表 30 欄全部可經由 API/UI 設定;
任一欄需要改程式才能變,即為參數化缺口,須回報為缺陷(BRD 驗收標準 1)。
4. 領域模型(設計提案)
4.1 聚合根
| 聚合 | 職責 | 不變式 |
|---|---|---|
Campaign |
活動定義+規則版本鏈 |
effective_to >= effective_from;stacking 必填(無預設值,BRD D9);cap 至少一筆;clawback_rule 於信貸型必填 |
Enrollment |
錄取紀錄(BR-06) | 名額不得超發(原子遞減);同戶同波次唯一;拒絕碼必落 log |
Plan |
權益方案(BR-12) |
benefit_rate+level_req;valid_from/to 不可重疊 |
Quest |
任務(BR-12) | 進度單調;prize 與 Campaign.cap 連動 |
4.2 值物件與列舉
RecognitionBasis、RewardForm(7 值)、CapUnit(6 值)、PayPath(5 值)、
StackingMode(STACKABLE/NON_STACKABLE)、HouseholdUnit(PRIMARY/CARD)、
ReversalPolicy(點數里程回沖 vs 贈品打呆帳)、ClawbackRule、CampaignStatus。
4.3 狀態機
stateDiagram-v2
[*] --> DRAFT
DRAFT --> ACTIVE: approve
ACTIVE --> PAUSED: pause
PAUSED --> ACTIVE: resume
ACTIVE --> CLOSED: close
PAUSED --> CLOSED: close
CLOSED --> [*]
4.4 錄取狀態機(BR-06,BRD §12.2 原圖照錄)
stateDiagram-v2
[*] --> 未登錄
未登錄 --> 已登錄 : 錄取成功
未登錄 --> 錄取額滿 : 名額已滿
已登錄 --> 資格符合 : 達成消費門檻
已登錄 --> 已失效 : 逾期或停卡
資格符合 --> 回饋處理中 : 結帳日計算
回饋處理中 --> 回饋完成 : 發放入帳
回饋處理中 --> 部分回沖 : 退款
回饋完成 --> [*]
部分回沖 --> 回饋完成 : 差額補正
已失效 --> [*]
錄取額滿 --> [*]
⚠️ 併發硬規格:名額以原子計數控管(DB 條件更新或 Redis 原子遞減);
禁超發(BRD 驗收標準 7:100 併發搶 10 名額,成交恰 10)。
拒絕碼須落 log 供客訴追溯(US8)。
5. 資料模型(設計提案,DDL 待補 Flyway)
| 表 | 關鍵欄位 |
|---|---|
CC_CPG.CAMPAIGNS |
campaign_code(PK)、activity_from/to、apply_from/to、disburse_deadline、recognition_basis、enroll_mode、reward_form、stacking(JSON)、household_unit、clawback_rule、status、rule_version
|
CC_CPG.CAMPAIGN_RULES |
campaign_code(FK)、rule_version、rule_json(§3 全 30 欄之版本快照) |
CC_CPG.ENROLLMENTS |
campaign_code、wave_no、household_id、instrument_id、status、enroll_ts、quota_seq、attribution_code、source_channel
|
CC_CPG.CAMPAIGN_CAPS |
campaign_code、cap_unit、cap_value、consumed(原子遞減/遞增) |
CC_CPG.PLANS |
campaign_code、plan_code、benefit_rate(JSON)、level_req、valid_from/to
|
CC_CPG.QUESTS |
quest_id、campaign_code、rule_json、progress_json、prize
|
CC_CPG.PAY_PATHS |
auth_code、path_type、wallet_id、classified_by(規則表版本) |
CC_CPG.CHANNELS |
channel_id、adapter_type、path_type、status(BR-14 插件掛載) |
CC_CPG.REWARD_CMDS |
cmd_id、campaign_code、target_service、payload、status(指令下發+outbox) |
載體抽象(BR-16):交易最小鍵由
card_no升級為instrument_id(實體卡/虛擬卡號/Token 同構)。
既有CRD_CARD等以視圖對接,不要求重寫——這是侵入性最高的一項,須單獨評估(見 §8)。
6. 計算範例即驗收基準(BRD §15 十例)
| 例 | 場景 | 關鍵參數 | 預期 |
|---|---|---|---|
| 1 | 一銀星璨 0.5%+指定 1%、每戶每期上限 500 |
rate_schedule×2、cap=[PER_HOUSEHOLD,PER_STATEMENT,500]
|
300 元刷卡金 |
| 2 | 紅利 20 元 1 點、當期門檻 3,000、效期 2 年 |
rate_schedule、threshold、exclusions
|
160 點入正卡帳;2 年後清零 |
| 3 | JCB 悠遊加值 10%、月上限 50 |
external_dependency=autoload、cap=[PER_CARD,PER_MONTH,50]
|
截斷 50 元 |
| 4 | AMEX 週五滿 2,000 送 100、月上限 200 |
threshold、cap
|
截斷 200 元 |
| 5 | SOGO 滿額禮「每館每卡每日擇一」 |
threshold_scope=PER_STORE_PER_DAY、多檔門檻 |
90,000→1,500;他館 6,000→另計 100 |
| 6 | 富邦分期滿額贈 200,000→10,000、限量 3 |
enroll_quota=3、enroll_mode=REGISTER
|
前 3 名得;第 4 名無 |
| 7 | CUBE 樂饗購 3.3% 支付路徑分級 |
pay_path_limit、rate_schedule
|
實體 330 點/LINE Pay 30 點/分期 30 點 |
| 8 | 華航鼎尊條件式累哩(生日月 x3) |
rate_schedule 分層、期間 |
同額差 3 倍;條件可追溯還原 |
| 9 | eLOAN 首期 0%+第 2 期減碼+提前清償 |
reward_form=RATE_CUT、clawback_rule
|
依 clawback_rule 回沖或 0 元 |
| 10 | Whatever Pay 新通路插件 |
CC_CPG.CHANNELS 新增一列+adapter |
不改引擎即納入指定通路 |
⭐ 這十例就是我們的自動化驗收案例(TRR),逐例可在真 Oracle 上以參數建立並驗算。
7. 關鍵邏輯
7.1 活動套用流程(BRD §11 原流程)
1 交易認列(recognition_basis)→ 2 排除項檢查(可按活動覆寫)
→ 3 錄取/資格 → 3b 支付路徑認定(BR-13,規則表驅動)
→ 4 門檻判定(單筆/累計/每館每日)→ 5 回饋計算(條件式比率)
→ 6 上限截斷 → 7 入帳/發放 → 8 退款回沖(可生負點)
7.2 疊加規則(BRD D9:必填、無預設)
stacking ∈ {並行, 擇優, 互斥} + exclusion_group + priority。
並行=雙重回饋;擇優=取優先序高者;互斥=同群僅一筆生效。
7.3 信貸側九差異(D1–D9)之落地
雙日期(apply_period+disburse_deadline)、雙錨點(recognition_basis=APPLY_AND_DISBURSE)、
進件歸因、利率/費用型回饋、全新客戶定義(跨產品線往來月數)、條件交換
(綁約換手續費、MyData 換開辦費)、動用排他(期間動用筆數檢核)、抽獎(draw_pool)、
併用宣告(疊加必填)。
7.4 fail-closed
取規則失敗(連線/4xx/5xx)→ 不套用該活動、不臆造放行,記 WARN 並落拒絕碼(沿用 §7.14 A4 模式)。
7.5 活動認列輸入契約(CR-046 B4;★ BEAM PMO 2026-10-16 已裁定,見各項標註)
⚠️ 消費端程式於本節②③裁定落定後開工(
GOV-003)。
① 認列來源(唯一):posting.transaction.posted.v1(SDD-006 §4.2;入帳為交易之權威落點)。
不另訂閱授權事件作認列依據(授權可能不出現請款;認列以 recognition_basis 錨定入帳日)。
| 契約欄位 | 事件欄位 | 權威來源 | 語意 |
|---|---|---|---|
instrumentId |
payload.instrumentId |
CC_CARD.INSTRUMENTS(載體主檔) |
AC 11 之載體最小識別;null=升級前歷史交易(不參與同構對照) |
merchantId |
payload.merchantId |
入帳請求(可追溯至授權) | 通路/特店歸因;null 容忍 |
mcc |
payload.mcc |
授權紀錄(CC_AUTH.AUTHORIZATIONS.mcc) |
指定消費類別判定;null 容忍 |
accountId |
payload.accountId |
CC_PARTY.ACCOUNTS |
即歸戶主鍵(見 ②) |
amount/currency
|
payload.amount/currency
|
入帳交易 | 門檻累計基礎(最小單位:分) |
txType/postingDate
|
payload.txType/postingDate
|
入帳交易 |
REVERSAL/沖正不計正面累計;認列日以 postingDate 為準(不用 occurredAt 反推業務日)。★ 本期可認列類別=僅 PURCHASE(唯一無爭議之消費類別)——分期/退款回沖/其他類別之認列與排除規則(BRD §6B/排除項參數)尚未實作,不臆造;其餘類別略過並記錄(fail-closed) |
② 歸戶語意(★ BEAM PMO 已裁定:自然人成立;法人經查證同構):ENROLLMENTS.household_id = 歸戶主鍵 = accountId。
權威:SCS-004 歸戶模型——本系統之「歸戶」即 CC_PARTY.ACCOUNTS(Account 聚合根:歸戶聚合根;
/api/v1/accounts 即歸戶 REST;正附卡為同戶之 parties[],共用 cif_no)。
★ 法人公司戶(BEAM PMO 2026-10-16 提問,已實查):
CC_PARTY.ACCOUNTS.account_type之 CHECK
為IN ('PERSONAL','CORPORATE')(V1 baseline 即有;AccountType枚舉一致);BRD-005 §3
持卡人分類亦明列「正卡/附卡/公司戶」→ 法人公司戶與自然人同構:公司戶本身即一個歸戶
(一個accountId,account_type='CORPORATE'),員工附卡為該歸戶之parties[]。
故household_id ≡ accountId對 PERSONAL 與 CORPORATE 皆成立,不需要第二套歸戶鍵。
公司戶之活動差異(例:BRD-005AMEX 企業卡除外)屬活動排除參數(exclusions),
不是歸戶模型問題。BEAM PMO 已確認自然人部分成立;法人部分依本查證同構適用。(更正:
#A-253曾載「householdId 不得以 accountId 代替」——該判斷係當時未查 party 模型之過度保守,
經實查SCS-004/JdbcAccountRepository後更正。HouseholdUnit枚舉仍保留,表達錄取門檻之歸戶單位類型。)
③ payPath(BR-13):★ BEAM PMO 已同意規則表對照方向;尚有一個輸入缺口
- 認定器屬 campaign-service(規則表
campaign_pay_paths驅動,auth_code→path_type;fail-closed)。 -
值域有 BRD 依據:
PHYSICAL/INTL_3PAY/BANK_OWN/THIRDPARTY四類即 BRD AC 8
「實體/三 Pay/自家/第三方四路徑分級」與 BRD §6B 分級事實(DDL CHECK 同值域)。 -
★ 已定位之輸入缺口(實查授權側):分類需「wallet 識別」(哪一家三 Pay/第三方條碼),
惟授權側現僅有自由格式emvDataJSON(V10接線之entry_mode/emv_data),無結構化
wallet 欄位 → 種子規則可依 BRD §6B 立(實體卡=PHYSICAL;entryMode為錢包類且 wallet 屬
Apple/Google/Samsung Pay=INTL_3PAY;本行自家 Wallet=BANK_OWN;LINE Pay/街口等=
THIRDPARTY),wallet 識別之權威欄位須由 authorization 契約補強(另立增量),補強前
認定器僅能對PHYSICAL(非錢包 entryMode)作可靠分類,其餘落UNKNOWN——fail-closed,不猜。 - 不在 posting 事件上加 payPath(分類是活動規則職責,非帳務權威)。
④ 消費者技術約束(承 SAD-004 §5.4 慣例):
分區鍵=aggregateId;冪等=eventId 去重(consumed_events 型投影);亂序=sequenceNo 略過;
舊事件缺欄(instrumentId/merchantId/mcc 為 null)不得阻斷消費——僅活動參與判定之欄位缺失時落「資料不足」拒絕碼,不臆造。
⑤ AC 11(B4)驗收前置條件(全部滿足方可執行真機驗收):
- 本節②③裁定已落定(★ 2026-10-16 達成:自然人確認+法人同構查證;payPath 方向同意);
- campaign 消費者+錄取寫入(
ENROLLMENTS)+回饋指令(REWARD_CMDS)實作完成且真 Oracle 測試通過(★ 已達成,§7.6); - 至少一個活動以真 Oracle 參數建立(BRD §15 之例);
- 同戶以實體卡/虛擬卡兩載體產生同額交易,回饋結果逐分一致(Token 路徑依 BRD AC 11 範圍);
- wallet 識別欄位之授權側契約補強(③缺口)——僅影響 payPath 維度之活動,不阻擋 AC 11 之載體同構驗證。
7.6 指令派發與 rewards 消費(★ 實作完成,真機通過)
① REWARD_CMDS 即指令 outbox:本表自帶派發狀態機(PENDING → SENT,V1__baseline.sql),
不重複經 outbox 表轉運(避免雙表狀態同步)。
TransactionRecognitionService 於錄取之同交易寫入 REWARD_CMDS(PENDING);
RewardCommandRelay(@Scheduled,批次 100、間隔 2s,退避由排程週期決定——CSP-003 §7 E5)
掃 PENDING → 發布 → 成功即標 SENT。
② 事件契約:主題 cc.campaign.reward.commanded;事件 campaign.reward.commanded.v1;
分區鍵=commandId(同指令有序);信封依 SAD-004 §5.1;
payload = {commandId, campaignCode, recognition:{...§7.5① 之事實...}}。
SENT = 已派發至事件流,非「rewards 已入點」(消費確認屬 rewards 職責)。
③ rewards 消費端(CampaignRewardCommandListener,SCS-010 §7.4):
-
計算權威在 rewards:
points = floor(amount/100) × 帳戶層級 earn_multiplier(SCS-010 §7)——
指令僅載事實,不含計算結果(CR-045D3)。 -
冪等:
accrue以POINTS_LEDGER.IDEMPOTENCY_KEY = campcmd:<commandId>判定——
relay 之至少一次投遞重放安全;同指令重放回前次結果。 -
到期日錨點:以
postingDate(認列日)起算CUR效期,不用occurredAt。 - ⚠️ 活動加成比率(BR-01
rate_schedule)尚未實作——本期入點為標準層級倍率;
活動加成不得臆造,加成規則實作前不得宣稱「活動回饋已完整」。
④ 錄取語意:認列驅動之錄取寫 status='REGISTERED'、wave_no=0(本期無波次管理);
QUALIFIED 及其後狀態須待門檻判定實作——不得由本鏈路逕行宣告合格。
7.7 名額原子控管與登錄 API(CR-046 AC 7;BRD BR-06「禁超發」)
① 原子性設計(V1__baseline.sql 之 DDL 註解即本節依據——「以條件更新原子遞減」):
-
campaign_quotas之CHECK (quota_used <= quota_total)為最後防線; - 應用層採
SELECT … FOR UPDATE(行鎖)+條件更新兩段式:讀quota_used(鎖列)→
檢查< quota_total→UPDATE SET quota_used = quota_used + 1 WHERE quota_used = <舊值>; -
quota_seq= 更新前之quota_used + 1(第 N 個名額);波次窗口(window_from/to非空時)
須涵蓋登錄日; - 併發安全:行鎖內無窗口;CHECK 為 DB 層最終防線(應用層破防也擋得住)。
② 登錄 API:POST /api/v1/campaigns/{campaignCode}/enrollments(僅 REGISTER 模式):
- 活動不存在 →
CC-CAM-4040;非ACTIVE或非REGISTER→CC-CAM-3001(AUTO 活動不接受登錄); -
冪等:同
(campaign_code, wave_no, household_id)已有錄取 → 回前次結果(uq_enroll自然冪等); - 額滿 →
CC-CAM-1004(422;語意來自SDD-006 §3.4,BR-06); - 取號成功 → 錄取寫入(
REGISTERED+quota_seq);
併發同戶撞uq_enroll→ 退回名額(quota_used - 1)並回前次結果(補償,不得吃掉名額)。
③ 仍缺(下一增量):門檻判定(REGISTERED → QUALIFIED)、上限截斷(BR-03)、排除項、疊加計算、
活動加成比率(rate_schedule)——本節僅解 AC 7 之「禁超發」與登錄入口。
7.15 疊加規則(D9)與活動優先序 ★ 實作完成
依據:
BRD-005D9(stackingMode必填、無預設值——CC-CAM-1002)。
① 優先序參數:campaigns.priority NUMBER(4) DEFAULT 100 NOT NULL(V7)——數值越小越優先;
未指定為 100。實查原表無此欄(多個不可疊加活動同時命中時無從擇一)。
② 判定(TransactionRecognitionService):
- 取「期間內且
ACTIVE」之活動; - 其中若有
NON_STACKABLE→ 擇一:取priority最小者,
並排除其他所有活動(含STACKABLE——NON_STACKABLE=不得與任何活動併用); - 全為
STACKABLE→ 全部套用(疊加)。
③ 契約:CreateCampaignRequest.priority(可選)/CampaignView.priority。
7.16 排除項(參數化)★ 實作完成
依據:
BRD-005 §11「排除項:預借現金、年費、稅、學費、小額支付平台、全聯、分期特店、退款…」;
「排除項清單可按活動覆寫(含加值事件翻轉)」。
① 基準(未覆寫時):僅 PURCHASE 納入——其餘 txType(CASH_ADVANCE/FEE/INTEREST/
PAYMENT/REFUND/REVERSAL/ADJUSTMENT/CHARGEBACK/REWARD_REDEEM/INSTALLMENT)皆排除。
此與 §7.5① 之原行為一致(原即只認 PURCHASE),故無行為變更風險。
② 活動覆寫(campaign_exclusions.exclusion_json,本節定案 schema):
| 鍵 | 語意 | 例 |
|---|---|---|
includeTxTypes |
納入之額外交易類別(覆寫基準;BRD「可按活動覆寫/翻轉」) | ["INSTALLMENT"] |
excludedMccs |
排除之 MCC(活動自訂;代碼權威在收單側,由活動定義者填入) | ["5942","8211"] |
- 判定:
txType ∈ {PURCHASE} ∪ includeTxTypes且mcc ∉ excludedMccs→ 認列;否則略過(記 INFO)。 -
mcc 缺席(舊事件/非授權來源):僅在活動宣告
excludedMccs時無法判定 → fail-closed 略過
(不臆造);未宣告excludedMccs則不受影響。 - ⚠️ 本節 schema 為工程定案(
BRD-005未給exclusion_json欄名);如與客戶既有參數契約不符,
以CR調整。
7.14 退款/沖正之回沖(clawback)★ 實作完成
依據:
BRD-005 §11(排除項含「退款」)、例 9(利率型必填clawbackRule)、SCS-026 §3參數 #18。
來源事件:posting.transaction.reversed.v1(payload:postingId(沖正交易)/reversalOf(原 postingId)/
amount/reason/postingDate*(★CR-049:沖正交易之入帳日=業務日錨點))
——生產端PostingEventFactory.reversedEnvelope。
⓪ 業務日(★ CR-049 真機揭露後補強):原 reversed.v1 payload 恰 4 欄、無任何業務日,
消費端只能拼 null + "T00:00:00Z" → 解析失敗 → 回沖在真機從未成功(單元測試因手工餵入正確 JSON 而全綠)。
修正:生產端增可選 postingDate(不升版)+消費端缺值即 fail-closed 略過(不拼字串、不以 occurredAt 反推)。
① 判定與動作:
-
反查原指令:以
reversalOf對campaign_reward_cmds之結構化欄位posting_id(V8)反查
→ 得原活動/歸戶/載體/原金額;查無 → 略過(該筆未經認列,無可回沖)。
★ 實作決定:V8__reward_cmd_lookup_columns.sql加posting_id/household_id/amount_minor
+索引——不依賴JSON_VALUE(JSON 函式之相容性與可索引性皆不理想;銀行稽核要求可查、可索引)。 -
回沖條件(
clawback_rule):-
CLAW_BACK→ 發回沖指令(command_json帶"refund":true+originalPostingId+postingDate); -
NO_CLAWBACK→ 不回沖(活動明示不追回); - ★ 未宣告(null)→ 不回沖(
BRD-005僅要求利率型必填;其餘型別未宣告時
保守不扣客戶點數)——★ BEAM PMO 2026-10-17 裁定:維持「不回沖」(已凍結)。
-
-
上限用量退還:回沖時同步退還 cap 用量(
consumed_minor = GREATEST(consumed_minor - X, 0);
CHECK consumed_minor >= 0為防線),業務日採沖正交易之入帳日(CR-049)。 -
冪等:
consumed_events以eventId去重(同一沖正事件僅回沖一次)。
② rewards 側:回沖判定以 txType=REFUND 為權威(refund 旗標為輔)→
AccrualRequest.isRefund=true → 既有退款分支(ADJUST +負值、自 CUR 桶扣回)——復用既有權威路徑,不新增計算。
★
CR-049欄位層級一致性:relay 一律把command_json包進payload.recognition;
故消費端對refund/originalPostingId/postingDate一律「recognition優先、payload次之」
(與txType/accountId/instrumentId同一慣例)。原實作僅讀payload層 →refund恆false
→ 把退款當一般入點(方向錯誤),為真機 E2E 揭露之實質缺陷。
③ 部分退款:reversed.v1 之 amount 為沖正金額;本期以原指令之金額為回沖基準
(部分退款之按比例回沖待 SCS-010 定義,見 §8)。
7.13 條件式比率凍結定案(R-1~R-5,BEAM PMO 2026-10-17 裁定)★ 實作完成
BEAM PMO 逐條裁定:R-1② 由
campaign_plans.benefit_rate_json直接承載方案層權益率;
R-2 生日權威=客戶主檔;R-3 由 rewards 提供查詢 API(需改SAD-002 §5ACL);
R-4 授權側新增結構化欄位,不接受以UNKNOWN交差;R-5 指令加rateReason。
① 條件 schema(本節定案,campaign_rates.condition_json)——單層物件,多鍵為 AND:
| 鍵 | 語意 | 值域 | 來源(實查) |
|---|---|---|---|
planCode |
方案 | campaign_plans.plan_code |
★ R-1②:方案層權益率改由 campaign_plans.benefit_rate_json 承載(schema:{"rate": <number>});condition_json.planCode 僅用於選檔
|
birthMonth |
生日月 |
1–12
|
客戶主檔(外部;本地 CC_PARTY.ACCOUNTS/PARTIES 無生日欄——實查) |
minLevelRank |
最低層級序(★ R-3b 定案:BEAM PMO 裁定採 (a)) | 整數(1 起算;呼應 BRD §11「Level 2 起 3%」) |
rewards API PointsAccountView.tierRank(序之權威在 rewards,以 QUALIFICATION_SPEND 升冪)——SAD-002 §5 ACL 已加 cc-campaign-service
|
payPath |
支付路徑 |
PHYSICAL|INTL_3PAY|BANK_OWN|THIRDPARTY(BRD-005 §6B/AC 8) |
★ R-4:授權側新增結構化 pay_path 欄位(V13)→ posting 事件帶出 |
② 判定順序(§7.11① 擴充):
- 有條件檔逐檔比對:全鍵滿足者為候選;取候選中最高 rate;
- 無條件基準檔(
condition_json={})為底線; - 方案層權益率(
campaign_plans.benefit_rate_json.rate,planCode命中時)優先於條件檔; - 三類皆無 → 不帶
applicableRate(rewards 走標準層級倍率)。
③ rateReason(R-5,稽核留痕):command_json.rateReason(可選)——
格式 BASE|PLAN:<planCode>|LEVEL>=<tier>|BIRTH:<m>|PAYPATH:<v>,多條件以 + 連接
(支援 BRD-005 §12「條件式比率覆核:實際達成條件與筆數」之月報)。
④ 缺值處理(R-4 之「不接受 UNKNOWN」):
- UNKNOWN = 猜測為未知類別 → 禁止(不臆造);
- 授權側欄位補強後,若仍缺值(如舊事件、未帶欄)→ 該條件視為不滿足(記 WARN,
不套用該條件檔)——即「不判定」而非「猜成 UNKNOWN」。 - ★ 與 R-4 裁定不衝突:裁定要求的是補強來源使其可得,本節即完成授權側結構化欄位補強。
④b ★ R-3b 定案(BEAM PMO 2026-10-17 裁定採 (a)):CC_RW.POINTS_TIERS 無序號欄(實查)→
層級序之權威歸 rewards:PointsAccountView 加 tierRank(以 QUALIFICATION_SPEND
升冪取序,1 起算;停用層級不佔序;查無 → null)。campaign 不需知道序之規則,
只消費序號——條件鍵為 minLevelRank(數值序)。
★ 此設計更貼近 BRD 原文:BRD-005 §11 即寫「Level 2 起 3%」——Level 就是序。
⚠️ 序缺席(無點數帳戶/無層級參數)→ 條件不判定(不臆造序)。
⑤ 外部依賴(R-2):生日月權威=客戶主檔(本平台無 customer-service,實查 spec/services.yaml)
→ 介面待外部提供;提供前 birthMonth 條件檔不套用(記 WARN)。
⚠️ 此為外部依賴(非自我設限)——已盤點本地 party 無生日欄。
7.12 條件式比率之待凍結清單(R-1~R-5)——★ 已於 2026-10-17 由 BEAM PMO 逐條裁定,定案見 §7.13
用途:
§7.11①之「有條件檔」目前 fail-closed(不套用)。解除 fail-closed 之前,
需逐條凍結下列維度之欄名、值域、來源服務。本清單每條含「BRD 依據/實查現況/待裁定」,
裁決後直接可實作(不需再查)。BRD 依據原文(
BRD-005 §11):「條件式比率(本版):比率依方案/生日月/Level/支付路徑
分層——生日月國外 x3、Level 2 起 3%」;另「支付路徑限定(本版):實體/國際三Pay/
自家支付/第三方條碼——CUBE 指定消費限實體與三Pay」。
| # | 維度 | BRD 依據 | 值域來源(實查現況) | 需 BEAM PMO 裁定 | 凍結後落點 |
|---|---|---|---|---|---|
| R-1 | 方案(plan) | §11 條件式比率 |
表已存在:CC_CPG.CAMPAIGN_PLANS(plan_code/benefit_rate_json/level_req/valid_from-to) |
① 條件式比率之「方案」=對映 campaign_plans.plan_code?② 或改由 campaign_plans.benefit_rate_json 直接承載方案層權益率(與 campaign_rates 之分工)? |
condition_json.planCode 匹配 campaign_plans(同活動內) |
| R-2 | 生日月 | §11(生日月國外 x3) |
來源未備:CC_PARTY.ACCOUNTS 無生日欄(實查 birth 無命中) |
生日資料權威在何處?(客戶主檔/party 擴充/外部)——決定後需同步契約
|
需新增來源;condition_json.birthMonth 比對「帳戶生日月」 |
| R-3 | Level | §11(Level 2 起 3%);BRD §312「CUBE Level 進階制」 |
有表但跨服務:CC_RW.POINTS_ACCOUNTS.tier/POINTS_TIERS.tier_code;⚠️ campaign → rewards 無同步 ACL(SAD-002 §5) |
① Level 權威=rewards 之 tier?② 傳遞方式:事件投影(rewards 發布 tier 變更 → campaign 本地投影)或 rewards 提供查詢 API(需改 SAD-002 §5 ACL)? |
投影表或 API;condition_json.minLevel 比對 |
| R-4 | 支付路徑(payPath) | §11 支付路徑限定(實體/三Pay/自家支付/第三方條碼) |
來源未備(#A-257 實查):授權側無結構化 wallet 欄位(僅自由格式 emvData) |
① 授權側契約補強(新增結構化欄位)之範圍?② 補強前非實體路徑落 UNKNOWN(fail-closed)是否可接受? |
condition_json.payPath ∈ {PHYSICAL, THREEPAY, OWN_PAY, THIRD_BARCODE} |
| R-5 | 適用率留痕(稽核) | §12 條件式比率覆核「對外宣稱最高 XX% 之實際達成條件與筆數」 | 現行:指令已帶 applicableRate(§7.11②)但未記「為何適用該率」
|
是否需於指令加 rateReason(命中哪一檔/哪個條件)以支援月報覆核? |
command_json.rateReason(可選) |
裁定方式:逐條回覆(例:
R-1 採①;R-3 採事件投影;R-5 需要)即可。
未裁定前之行為:有條件檔一律 fail-closed(記 WARN、不套用、不臆造匹配)——
即「不會算錯」,但「有條件加碼」暫不生效。
★ 注意:campaign_plans與campaign_rates之分工(R-1②)牽動§3 #15/#16之參數模型,
建議與CR-045(參數極大化)一併定案。
7.11 活動回饋率接線與精確上限截斷(★ 實作完成,真機通過)
語意依 BRD 例 1 算式(權威):回饋價值 = 消費金額 × rate(20,000 × 0.5% = 100);
campaign_rates.rate即該比率(SCS-026 §3 #15條件式比率)。
① 適用比率判定(campaign 職責——CR-045 D3「規則判定」):
-
無條件基準檔(
condition_json為空物件{}/null)=唯一可判定者——BRD 例 1 之
「一般消費 0.5%」即此檔;取最高 rate。 -
有條件檔(依方案/生日月/Level/支付路徑分層):逐欄語意尚未凍結 →
fail-closed 不套用(記 WARN),不臆造條件匹配。 - ⚠️ 實作要點:
condition_json為 Oracle JSON 型別——以getString讀取
(getObject拋ORA-18722),且不在 SQL 內與字串比較(語意不穩)→ 於 Java 端判定空物件。
② 比率隨指令下發:command_json.applicableRate(可選);rewards 有值時取代標準層級倍率
(SCS-010 §7.4),無值走既有倍率。
③ 精確上限截斷(CR-047 §3.1 補完):
已用量 ≥ 上限 或 已用量 + 本筆推定價值 > 上限 → 停發;
推定價值=amount × rate(無比率時為 0 → 退化為「已達上限即停發」,保守不臆造)。
★ 對照 BRD 例 1:上限 1,000 元、已用 995 元、本筆 20,000 元 × 0.5% = 100 元 → 超限即停。
④ 仍缺:有條件比率之逐欄語意(待 BRD §11 條件式比率逐條驗證)/排除項/疊加(D9)/
REFUND 回沖(reversed.v1 帶 reversalOf,需規格先行)/POINTS_LEDGER.campaign_code 寫入。
7.8 門檻判定與指令下發語意(★ 實作完成,真機通過)
★ 權威矛盾釐清(依 preflight 紀律,不可靜默):
SCS-026 §2之 BR-03 表列
「campaign-service(計算)+ rewards-service(點數帳本)」與CR-045D3(BEAM PMO 2026-10-15 採納:
「Campaign 不接管計算;rewards 保留紅利累點與回沖規則權威」相衝突。
以 D3 為準:campaign 判定資格與規則(門檻/錄取/疊加/上限之「算不算」),
rewards 執行點數計算與帳本(「給多少」)。SCS-026 §2已同步更正。
① 判定流程(TransactionRecognitionService,認列同交易):
- 查活動之
campaign_thresholds列:- 無門檻列(即時回饋型)→ 維持現行:每筆認列下發指令;
-
有門檻列(滿額型)→ 累計判定,
QUALIFIED才下發指令(一次性)。
- 門檻 scope(
campaign_thresholds.threshold_scope):-
PER_TXN:單筆amount_minor ≥ min_amount_minor即達標; -
ACCUM:campaign_enrollments.accrued_amount_minor原子累計(V5新欄,+=單語句),
累計 ≥min_amount_minor即達標(正附卡合併=household 語意,§7.5②); -
PER_STORE_PER_DAY:本期不實作(fail-closed)——「每館」之店別識別與 merchantId 之
等價性未經業務確認;含此 scope 之活動記 WARN 並不下發指令。
-
-
升格原子性:
UPDATE … SET status='QUALIFIED' WHERE id=? AND status='REGISTERED'——
恰更新 1 列者才下發指令(不同 postingId 分區之併發升格,僅一筆成功→指令恰一次)。 - 多檔門檻(SOGO 型多檔):任一列達標即升格;檔位對應之差異化禮內容屬回饋計算(下一增量),
本期指令為一次性、不區分檔位(誠實揭露)。
② 事實欄:V5__enrollment_accrued.sql 為 campaign_enrollments 加
accrued_amount_minor NUMBER(18) DEFAULT 0 NOT NULL(活動期內認列累計;稽核友善,
QUALIFIED 後仍持續累計)。不修改 V1 baseline。
7.9 上限截斷(cap)——★ 已依 CR-047 定案並實作(2026-10-17)
BEAM PMO 2026-10-17 裁定:開立
CR-047並回訪既有架構(參數極大化,不用寫死/workaround);
「點」與「元」兩側皆為參數(現防呆 1 點=1 元)。
定案設計見CR-047 §2 架構回訪 R1–R6/§3;實作摘要:
- 定義/用量兩表分工:
campaign_caps(定義)+campaign_cap_usages(用量;V6)——
通用scope_key+period_key維度(新增單位不需改表);- 單位:一律以回饋價值(分) 記帳;點值以商品參數
rewards/point-value-minor
換算(兩側皆參數;預設 1=既有防呆);- 事前截斷:下發指令前檢查「已用量 ≥ 上限即停發」(保守;精確之「已用量+本筆推定價值」
待rate_schedule實作後補);- 事後記帳:
rewards.points.accrued.v1回流(payload 帶campaignCode/valueMinor/
instrumentId/postingDate;CR-047 §3.3)→ 原子遞增用量;- 本期支援:
PER_CARD/PER_HOUSEHOLD/PER_MONTH/PER_YEAR;
PER_STATEMENT/PER_HOUSEHOLD_MEMBERfail-closed(來源未備,記 WARN 不臆造)。真機判定力:未達上限可發/回填達上限後停發/
PER_MONTH跨月各自獨立/未支援單位不發。
原始提案紀錄(保留供追溯)
★ 實查
campaign_capsDDL 發現兩個真實落差,未裁定前不實作(fail-closed,不臆造換算):
| # | 落差 | 內容 | 提案 |
|---|---|---|---|
| ① | 結構落差 |
campaign_caps 無 household_id 維度——PER_HOUSEHOLD(每歸戶上限,BRD 例 1)需 per-household 之 consumed 追蹤,表僅有活動級單一 consumed_minor
|
V6 遷移:ALTER TABLE campaign_caps ADD household_id RAW(16)(可空;活動級 cap 為 NULL、每戶 cap 為 NOT NULL)+唯一約束 (campaign_code, cap_unit, household_id);consumed_minor 之原子條件遞增(WHERE consumed_minor + ? <= cap_value_minor)與名額取號同型 |
| ② | 單位落差 |
cap_value_minor(分)vs rewards 回流之 points(點)——回饋價值換算(例 1「上限 500 元刷卡金」=上限是回饋價值,而 points.accrued 帶的是點數)屬客戶業務規則
|
兩案待裁定:(a) cap 以點數累計(cap_value_minor 語意改「回饋單位」,最簡);(b) 以消費金額累計(門檻同源,accrued_amount_minor 可復用——但「回饋上限」≠「消費上限」,語意不符 BRD 例 1/3)。建議 (a):BRD 例 1/3 之上限皆為回饋價值,需 BEAM PMO 提供點數↔元換算(或確認以點數為 cap 單位) |
③ 回流路徑(已備):rewards 已發布 rewards.points.accrued.v1(SCS-010 §7.1,含 accountId/points)——campaign 新增消費者即可回流;卡在①②之裁定,非技術阻塞。
④ AC 11 載體無關性(契約即證明):rewards 之 AccrualRequest 無載體欄位(入點以
accountId 為識別)→ 入點天然載體無關;instrumentId 僅稽核用(campaign 指令事實)。
服務內判定力測試已驗:同戶實體卡+虛擬卡各一筆認列 → 跨載體累計合併、達標一次性指令。
⚠️ 跨服務真 Kafka 端到端仍待跨 JVM 驗證(同 #A-241 判例:本機沙箱攔截代理限制)。
8. 已知限制(誠實揭露)
- ★ 本版規格已部分落地,但不是完整活動引擎:
campaign-service現已具備 Campaign 定義(DDD/JDBC/Outbox/5 端點)+交易認列消費端(PostedTransactionListener/TransactionRecognitionService/consumed_events冪等/ENROLLMENTS錄取/REWARD_CMDS指令下發,真 Oracle 測試通過)。仍缺:門檻判定/上限截斷/排除項檢核/疊加計算/錄取名額原子控管(BRD AC 7)/登錄 API(REGISTER 模式)/rewards 側指令消費。不得把「認列已通」寫成「回饋已確定」。 -
B4/AC 11 尚不可執行:消費端鏈路(認列→錄取→指令)已通真機,但 AC 11 尚需 rewards 側消費
REWARD_CMDS之實作與三載體同額對照資料;現行REWARD_CMDS為 PENDING 佇列狀態(指令已下發、無人收——屬 §7.5① 之計畫內過渡,rewards 消費端為下一增量)。 -
交易事件資料缺口已定位:
posting.transaction.posted.v1現已可帶instrumentId/merchantId/mcc三個可選欄位(皆有權威來源:載體主檔/入帳請求/授權紀錄)。仍缺:payPath(BR-13 無核定輸出契約)、householdId(不得以 accountId 靜默代替)——二者屬活動認列契約範圍;不得為了讓活動規則「看起來可算」而臆造。 -
不可臆造 AC 11 行為:本版沒有自行新增「交易事件→活動認列→回饋發放」的 payload、事件消費者、回饋計算公式或活動政策;這些須先補
SCS/契約,並確認與rewards-service權威及 BRD 之 recognition/排除/上限語意一致。 -
BR-16 載體抽象侵入性最高:交易鍵由
card_no升為instrument_id,牽動全服務 DTO/DDL/事件 payload;已完成 card/authorization/posting/statement/party 等現有鏈路接線,但 B4 同構回饋仍是獨立未完成增量。 -
BR-13 支付路徑認定器需與
posting-service/卡組織歸屬檔介接規格先行(BRD 附錄 B 行動 6 未結)。 - BR-14 通路插件的 Adapter 介面需與 IT 共擬(BRD 附錄 B 行動 9 未結)。
-
BRD 遺留假設仍待客戶:A1 銀聯檔期清單、A6 本行信貸產品全表、A7 利率減碼會計科目、
A4/A5 逐卡官網條款覆核。這些屬客戶輸入,非我方阻塞。 - ⚠️ 前版 1.0 之「六項口述政策」已由 BRD 取代;兩者不一致處以 BRD 為準,
CR-045 §8.2僅保留為歷史決策記錄。
9. 測試案例(對應 TRP-001,設計提案)
| # | 案例 | 對應 |
|---|---|---|
| 1 | 例 1–10 逐例以參數建立並驗算 | BRD §15 |
| 2 | 100 併發搶 10 名額,成交恰 10、無超發 | AC 7 |
| 3 | 同卡同額四支付路徑分級 | AC 8 |
| 4 | Level/方案切換次月生效+慶生月任務進度 | AC 9 |
| 5 | 新通路經 CHANNEL 主檔掛載後納入既有活動 | AC 10 |
| 6 | 實體卡號 vs 虛擬卡號同構回饋一致(待交易認列/回饋發放契約先立) | AC 11;本版尚不可執行,不得以 DDL 代替驗收 |
| 7 | 信貸首期 0%+提前清償依 clawback 回沖 | AC 12 |
| 8 | 疊加三模式(並行/擇優/互斥) | D9 |
| 9 | 排除項可按活動覆寫(加值事件翻轉) | AC 5 |
| 10 | 正附卡合併上限 vs JCB 獨立登錄雙模式 | AC 4 |
10. 修訂記錄
| 版本 | 說明 |
|---|---|
| 3.9 |
2026-10-17:★ CR-049 真機缺陷修正(跨服務線格式與回沖業務日)——① posting.transaction.reversed.v1 增可選 postingDate(沖正交易之入帳日=業務日錨點;不升版);② ReversalService 之 refundCommandJson 帶 postingDate、未帶時以當日為錨點並記 WARN;③ rewards CampaignRewardCommandListener 對 refund/originalPostingId/postingDate 改「recognition 優先、payload 次之」、回沖判定以 txType=REFUND 為權威、缺 postingDate fail-closed(原拼 "nullT00:00:00Z" → 解析失敗);④ 6 個服務+產生器之 value-serializer 改 StringSerializer,新增架構關卡 A9。★ 真機 E2E 32/32(ops/local/e2e/campaign_chain_e2e.sh):載體無關 123+123、比率 5% → 6 點、上限 used=600 estimated=617 cap=1000 精確截斷、回沖 ADJUST -123 淨零。 |
| 3.8 |
2026-10-17:★ §7.16 排除項參數化實作完成——① 基準=僅 PURCHASE(與原行為一致,無行為變更);② 活動覆寫 exclusion_json:includeTxTypes(納入額外類別,BRD「可按活動覆寫/翻轉」)/excludedMccs(活動自訂 mcc 排除;mcc 缺席時 fail-closed);③ ★ BEAM PMO 裁定「未宣告 clawback_rule → 不回沖」已凍結(原待覆核項結案)。判定力:真機 3 例(預設只認 PURCHASE/覆寫納入 INSTALLMENT/mcc 排除)。 |
| 3.7 |
2026-10-17:★ §7.15 疊加 D9 與優先序實作完成——① V7__campaign_priority.sql(priority;★ 實查原表無此欄——多個 NON_STACKABLE 命中時無從擇一);② Campaign 聚合+repo+DTO(相容多載,既有呼叫不動)+契約;③ TransactionRecognitionService:命中 NON_STACKABLE → 取 priority 最小者並排除其他所有活動;全 STACKABLE → 全部套用。判定力:5 例真機(NON_STACKABLE 擇一/priority 較大者不套用/可疊加者被排除/全 STACKABLE 全套用)。 |
| 3.6 |
2026-10-17:★ §7.14 退款/沖正回沖實作完成——① ReversalListener(cc.posting.transaction.reversed)+ReversalService:eventId 冪等 → 以 reversalOf 對 JSON_VALUE(command_json,'$.postingId') 反查原指令 → 依 clawback_rule 判定(CLAW_BACK 回沖/NO_CLAWBACK 與未宣告不回沖)→ 發回沖指令(refund:true)+ cap 用量退還(GREATEST(consumed - X, 0));② rewards:listener 讀 refund → AccrualRequest.isRefund=true(復用既有退款分支:ADJUST+負值、自 CUR 扣回);③ POINTS_LEDGER.campaign_code 寫入(活動歸屬稽核,欄位已備)。★ 未宣告 clawback_rule 之預設(不回沖)列待 BEAM PMO 覆核;部分退款按比例回沖待 SCS-010 定義。 |
| 3.5 |
2026-10-17:★ R-3b 定案並實作(BEAM PMO 裁定採 (a)「rewards API 回傳 tier 序」)——① rewards:PointsRepository.findTierRank(ROW_NUMBER() OVER (ORDER BY QUALIFICATION_SPEND ASC, TIER_CODE ASC);僅 ACTIVE;查無 → null)+PointsAccountView.tierRank+assembler/PointsAccountService 傳遞;② campaign:TierLookupPort.TierInfo(tier, rank)+RewardsTierClient 解析+RateResolver 條件鍵 minLevelRank(數值序比較;序缺席 → 不判定);③ 契約同步(PointsAccountView.tierRank)。★ 設計洞察:minLevelRank(序)更貼近 BRD §11 原文「Level 2 起 3%」——Level 即序。判定力:campaign RateResolverTest 7 例(基準檔/方案優先/序達標/序未達標/序缺席不判定/payPath 三態/多條件 AND/多檔取最高/無檔 NONE+verify);rewards 真機 2 例(序 1-2-3/停用不佔序)。契約 106 endpoints/0 不符;SQL 44 檔 0 違規;掃描器 0 命中(★ T3 攔下純函式測試缺 verify → 補互動斷言)。 |
| 3.4 |
2026-10-17:★ §7.13 條件式比率凍結定案並實作——BEAM PMO 逐條裁定:R-1②(方案權益率由 campaign_plans.benefit_rate_json 承載,schema {"rate":n};BRD §646 之 PLAN 實體欄位與之一致)、R-2(生日權威=客戶主檔;★ 實查本平台無 customer-service 且 party 無生日欄 → 外部依賴,介面待提供)、R-3(rewards 提供查詢 API;SAD-002 §5 ACL 新增 cc-campaign-service)、R-4(授權側新增結構化 pay_path V13;★ BEAM PMO 明確不接受以 UNKNOWN 交差——缺值時改為「條件不滿足」(不判定,非猜測))、R-5(rateReason 稽核留痕,支援 BRD §12 覆核)。條件 schema 四鍵(planCode/birthMonth/minLevel/payPath)AND 語意、取最高 rate。 |
| 3.3 |
2026-10-17:★ §7.12 條件式比率待凍結清單(R-1~R-5)——應 BEAM PMO「需要我逐條凍結的列在哪裡?」而產出:將散在 BRD-005 §11/本規格 §3 #15 之條件維度整理為可逐條勾選之清單,每條附 BRD 依據/實查現況/待裁定項/凍結後落點。★ 實查新發現:campaign_plans 表已存在(含 benefit_rate_json/level_req)——「方案」維度可用,惟與 campaign_rates 之分工需一併定案(R-1②)。 |
| 3.2 |
2026-10-17:★ §7.11 活動回饋率接線+精確上限截斷(真機通過)——① 語意依 BRD 例 1 算式(回饋價值=金額 × rate)——rate 為回饋率(非倍率);② 僅採無條件基準檔(condition_json={}),有條件檔 fail-closed(語意未凍結);③ applicableRate 隨指令下發 → rewards 有值時取代標準倍率;④ 精確截斷:used ≥ cap 或 used + amount×rate > cap → 停發(CR-047 §3.1 補完);⑤ ★ 實作陷阱:condition_json 為 Oracle JSON 型別——getString 讀取(getObject→ORA-18722)且不在 SQL 內字串比較(改 Java 端判定)。campaign 46 例/rewards 159 例 全綠;SQL 30 檔 0 違規;掃描器 0 命中;契約 106 endpoints/0 不符。仍缺:有條件比率逐欄語意/排除項/疊加/REFUND 回沖/ledger 歸屬寫入。 |
| 3.1 |
2026-10-17:★ CR-047 cap 定案並實作(真機通過)——依 BEAM PMO 裁定(開 CR+回訪架構;點與元兩側皆參數)。架構回訪:campaign_caps 僅活動級單一 consumed,無法承載 BRD §11「六單位各自可配+可組合」→ 定義/用量兩表分工(campaign_caps 定義/campaign_cap_usages 用量;V6);通用 scope_key+period_key(新增單位不改表)。換算:一律以回饋價值(分)記帳;點值=商品參數 rewards/point-value-minor(預設 1=既有防呆)——「點」為資料、「元」為參數。實作:CapGuardService(事前截斷/事後回填)、JdbcCapUsageRepository(原子遞增)、PointAccruedListener(cc.rewards.points.accrued 回流)、TransactionRecognitionService 下發前經守衛。rewards 側:AccrualRequest 加 campaignCode/instrumentId/postingDate(可選)、accrued payload 加 4 可選欄位(CR-047 §3.3,不升版)、ProductPointValueHttpClient(取點值)。判定力:未達上限可發/回填達上限停發/PER_MONTH 跨月獨立/未支援單位 fail-closed。campaign 43 例、rewards 158 例 全綠;SQL 30 檔 0 違規;掃描器 0 命中;契約 106 endpoints/0 不符。 |
| 3.0 |
2026-10-17:★ §7.9 cap 設計提案+錄取查詢端點+AC 11 載體無關性(真機通過)——① cap 兩落差實查:campaign_caps 無 household_id 維度(PER_HOUSEHOLD 之 per-household consumed 無處放)、cap_value_minor(分)vs rewards 回流點數之換算未定——待 BEAM PMO 裁定後實作(提案:V6 加欄+兩案換算,建議以點數為 cap 單位),不臆造;② 新端點 GET /api/v1/campaigns/{code}/enrollments/{householdId}(回 status/quotaSeq/accruedAmountMinor——AC 11 對照之可觀測性),契約重生 106 endpoints/0 errors;③ AC 11 載體無關性服務內判定力測試:同戶實體卡+虛擬卡各一筆認列→跨載體累計合併(16,000 達標)→一次性指令(載體僅稽核;AccrualRequest 無載體欄位=契約面載體無關);⚠️ 跨服務真 Kafka 端到端仍待跨 JVM 驗證(#A-241 判例)。campaign 全測 40 例 BUILD SUCCESSFUL;SQL 28 檔 0 違規;掃描器 0 命中;契約↔DTO 0 不符。 |
| 2.9 |
2026-10-17:★ §7.8 門檻判定與指令下發完成(真機通過)——① 無門檻活動=即時型(每筆認列下發指令,現行語意保留);② 有門檻活動=滿額型:PER_TXN(單筆達標)/ACCUM(V5__enrollment_accrued.sql 新欄 accrued_amount_minor 原子 += 累計)→ 達標 原子升格 QUALIFIED(UPDATE … WHERE status='REGISTERED',併發下恰一筆成功→指令恰一次);PER_STORE_PER_DAY fail-closed 略過(店別識別未經業務確認);③ REGISTER+已登錄者之認列接通(未登錄略過——登錄 API 與認列鏈閉合);④ ★ 權威矛盾釐清:SCS-026 §2 BR-03(campaign 計算)與 CR-045 D3(Campaign 不接管計算)相衝突——依 D3 為準:campaign 規則判定(「算不算」)、rewards 點數計算與帳本(「給多少」),§2 已更正。campaign 全測 38 例 BUILD SUCCESSFUL(判定力:ACCUM 三段式 12000→21000→26000 之無指令/一次性下發/不重發;PER_TXN 立即升格;未達不下發;fail-closed;REGISTER 已登錄才認列)。SQL 合併 28 檔 0 違規;掃描器 0 命中;契約↔DTO 0 不符。 |
| 2.8 |
2026-10-17:★ §7.7 名額原子控管與登錄 API 實作完成(真機通過)——EnrollmentService+POST /api/v1/campaigns/{code}/enrollments(僅 REGISTER 模式):① 原子取號=行鎖(SELECT FOR UPDATE)+條件更新(雙重保險)+DB CHECK 防線;② 額滿 CC-CAM-1004;③ 冪等:同 (campaign, wave, household) 回前次結果;④ 併發撞 uq_enroll → 退回名額補償;⑤ AUTO/非 ACTIVE → CC-CAM-3001;⑥ 波次窗口檢核。新檔:EnrollmentRepository/JdbcEnrollmentRepository/EnrollmentService/EnrollmentController/DTO/CampaignTimeConfig。契約重生 105 endpoints/0 errors。campaign 全測 33 例 BUILD SUCCESSFUL(真 Oracle 判定力:取號連號/禁超發/冪等/AUTO 拒絕/404/窗口外)。AC 7「禁超發」解鎖。 |
| 2.7 |
2026-10-17:★ §7.6 指令派發與 rewards 消費完成(真機通過)——campaign RewardCommandRelay(REWARD_CMDS 即指令 outbox:PENDING→發布→SENT;SENT=已派發非已入點);事件 campaign.reward.commanded.v1(主題 cc.campaign.reward.commanded,分區鍵 commandId);rewards CampaignRewardCommandListener → AccrualService.accrue(冪等鍵 campcmd:<commandId>;到期日以 postingDate 錨點;⚠️ 活動加成 rate_schedule 未實作——本期標準層級倍率,不臆造加成)。§7.5① 收斂:本期認列類別=僅 PURCHASE。§7.5① 表同步 SAD-004 §5.2(posting 消費者加 campaign;新列 campaign.reward.commanded)。campaign 全測 27 例、rewards 全測 158 例 BUILD SUCCESSFUL;SQL 合併 27 檔 0 違規;掃描器 0 命中(faketest T5 攔下無斷言測試,已補 assertDoesNotThrow)。仍缺:門檻/上限/排除項/疊加/名額控管/登錄 API/活動加成比率。 |
| 2.6 |
2026-10-16:★ §7.5 消費端實作完成(真機通過)——PostedTransactionListener(cc.posting.transaction.posted)→ TransactionRecognitionService(eventId 冪等+sequenceNo 亂序略過+沖正略過+REGISTER 略過)→ ENROLLMENTS 錄取(household_id ≡ accountId;uq_enroll 冪等重放)→ REWARD_CMDS 指令下發(僅認列事實、PENDING;計算屬 rewards 權威)。遷移:V3__consumed_events.sql、V4__nullable_instrument.sql(舊事件 instrumentId null 不阻斷,§7.5④)。campaign 全測 25 例全綠(真 Oracle 判定力測試:冪等/亂序/沖正/REGISTER/舊事件相容/期間外/未知型別)。仍缺:門檻判定/上限截斷/排除項/疊加/名額控管(AC 7)/登錄 API/rewards 消費端——不得宣稱回饋已確定。 |
| 2.5 |
2026-10-16:★ BEAM PMO 裁定落定——② 歸戶語意:自然人經 BEAM PMO 確認;法人公司戶經實查同構(account_type IN ('PERSONAL','CORPORATE'),公司戶即一歸戶、員工附卡為其 parties[];活動差異走 exclusions 參數);③ payPath:規則表對照方向經 BEAM PMO 同意;實查定位新缺口——授權側無結構化 wallet 欄位,種子規則依 BRD §6B/AC 8 可立,wallet 識別權威待授權側契約補強(補強前非實體路徑落 UNKNOWN,fail-closed)。 |
| 2.4 |
2026-10-16:CR-046 新增 §7.5 活動認列輸入契約(設計提案,待 BEAM PMO 核定)——① 認列來源唯一化(posted.v1);② 欄位權威映射;③ 歸戶語意更正:household_id ≡ accountId(權威 CC_PARTY.ACCOUNTS/SCS-004;更正 #A-253 之過度保守判斷);④ payPath 權責:認定器屬 campaign、認定規則待 BEAM PMO 核定、不在 posting 事件加欄;⑤ AC 11 驗收前置條件五項。核定前不寫 campaign 消費端。 |
| 2.3 |
2026-10-16:CR-046 AC 11 資料鏈補強——posted.v1 已可帶 instrumentId/merchantId/mcc(皆權威來源);campaign 消費者、payPath 輸出契約、household 歸戶解析仍未立,AC 11 繼續維持「待契約」。 |
| 2.2 |
2026-10-16:CR-046 AC 11 資料平面盤點——posting.transaction.posted.v1 已帶可選 instrumentId;惟 posting 交易未持久化 mcc、PostingRequest.merchantId 未進入聚合,故 mcc/merchantId 不能臆造加入活動認列;AC 11 仍待契約與來源治理。 |
| 2.1 |
2026-10-16:CR-046 B4/AC 11 動工前盤點更正——Campaign 定義之 Java 主幹已存在,但 campaign_enrollments/campaign_reward_cmds 僅為 DDL;尚無交易事件消費、錄取服務、回饋計算與 rewards 發放鏈路。故 AC 11 改列「待立交易認列/回饋契約」,不以有表或有 instrument_id 欄位宣稱完成 |
| 2.0 |
2026-10-16:依 BRD-005(客戶提供,v1.3)全面改寫——① 對應需求由「六項口述政策」擴為 BR-01~16 全 16 條;② BR-01 參數化要素由前版 6 項擴為 30 欄完整契約(新增申請/撥款雙日期、認列錨點、進件歸因、條件式比率、cap 六單位、支付路徑限定、成本分攤、外部事件依賴、clawback、任務、抽獎等);③ 新增 ENROLLMENT/PLAN/QUEST/PAY_PATH/CHANNEL/REWARD_CMD 實體;④ 錄取狀態機照 BRD §12.2 錄入;⑤ §6 十個驗算範例列為自動化驗收基準;⑥ §8 明列 BRD 遺留假設屬客戶輸入、BR-16 載體抽象須單獨分期 |
| 1.0 | 2026-10-15:初版(僅基於 BEAM PMO 口述六項政策之草案) |