Skip to content

資料定義

商品

定義如下:

ts
/** 商品
 *
 * key 與 price 會影響計算結果,其他欄位隨意。
 *
 * 同一商品、不同規格,也必須使用不同 key。
 */
export interface Commodity<Data = undefined> {
  /** 唯一值 */
  key: string;
  price: number;
  /** 方便識別,不影響計算 */
  name: string;
  /** 規格。參考用 */
  specifications?: string[];
  /** 自定義額外資料 */
  metadata?: Data;
}
ts
/** 項目,表示商品與其數量 */
export interface Item<Data = undefined> {
  /** 商品 */
  commodity: Commodity<Data>;
  quantity: number;
}

贈品

列出候選商品,需要讓使用者選擇

ts
/** 贈品,表示可選商品與最大數量 */
export interface SelectableGiveaway<Data = undefined> {
  /** 候選商品。可以加入 undefined 表示有「不選擇」選項 */
  commodities: (Commodity<Data> | undefined)[];
  /** 選擇數量。最後選擇總數必須等於此值 */
  quantity: number;
}

優惠類別

優惠基本結構為

ts
/** 優惠規則 */
interface PromotionBase<Type extends `${PromotionType}`, Content> {
  /** 唯一值 */
  key: string;
  type: Type;
  name: string;
  description: string;
  /** 優惠內容。條件與實際結果在此定義。
   *
   * 任一個 content 符合條件,即可套用此優惠。
   */
  contents: Content[];
  /** 優惠群組名稱。分類優惠,可用於限制不可與某 group 一起使用 */
  group?: string;
  /** 限制。例如一筆訂單只能使用一次,或是排除某優惠(不會同時存在) */
  constraints?: Constraint[];
  /** 不計入滿額優惠之條件計算 */
  notIncludedInSpendTotalPrice?: boolean;
}

💡 不計入滿額優惠之條件計算

細節請見文件

其中 type 有三種類別:

ts
export enum PromotionType {
  /** 商品優惠
   *
   * 條件與商品直接相關,過程會取出被套用的商品,不能重複套用商品。
   *
   * 例:
   * - 指定商品滿件折扣、折價、贈送等等。
   */
  COMMODITY_OFFER = 'commodity-offer',

  /** 訂單優惠
   *
   * 條件與商品無直接關連,只與整筆訂單相關。
   *
   * 例:
   * - 整筆訂單 8 折。
   *
   */
  ORDER_OFFER = 'order-offer',

  /** 滿額優惠
   *
   * 依照前兩種優惠的結果,計算其優惠條件。
   *
   * 例:
   * - 滿千送 A、每滿千折 200。
   */
  MINIMUM_SPEND_OFFER = 'minimum-spend-offer',
}

contents 則對應 type 定義其細節:

  1. 商品優惠

    ts
    /** 商品優惠
     *
     * 條件與商品直接相關,過程會取出被套用的商品,不能重複套用商品。
     *
     * - A + B 只要 50 元
     * - A + B,A 打折、B 打折
     * - 買 A 區送 B 區
     * - 任選 10 個 A 系列,打折
     * - 任 3 件 84 折
     * - 2 件 5000 元
     * - 3 件折 500 元
     * - 同品項買 2 送 1
     */
    export type PromotionCommodityOffer<Data = undefined> = PromotionBase<'commodity-offer', {
      /** 候選商品群組。
       *
       * condition.comparison.target 只能是 `purchase-quantity`,因為滿額邏輯只能定義於滿額優惠中。
       *
       * 必須所有 condition 皆相符,才能套用目前 content。
       */
      condition: PromotionDecision<Data>[];
    
      /** 整體結果。
       *
       * 優惠結果可以在 condition 分別定義,也可用此值全部帶入。
       */
      result?: SelectableGiveaway | Arithmetic;
    }>
  2. 訂單優惠

    ts
    /** 訂單優惠
     *
     * 條件與商品無直接關連,只與整筆訂單相關。
     *
     * - 整筆訂單 8 折。
     */
    export type PromotionOrderOffer<Data = undefined> = PromotionBase<'order-offer', {
      result: SelectableGiveaway<Data> | Arithmetic;
    }>
  3. 滿額優惠

    ts
    /** 滿額優惠
     *
     * 依照前兩種優惠的結果,計算其優惠條件。
     *
     * - 單筆消費滿 3 萬元,送 A 商品
     * - 任意消費送 A 商品
     */
    export type PromotionMinimumSpendOffer<Data = undefined> = PromotionBase<'minimum-spend-offer', {
      condition: Comparison;
      /** 結果 */
      result: SelectableGiveaway<Data> | Arithmetic;
    }>

處理邏輯

實際處理邏輯為從 1 到 3 依序帶入優惠:

  1. 將所有商品依照「商品優惠」條件取出並分組
  2. 套入「訂單優惠」
  3. 依照條件判斷是否可使用「滿額優惠」

「商品優惠」與「訂單優惠」可以設定其消費金額是否累積至「滿額累計」。

「不計入滿額優惠之條件計算」具體邏輯如下:

  • 商品優惠:套用商品優惠後的商品價格不列入滿額計算,假設 A 商品原價 100,套用過該商品優惠後,則不列入滿額計算,故滿額累計 0 元

  • 訂單優惠:使用訂單優惠前的價格來判斷是否滿額,假設訂單優惠前是 1000,優惠後是 800,啟用該選項後,滿額累計為 1000 元