コンテンツにスキップ

キャンペーンエンジンプラグイン (Campaign Engine Plugin)

概要

Ritsubiの6種類の複雑なキャンペーンタイプに対応するVendureカスタムプラグイン。購入金額・数量ベースのギフト・割引、複雑な条件判定、ハイブリッドキャンペーンなどを実現します。

互換性レンジ

  • Vendure: 3.5.x(apps/vendure-server は ^3.5.1 を使用)
  • Node.js: >=22.11.0(apps/vendure-server/package.json の engines に準拠)

パッケージ情報

  • パッケージ名: @ritsubi/campaign-engine-plugin
  • パス: /packages/plugins/campaign-engine
  • エントリーポイント: src/index.ts

実装機能

6種類のキャンペーンタイプ

1. AMOUNT_BASED_GIFT - 購入金額に基づくギフト

購入金額が一定額を超えた場合、ギフト商品を自動的にカートに追加。

:

  • 30,000円以上購入で、サンプル商品Aをプレゼント
  • 50,000円以上購入で、サンプル商品B + Cをプレゼント
{
  type: 'AMOUNT_BASED_GIFT',
  threshold: 30000,
  gifts: [
    { productId: 'sample-a', quantity: 1 }
  ]
}

2. QUANTITY_BASED_DISCOUNT - 購入数量に基づく割引

特定商品を一定数量以上購入した場合、割引を適用。

:

  • エクスビアンス商品10個以上で5%割引
  • エクスビアンス商品20個以上で10%割引
{
  type: 'QUANTITY_BASED_DISCOUNT',
  targetProducts: ['exuviance-*'],
  tiers: [
    { minQuantity: 10, discountRate: 0.05 },
    { minQuantity: 20, discountRate: 0.10 }
  ]
}

3. QUANTITY_BASED_GIFT - 購入数量に基づくギフト

特定商品を一定数量以上購入した場合、ギフト商品を提供。

:

  • メソセウティカル商品5個以上で、ギフトAを1個プレゼント
  • メソセウティカル商品10個以上で、ギフトBを2個プレゼント
{
  type: 'QUANTITY_BASED_GIFT',
  targetProducts: ['mesoceutical-*'],
  tiers: [
    { minQuantity: 5, gifts: [{ productId: 'gift-a', quantity: 1 }] },
    { minQuantity: 10, gifts: [{ productId: 'gift-b', quantity: 2 }] }
  ]
}

4. COMPLEX_CAMPAIGN - 複雑な条件のキャンペーン

複数の条件を組み合わせた高度なキャンペーン。

:

  • エクスビアンス10個 + メソセウティカル5個 + 合計金額50,000円以上で、特別ギフトをプレゼント
{
  type: 'COMPLEX_CAMPAIGN',
  conditions: [
    { type: 'PRODUCT_QUANTITY', productPattern: 'exuviance-*', minQuantity: 10 },
    { type: 'PRODUCT_QUANTITY', productPattern: 'mesoceutical-*', minQuantity: 5 },
    { type: 'AMOUNT_THRESHOLD', minAmount: 50000 }
  ],
  action: {
    type: 'ADD_GIFTS',
    gifts: [{ productId: 'special-gift', quantity: 1 }]
  }
}

5. MULTI_CATEGORY_SELECTION - 複数カテゴリ選択型

複数のカテゴリから一定数量を選択購入した場合に特典を提供。

:

  • カテゴリA、B、Cから合計15個以上購入で、特別割引10%
{
  type: 'MULTI_CATEGORY_SELECTION',
  categories: ['category-a', 'category-b', 'category-c'],
  minTotalQuantity: 15,
  action: {
    type: 'DISCOUNT',
    discountRate: 0.10
  }
}

6. HYBRID_CAMPAIGN - ハイブリッドキャンペーン

購入金額と数量の両方の条件を組み合わせたキャンペーン。

:

  • エクスビアンス商品10個以上 かつ 合計30,000円以上で、ギフト + 5%割引
{
  type: 'HYBRID_CAMPAIGN',
  quantityCondition: {
    targetProducts: ['exuviance-*'],
    minQuantity: 10
  },
  amountCondition: {
    minAmount: 30000
  },
  actions: [
    { type: 'ADD_GIFTS', gifts: [{ productId: 'gift-x', quantity: 1 }] },
    { type: 'DISCOUNT', discountRate: 0.05 }
  ]
}

追加機能

セミナーURL連動キャンペーン

特定のセミナーURLから流入したユーザー限定のキャンペーン。

{
  enableSeminarUrls: true,
  seminarUrlPattern: 'https://example.com/seminar/{campaignCode}'
}

隠しキャンペーンコード

管理画面でのみ表示される特別なキャンペーンコード。

{
  enableHiddenCodes: true,
  hiddenCodes: ['SPECIAL2025', 'VIP-ONLY']
}

技術仕様

プラグイン設定オプション

export interface CampaignEnginePluginOptions {
  enableAmountBasedGift?: boolean;
  enableQuantityBasedDiscount?: boolean;
  enableQuantityBasedGift?: boolean;
  enableComplexCampaign?: boolean;
  enableMultiCategorySelection?: boolean;
  enableHybridCampaign?: boolean;
  enableSeminarUrls?: boolean;
  enableHiddenCodes?: boolean;
}

プラグイン初期化

import { CampaignEnginePlugin } from '@ritsubi/campaign-engine-plugin';

export const config: VendureConfig = {
  plugins: [
    CampaignEnginePlugin.init({
      enableAmountBasedGift: true,
      enableQuantityBasedDiscount: true,
      enableQuantityBasedGift: true,
      enableComplexCampaign: true,
      enableMultiCategorySelection: true,
      enableHybridCampaign: true,
      enableSeminarUrls: true,
      enableHiddenCodes: true,
    }),
  ],
};

エンティティ

CampaignEntity

キャンペーンの定義を管理するエンティティ。

@Entity()
export class CampaignEntity extends VendureEntity {
  @Column()
  name: string;

  @Column()
  campaignCode: string;

  @Column('enum', {
    enum: [
      'AMOUNT_BASED_GIFT',
      'QUANTITY_BASED_DISCOUNT',
      'QUANTITY_BASED_GIFT',
      'COMPLEX_CAMPAIGN',
      'MULTI_CATEGORY_SELECTION',
      'HYBRID_CAMPAIGN',
    ],
  })
  campaignType: CampaignType;

  @Column('jsonb')
  conditions: CampaignCondition[];

  @Column('jsonb')
  actions: CampaignAction[];

  @Column()
  startDate: Date;

  @Column()
  endDate: Date;

  @Column()
  isActive: boolean;

  @Column({ default: false })
  isHidden: boolean; // 隠しキャンペーン

  @Column('int', { default: 0 })
  priority: number; // 複数キャンペーン適用時の優先度
}

CampaignUsageEntity

キャンペーン使用履歴を記録するエンティティ。

@Entity()
export class CampaignUsageEntity extends VendureEntity {
  @Column()
  campaignId: string;

  @Column()
  orderId: string;

  @Column()
  customerId: string;

  @Column('jsonb')
  appliedActions: Record<string, unknown>;

  @Column()
  appliedAt: Date;

  @Column('decimal', { precision: 12, scale: 2 })
  discountAmount: number;

  @Column('jsonb', { nullable: true })
  giftsAdded: Array<{ productId: string; quantity: number }>;
}

サービス

CampaignService

キャンペーンの管理を行うサービス。

主要メソッド:

  • createCampaign(input: CreateCampaignInput): Promise<CampaignEntity>
  • 新しいキャンペーンを作成
  • getCampaign(campaignCode: string): Promise<CampaignEntity>
  • キャンペーンを取得
  • getActiveCampaigns(): Promise<CampaignEntity[]>
  • 有効なキャンペーン一覧を取得
  • validateCampaignCode(code: string): Promise<boolean>
  • キャンペーンコードの有効性を検証

CampaignEngineService

キャンペーンの適用ロジックを担当するサービス。

主要メソッド:

  • evaluateCampaigns(order: Order): Promise<ApplicableCampaign[]>
  • 注文に適用可能なキャンペーンを評価
  • applyCampaign(order: Order, campaign: CampaignEntity): Promise<void>
  • キャンペーンを注文に適用
  • calculateDiscount(order: Order, campaign: CampaignEntity): Promise<number>
  • キャンペーン割引額を計算
  • addGiftItems(order: Order, gifts: GiftItem[]): Promise<void>
  • ギフト商品をカートに追加

カスタムフィールド

Order

  • appliedCampaigns (text): 適用されたキャンペーン情報(JSON形式)

OrderLine

  • campaignGift (boolean): キャンペーンによるギフト商品かどうか
  • originalPrice (int): キャンペーン適用前の価格

対応する要件

要件定義書との対応

  • キャンペーン6種: /docs/specifications/キャンペーン.xlsx の全6種類のキャンペーンタイプに対応
  • セミナーURL連動: 特定セミナー参加者向けキャンペーン
  • 隠しキャンペーンコード: 管理画面限定表示

使用例

フロントエンド(Next.js)での使用

// キャンペーンコード入力
await apolloClient.mutate({
  mutation: APPLY_CAMPAIGN_CODE,
  variables: {
    orderId: currentOrder.id,
    campaignCode: 'SUMMER2025',
  },
});

// 適用可能なキャンペーン一覧を取得
const { data } = await apolloClient.query({
  query: GET_APPLICABLE_CAMPAIGNS,
  variables: { orderId: currentOrder.id },
});

data.campaigns.forEach(campaign => {
  console.log(`${campaign.name}: ${campaign.description}`);
});

キャンペーンギフトの表示

// カート内のギフト商品を識別
const giftItems = order.lines.filter(line => line.customFields.campaignGift);

giftItems.forEach(item => {
  console.log(`ギフト: ${item.productVariant.name}`);
});

管理画面での使用

// 新しいキャンペーンを作成
await campaignService.createCampaign({
  name: '夏の大感謝祭',
  campaignCode: 'SUMMER2025',
  campaignType: 'HYBRID_CAMPAIGN',
  conditions: [
    {
      type: 'PRODUCT_QUANTITY',
      productPattern: 'exuviance-*',
      minQuantity: 10,
    },
    { type: 'AMOUNT_THRESHOLD', minAmount: 30000 },
  ],
  actions: [
    { type: 'ADD_GIFTS', gifts: [{ productId: 'gift-sample', quantity: 1 }] },
    { type: 'DISCOUNT', discountRate: 0.05 },
  ],
  startDate: new Date('2025-07-01'),
  endDate: new Date('2025-08-31'),
});

// キャンペーン使用状況を確認
const usage = await campaignService.getCampaignUsageStats('SUMMER2025');
console.log(`使用回数: ${usage.totalUsages}`);
console.log(`総割引額: ${usage.totalDiscountAmount}円`);

キャンペーン評価フロー

複数キャンペーンの同時適用

1. アクティブなキャンペーンを取得
   ├─ 期間内のキャンペーンをフィルタ
   ├─ 顧客ステータスで絞り込み
   └─ 優先度順にソート

2. 各キャンペーンの条件を評価
   ├─ 金額条件をチェック
   ├─ 数量条件をチェック
   ├─ カテゴリ条件をチェック
   └─ カスタム条件をチェック

3. 適用可能なキャンペーンを選択
   ├─ 優先度の高い順に適用
   ├─ 重複適用のルールを確認
   └─ 最大適用数を考慮

4. キャンペーンアクションを実行
   ├─ 割引を適用
   ├─ ギフト商品を追加
   └─ 使用履歴を記録

セキュリティ考慮事項

  1. キャンペーンコード検証: フロントエンドからのコードをバックエンドで検証
  2. 重複適用防止: 同一キャンペーンの重複適用をブロック
  3. 隠しコードの保護: 隠しキャンペーンコードは管理者のみアクセス可能
  4. 使用履歴の記録: すべてのキャンペーン適用を記録

パフォーマンス最適化

  • キャンペーンキャッシング: アクティブなキャンペーンをRedisにキャッシュ
  • 条件評価の最適化: 早期リターンで不要な評価をスキップ
  • バッチ処理: 大量のキャンペーン適用はバックグラウンドで処理

トラブルシューティング

よくある問題

問題: キャンペーンが適用されない

  • 原因: キャンペーン期間外または条件未達
  • 解決: startDate, endDate, conditions を確認

問題: ギフト商品が重複して追加される

  • 原因: キャンペーン重複適用の制御が不足
  • 解決: CampaignUsageEntity で使用履歴を確認し、重複をブロック

関連ドキュメント

今後の拡張予定

  • AIによるキャンペーン効果予測
  • A/Bテスト機能
  • 自動キャンペーン生成機能
  • リアルタイムキャンペーン効果分析ダッシュボード