Project

General

Profile

CR #201 » SCS-026_行銷活動Campaign模型程式設計規格.md

內容更新:內部稱謂去識別化(2026-10-18) - Redmine Admin, 10/18/2026 05:32 AM

 

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-045 D3 治理):計算權威留原服務(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-005 AMEX 企業卡除外)屬活動排除參數(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/第三方條碼),
    惟授權側現僅有自由格式 emvData JSON(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)驗收前置條件(全部滿足方可執行真機驗收):

  1. 本節②③裁定已落定(★ 2026-10-16 達成:自然人確認+法人同構查證;payPath 方向同意);
  2. campaign 消費者+錄取寫入(ENROLLMENTS)+回饋指令(REWARD_CMDS)實作完成且真 Oracle 測試通過(★ 已達成,§7.6);
  3. 至少一個活動以真 Oracle 參數建立(BRD §15 之例);
  4. 同戶以實體卡/虛擬卡兩載體產生同額交易,回饋結果逐分一致(Token 路徑依 BRD AC 11 範圍);
  5. 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-045 D3)。
  • 冪等: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 模式):

  1. 活動不存在 → CC-CAM-4040;非 ACTIVE 或非 REGISTER → CC-CAM-3001(AUTO 活動不接受登錄);
  2. 冪等:同 (campaign_code, wave_no, household_id) 已有錄取 → 回前次結果(uq_enroll 自然冪等);
  3. 額滿 → CC-CAM-1004(422;語意來自 SDD-006 §3.4,BR-06);
  4. 取號成功 → 錄取寫入(REGISTERED+quota_seq);
    併發同戶撞 uq_enroll → 退回名額(quota_used - 1)並回前次結果(補償,不得吃掉名額)。

③ 仍缺(下一增量):門檻判定(REGISTERED → QUALIFIED)、上限截斷(BR-03)、排除項、疊加計算、
活動加成比率(rate_schedule)——本節僅解 AC 7 之「禁超發」與登錄入口。

7.15 疊加規則(D9)與活動優先序 ★ 實作完成

依據:BRD-005 D9(stackingMode 必填、無預設值——CC-CAM-1002)。

① 優先序參數:campaigns.priority NUMBER(4) DEFAULT 100 NOT NULL(V7)——數值越小越優先;
未指定為 100。實查原表無此欄(多個不可疊加活動同時命中時無從擇一)。

② 判定(TransactionRecognitionService):

  1. 取「期間內且 ACTIVE」之活動;
  2. 其中若有 NON_STACKABLE → 擇一:取 priority 最小者,
    並排除其他所有活動(含 STACKABLE——NON_STACKABLE =不得與任何活動併用);
  3. 全為 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 反推)。

① 判定與動作:

  1. 反查原指令:以 reversalOf 對 campaign_reward_cmds 之結構化欄位 posting_id(V8)反查
    → 得原活動/歸戶/載體/原金額;查無 → 略過(該筆未經認列,無可回沖)。
    ★ 實作決定:V8__reward_cmd_lookup_columns.sql 加 posting_id/household_id/amount_minor
    +索引——不依賴 JSON_VALUE(JSON 函式之相容性與可索引性皆不理想;銀行稽核要求可查、可索引)。
  2. 回沖條件(clawback_rule):
    • CLAW_BACK → 發回沖指令(command_json 帶 "refund":true + originalPostingId + postingDate);
    • NO_CLAWBACK → 不回沖(活動明示不追回);
    • ★ 未宣告(null)→ 不回沖(BRD-005 僅要求利率型必填;其餘型別未宣告時
      保守不扣客戶點數)——★ BEAM PMO 2026-10-17 裁定:維持「不回沖」(已凍結)。
  3. 上限用量退還:回沖時同步退還 cap 用量(consumed_minor = GREATEST(consumed_minor - X, 0);
    CHECK consumed_minor >= 0 為防線),業務日採沖正交易之入帳日(CR-049)。
  4. 冪等: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 §5 ACL);
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① 擴充):

  1. 有條件檔逐檔比對:全鍵滿足者為候選;取候選中最高 rate;
  2. 無條件基準檔(condition_json={})為底線;
  3. 方案層權益率(campaign_plans.benefit_rate_json.rate,planCode 命中時)優先於條件檔;
  4. 三類皆無 → 不帶 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-045 D3(BEAM PMO 2026-10-15 採納:
「Campaign 不接管計算;rewards 保留紅利累點與回沖規則權威」相衝突。
以 D3 為準:campaign 判定資格與規則(門檻/錄取/疊加/上限之「算不算」),
rewards 執行點數計算與帳本(「給多少」)。SCS-026 §2 已同步更正。

① 判定流程(TransactionRecognitionService,認列同交易):

  1. 查活動之 campaign_thresholds 列:
    • 無門檻列(即時回饋型)→ 維持現行:每筆認列下發指令;
    • 有門檻列(滿額型)→ 累計判定,QUALIFIED 才下發指令(一次性)。
  2. 門檻 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 並不下發指令。
  3. 升格原子性:UPDATE … SET status='QUALIFIED' WHERE id=? AND status='REGISTERED'——
    恰更新 1 列者才下發指令(不同 postingId 分區之併發升格,僅一筆成功→指令恰一次)。
  4. 多檔門檻(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_MEMBER fail-closed(來源未備,記 WARN 不臆造)。

真機判定力:未達上限可發/回填達上限後停發/PER_MONTH 跨月各自獨立/未支援單位不發。

原始提案紀錄(保留供追溯)

★ 實查 campaign_caps DDL 發現兩個真實落差,未裁定前不實作(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 口述六項政策之草案)
(63-63/67)