Salesforceロイヤルティ管理:Apexを活用したカスタムロジックの実装

背景と適用シナリオ

Salesforce Loyalty Management(ロイヤルティ管理)は、企業が顧客のエンゲージメントとロイヤルティを高めるための包括的なソリューションを提供します。ポイントの付与、特典の交換、会員ティアの管理など、ロイヤルティプログラムの運営に必要な機能が標準で備わっています。多くの一般的なシナリオは、Salesforce Flowなどの宣言的なツールを使用して設定できますが、複雑なビジネス要件や独自のロジックを実装するには、プログラムによるカスタマイズが必要になる場合があります。

Salesforce開発者として、私たちがApex(エイペックス)によるカスタマイズを検討するのは、以下のようなシナリオです。

1. 複雑なポイント計算ロジック

標準のルールエンジンでは対応できない、独自の計算式や外部データソースを参照するポイント付与ロジック。例えば、特定の商品カテゴリの組み合わせ購入や、天候データと連動したボーナスポイントの付与などです。

2. リアルタイムでの大量トランザクション処理

外部のEコマースプラットフォームやPOSシステムから大量の取引データがリアルタイムで連携される場合、Apexの非同期処理(Asynchronous Apex)を活用して、スケーラブルかつ効率的に取引を処理する必要があります。

3. カスタムティアモデルの管理

標準のティア判定ロジック(年間購入額や獲得ポイント数など)に加え、顧客の行動データ(レビュー投稿回数、イベント参加履歴など)を組み合わせた独自のティア昇格・降格ロジックを実装したい場合。

4. 外部システムとの高度な連携

ロイヤルティプログラムのトランザクションを、外部の会計システムやデータウェアハウスと双方向に、かつ複雑なデータマッピングを伴って連携させる必要がある場合。

本記事では、Salesforce開発者の視点から、Loyalty Managementの強力なApex APIであるLoyaltyEngineを活用して、カスタムのロイヤルティプログラムロジックを実装する方法について、具体的なコード例を交えながら詳しく解説します。


原理説明

Apexを使用してLoyalty Managementをカスタマイズする際の中心的な要素は、Transaction Journal(取引ジャーナル)オブジェクトとLoyaltyManagement.LoyaltyEngineクラスです。

Transaction Journalオブジェクト

Transaction Journal (TransactionJournal) は、ロイヤルティプログラムにおけるすべての活動を記録するためのSObjectです。ポイントの付与(Accrual)、交換(Redemption)、調整(Adjustment)、ティアの変更など、会員のポイント残高やステータスに影響を与える可能性のあるすべてのイベントは、このオブジェクトのレコードとして作成されます。

開発者がApexでロジックを実装する際、最初に行うことは、処理対象のイベントを表すTransactionJournalレコードをメモリ上で作成することです。このレコードには、いつ(ActivityDate)、誰が(MemberId)、どのプログラムで(LoyaltyProgramId)、何をしたか(JournalType, JournalSubType)、といった重要な情報が含まれます。

LoyaltyManagement.LoyaltyEngineクラス

LoyaltyManagement.LoyaltyEngineは、Loyalty Managementの頭脳とも言えるApexクラスです。このクラスが提供するprocessTransactionJournalsメソッドを呼び出すことで、作成したTransactionJournalレコードをロイヤルティ処理エンジンに渡し、定義済みのルール(ポイント付与ルールやプロモーションなど)を適用させることができます。

重要な点は、この処理が非同期(asynchronous)で実行されることです。processTransactionJournalsメソッドを呼び出すと、処理はキューに追加され、Salesforceプラットフォームがリソースの空き状況に応じて実行します。これにより、呼び出し元のApexトランザクションは迅速に完了し、ガバナ制限(governor limits)への影響を最小限に抑えることができます。

処理の流れは以下のようになります。

1. トリガーイベントの発生: 外部APIからのコール、Platform Event(プラットフォームイベント)の受信、またはカスタムオブジェクトのレコード作成など、ロイヤルティポイントを付与するきっかけとなるイベントが発生します。

2. Transaction Journalの作成: Apexコード内で、イベントの情報を基にTransactionJournalオブジェクトのインスタンスを作成し、必要な項目(会員ID、プログラムID、活動日、取引金額など)を設定します。

3. LoyaltyEngineの呼び出し: 作成したTransactionJournalレコードのリストを引数として、LoyaltyManagement.LoyaltyEngine.processTransactionJournalsメソッドを呼び出します。

4. 非同期処理の実行: SalesforceプラットフォームがバックグラウンドでTransactionJournalを処理し、関連するロイヤルティルールを適用して、結果をLedger(台帳)オブジェクトに記録します。これにより、会員のポイント残高が更新されます。

この仕組みを理解することで、開発者はSalesforceの標準機能を最大限に活用しつつ、独自のビジネス要件を満たす柔軟なカスタマイズを実装することが可能になります。


サンプルコード

ここでは、特定のロイヤルティプログラムメンバーの購入活動に対してポイントを付与する、基本的なApexコードの例を示します。このコードは、指定されたメンバーIDとプログラム名、取引金額を基にTransactionJournalを作成し、LoyaltyEngineに処理を依頼します。

前提条件:

  • 「Cloud Kicks Inner Circle」という名前の有効なロイヤルティプログラムが存在する。
  • 対象の取引を行うロイヤルティプログラムメンバーが存在する。

// publicメソッドを持つクラスを定義
public class ProcessLoyaltyAccrual {
    
    // 取引を処理するためのpublicメソッド。会員ID、プログラム名、取引金額を引数に取る
    public static void processPurchaseTransaction(Id memberId, String loyaltyProgramName, Decimal transactionAmount) {
        
        // SOQLクエリを使用して、指定されたプログラム名に一致するLoyaltyProgramレコードを取得
        LoyaltyProgram program = [SELECT Id FROM LoyaltyProgram WHERE Name = :loyaltyProgramName LIMIT 1];
        
        // TransactionJournalオブジェクトの新しいインスタンスを作成
        TransactionJournal journal = new TransactionJournal();
        
        // ジャーナルの各項目に値を設定
        journal.LoyaltyProgramId = program.Id; // どのロイヤルティプログラムかを指定
        journal.MemberId = memberId; // どの会員の取引かを指定
        journal.JournalTypeId = getJournalTypeId('Accrual'); // ジャーナルのタイプを「付与(Accrual)」に設定
        journal.JournalSubTypeId = getJournalSubTypeId('Purchase'); // ジャーナルのサブタイプを「購入(Purchase)」に設定
        journal.ActivityDate = System.now(); // 活動日を現在の日時に設定
        journal.TransactionAmount = transactionAmount; // 取引金額を設定
        journal.Status = 'Pending'; // 処理前のステータスを「保留」に設定
        journal.ProcessingMode = 'Synchronous'; // ここでは同期モードを指定(通常は非同期が推奨される)
        
        // 作成したジャーナルをリストに追加
        List<TransactionJournal> journalsToProcess = new List<TransactionJournal>{ journal };
        
        try {
            // LoyaltyEngineのprocessTransactionJournalsメソッドを呼び出し、ジャーナルの処理を依頼
            // このメソッドは非同期で実行され、結果を返す
            List<LoyaltyManagement.ProcessJournalResult> results = LoyaltyManagement.LoyaltyEngine.processTransactionJournals(journalsToProcess);
            
            // 結果をループで確認
            for (LoyaltyManagement.ProcessJournalResult result : results) {
                if (result.isSuccess()) {
                    // 処理が成功した場合のログ
                    System.debug('Transaction Journal processed successfully. Journal Id: ' + result.getJournalId());
                } else {
                    // 処理が失敗した場合のエラーログ
                    System.debug('Error processing Transaction Journal. Journal Id: ' + result.getJournalId());
                    for (LoyaltyManagement.LoyaltyError error : result.getErrors()) {
                        System.debug('Error Code: ' + error.getErrorCode() + ', Message: ' + error.getMessage());
                    }
                }
            }
        } catch (Exception e) {
            // 例外が発生した場合の処理
            System.debug('An exception occurred: ' + e.getMessage());
        }
    }
    
    // ジャーナルタイプIDを取得するためのヘルパーメソッド
    private static Id getJournalTypeId(String journalTypeName) {
        JournalType journalType = [SELECT Id FROM JournalType WHERE Name = :journalTypeName LIMIT 1];
        return journalType.Id;
    }
    
    // ジャーナルサブタイプIDを取得するためのヘルパーメソッド
    private static Id getJournalSubTypeId(String journalSubTypeName) {
        JournalSubType journalSubType = [SELECT Id FROM JournalSubType WHERE Name = :journalSubTypeName LIMIT 1];
        return journalSubType.Id;
    }
}

上記のコードは、developer.salesforce.com の公式ドキュメントにあるLoyaltyEngineクラスの用例に基づいています。このコードをApexクラスとして保存し、例えばAnonymous Apexウィンドウから以下のように呼び出すことで実行できます。

// このコードを実行する前に、'memberId'を実際のLoyaltyProgramMemberのIDに置き換えてください
Id memberId = '0lM....'; // テスト用の会員ID
ProcessLoyaltyAccrual.processPurchaseTransaction(memberId, 'Cloud Kicks Inner Circle', 150.00);

注意事項

Apexを使用してLoyalty Managementをカスタマイズする際には、いくつかの重要な点に注意する必要があります。

権限 (Permissions)

Apexコードを実行するユーザーには、関連オブジェクトへのアクセス権が必要です。具体的には、TransactionJournalLoyaltyProgramLoyaltyProgramMemberなどのオブジェクトに対する参照・作成権限が求められます。通常は、「Loyalty Management - Admin」や「Loyalty Management - User」といった標準のPermission Set(権限セット)を割り当てるか、必要な権限を含むカスタム権限セットを作成します。

API制限とガバナ制限 (API Limits and Governor Limits)

Salesforceプラットフォームには、1つのトランザクション内で実行できるSOQLクエリの数やDMLステートメントの数に上限(ガバナ制限)があります。大量の取引を一度に処理しようとすると、これらの制限に抵触する可能性があります。

  • 一括処理(Bulkification): Apexコードは常に複数のレコードを処理できるように設計してください。トリガーであればTrigger.newを、カスタムサービスであればリストを引数に取るようにします。
  • 非同期処理の活用: 大量のデータを扱う場合は、Queueable ApexやBatch Apex(バッチApex)を使用して処理を非同期に分割し、ガバナ制限を回避することがベストプラクティスです。LoyaltyEngine自体が非同期で動作するため、呼び出し側のトランザクションの負荷は低いですが、その前段のデータ準備処理が重い場合は非同期化を検討してください。

エラー処理 (Error Handling)

processTransactionJournalsメソッドは、処理が失敗しても例外をスローするとは限りません。代わりに、LoyaltyManagement.ProcessJournalResultオブジェクトのリストを返します。各オブジェクトのisSuccess()メソッドをチェックし、失敗している場合はgetErrors()メソッドで詳細なエラー情報を取得して、ログに記録したり、管理者に通知したりする堅牢なエラーハンドリングロジックを実装することが不可欠です。

テストカバレッジ (Test Coverage)

本番環境にApexコードをデプロイするには、75%以上のテストカバレッジが必要です。Loyalty Managementのロジックをテストする際は、Test.startTest()Test.stopTest()を使用して非同期処理の実行を保証し、処理結果をアサーションで検証する必要があります。また、LoyaltyEngineの実際の処理はモック化できないため、テストデータとして必要なロイヤルティプログラムやメンバーのデータを作成することが重要です。


まとめとベストプラクティス

Salesforce Loyalty Managementは、Apexを活用することで、標準機能だけでは実現が難しい複雑でユニークなロイヤルティプログラムを構築できる、非常に柔軟なプラットフォームです。

開発者として成功するためのベストプラクティスは以下の通りです。

  1. 宣言的アプローチを優先: まずはFlowや標準のルール設定で要件を満たせないか検討します。コードはメンテナンスコストが高くなるため、最後の手段として利用するのが賢明です。
  2. 処理をモジュール化する: ロイヤルティ関連のロジックは、再利用可能なサービスクラスに集約します。これにより、コードの保守性が向上し、一貫した処理が保証されます。
  3. 非同期パターンを積極的に採用: 特に外部システムとの連携や大量データ処理が伴う場合は、Platform Eventを起点としたQueueable ApexやBatch Apexを組み合わせることで、スケーラブルで回復力のあるソリューションを設計します。
  4. 冪等性(Idempotency)を確保する: 外部システムからの連携では、同じリクエストが複数回送信される可能性があります。システムが同じリクエストを複数回受信しても、結果が常に同じになるように(例えば、ポイントが二重に付与されないように)、設計段階で冪等性を考慮することが重要です。

TransactionJournalLoyaltyEngineの仕組みを正しく理解し、プラットフォームのベストプラクティスに従うことで、Salesforce開発者はビジネスの価値を最大化する強力なロイヤルティソリューションを提供できるでしょう。

コメント