0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ミッション

Salesforceから、HULFT Squareのスクリプトを実行する

こんにちは、あるいはこんばんは、バッタもんです
今回はSalesforceの画面にリンクを作成し、HULFT Squareのスクリプトを動かしてみました
たやすくできると始めたところが、多くの敵に遭遇したので攻略法を書き残します
(貴方がウo〇〇〇スの〇昏さんなら、必要のない知識ですのでスルーしてください)
なお、スクショの一部をマスキングしていますので、ご了承ください

HULFT Square(サービス側)の設定

HULFT SquareのAPIマネジメントは、プログラミング知識がなくても、データ連携のためのAPIを簡単に作成・公開できます
具体的には、以下の3つの手順で設定が完了します

  1. HTTPメソッドと実行スクリプトの紐づけ(APIプロジェクト)
  2. アクセス権限の設定(APIクライアント)
  3. エンドポイントの公開(REST APIジョブ)

詳しくは、以下をご覧ください

Salesforce(クライアント側)の設定

この記事の内容を実践される場合は(サポートが充実している)本番ライセンスのクローンsandboxがオススメですが、デベロッパーライセンスでもOKです
(無料トライアルはApexクラス非対応なのでNG)

Bearer認証を使用してAPIを呼び出す

上記実現のために、フローApexクラスを利用します

開始当初はフローだけで済ませたいと考えていましたが、Bearer認証を採用するHULFT Squareの場合、SalesforceのHTTPコールアウトで必要な外部ログイン情報(指定ログイン情報)のカスタムヘッダーを可変で扱うことが難しく断念
Try&Errorの結果、フロー+Apexクラスが最も現実的と判断しました

なお、JavaScriptだけでも実現できる可能性がありますが、下記理由から除外しました

JavaScriptの直接記述は(classic互換で簡単にできちゃうけど)非推奨!

Salesforceは、ボタンやリンクに直接JavaScriptを書けますが、classic互換の機能です
ボタン(リンク)の設定画面の動作でJavaScriptを選択すると、非推奨のメッセージが表示されます

image.png

※詳しくは、以下のTrailheadサイトをご参照ください
Lightning の活用および JavaScript ボタンからの移行
https://trailhead.salesforce.com/ja/content/learn/modules/lex_javascript_button_migration/lex_javascript_button_migration_intro

Apexクラスの作成

以下の3つのApexクラスを作成します(テキストエディタで編集後、Salesforceに登録)
01. Login APIでアクセストークンを取得する
02. アクセストークンを更新する
03. REST APIジョブのエンドポイントを実行する

Apexコードの作成は、Google Geminiさんにお願いしました
ChatGPTと比較してApexの生成の精度が高く、デバッグに協力的だったのが理由ですが、推しポイントは、プロンプトにURLリンクが貼れることです

それぞれのApexコードを掲載します

01. Login APIでアクセストークンを取得する

Apexクラス:HulftSquareLoginService

/**
 * @description HULFT SquareのLogin APIを呼び出し、アクセストークンを取得します。
 */
public class HulftSquareLoginService {

    /**
     * @description Flowから呼び出し可能なLogin API実行メソッド。
     * @param requests ログイン情報を含むリクエストのリスト。通常は単一のリクエスト。
     * @return 処理結果(アクセストークン、成功フラグ、メッセージ)のリスト。
     */
    @InvocableMethod(label='HULFT Square Login API (アクセストークン取得)' description='HULFT Square Login APIを呼び出し、アクセストークンを取得します。')
    public static List<HulftSquareAuthResponse> getAccessToken(List<HulftSquareAuthRequest> requests) {
        List<HulftSquareAuthResponse> results = new List<HulftSquareAuthResponse>();

        if (requests == null || requests.isEmpty()) {
            results.add(new HulftSquareAuthResponse(false, null, null, 'リクエストデータがありません。', 400));
            return results;
        }

        HulftSquareAuthRequest request = requests[0]; // フローからの呼び出しは通常単一のリクエストを想定

        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(request.loginUrl); // HULFT Square Login APIのエンドポイントURL
            req.setMethod('POST');
            req.setHeader('Content-Type', 'application/json');
            req.setBody('{"email": "' + request.username + '", "password": "' + request.password + '"}');

            Http http = new Http();
            HttpResponse res = http.send(req);

            if (res.getStatusCode() == 200) {
                Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
                String accessToken = (String) responseMap.get('accessToken'); // HULFT Squareのキー名に合わせる
                String refreshToken = (String) responseMap.get('refreshToken'); // 必要であればリフレッシュトークンも取得
                results.add(new HulftSquareAuthResponse(true, accessToken, refreshToken, 'アクセストークンが正常に取得されました。', res.getStatusCode()));
            } else {
                results.add(new HulftSquareAuthResponse(false, null, null, 'Login APIの実行に失敗しました。ステータスコード: ' + res.getStatusCode() + ', レスポンス: ' + res.getBody(), res.getStatusCode()));
            }
        } catch (Exception e) {
            results.add(new HulftSquareAuthResponse(false, null, null, 'エラーが発生しました: ' + e.getMessage(), 500));
        }

        return results;
    }

    /**
     * @description フローからの入力パラメータを定義する内部クラス。
     */
    public class HulftSquareAuthRequest {
        @InvocableVariable(label='Login API URL' description='HULFT Square Login APIのエンドポイントURL')
        public String loginUrl;
        @InvocableVariable(label='Username' description='HULFT Squareのログインに使用するユーザー名(メールアドレス)')
        public String username;
        @InvocableVariable(label='Password' description='HULFT Squareのログインに使用するパスワード')
        public String password;
    }

    /**
     * @description フローへの出力結果を定義する内部クラス。
     * アクセストークン、リフレッシュトークン、成功フラグ、メッセージを返します。
     */
    public class HulftSquareAuthResponse {
        @InvocableVariable(label='成功' description='APIコールが成功したか')
        public Boolean isSuccess;
        @InvocableVariable(label='アクセストークン' description='取得したアクセストークン')
        public String accessToken;
        @InvocableVariable(label='リフレッシュトークン' description='取得したリフレッシュトークン')
        public String refreshToken;
        @InvocableVariable(label='メッセージ' description='APIコール結果に関するメッセージ')
        public String message;
        @InvocableVariable(label='ステータスコード' description='HTTPレスポンスのステータスコード')
        public Integer statusCode;

        public HulftSquareAuthResponse(Boolean isSuccess, String accessToken, String refreshToken, String message, Integer statusCode) {
            this.isSuccess = isSuccess;
            this.accessToken = accessToken;
            this.refreshToken = refreshToken;
            this.message = message;
            this.statusCode = statusCode;
        }
    }
}

02. アクセストークンを更新する

Apexクラス:HulftSquareTokenRefreshService

/**
 * @description HULFT Squareのアクセストークン更新APIを呼び出し、アクセストークンを更新します。
 * 認証ヘッダーに現在のアクセストークンを使用するように修正。
 */
public class HulftSquareTokenRefreshService {

    /**
     * @description Flowから呼び出し可能なアクセストークン更新メソッド。
     * @param requests リフレッシュトークン情報を含むリクエストのリスト。通常は単一のリクエスト。
     * @return 処理結果(新しいアクセストークン、成功フラグ、メッセージ)のリスト。
     */
    @InvocableMethod(label='HULFT Square アクセストークン更新' description='HULFT Squareのアクセストークン更新APIを呼び出し、新しいアクセストークンを取得します。認証ヘッダーに現在のアクセストークンを使用します。')
    public static List<HulftSquareAuthResponse> refreshAccessToken(List<HulftSquareRefreshRequest> requests) {
        List<HulftSquareAuthResponse> results = new List<HulftSquareAuthResponse>();

        if (requests == null || requests.isEmpty()) {
            results.add(new HulftSquareAuthResponse(false, null, null, 'リクエストデータがありません。', 400));
            return results;
        }

        HulftSquareRefreshRequest request = requests[0]; // フローからの呼び出しは通常単一のリクエストを想定

        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(request.refreshUrl); // HULFT Squareアクセストークン更新APIのエンドポイントURL
            req.setMethod('PUT'); // HULFT SquareのAPI仕様に合わせる(例: PUT)
            req.setHeader('Content-Type', 'application/json');
            
            // 既存のアクセストークンを認証ヘッダーとして追加
            if (request.currentAccessToken != null) {
                req.setHeader('Authorization', 'Bearer ' + request.currentAccessToken); 
            }

            req.setBody('{"refreshToken": "' + request.refreshToken + '"}'); // HULFT SquareのJSON形式に合わせる

            Http http = new Http();
            HttpResponse res = http.send(req);

            if (res.getStatusCode() == 200) {
                Map<String, Object> responseMap = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
                String newAccessToken = (String) responseMap.get('accessToken'); // HULFT Squareのキー名に合わせる
                String newRefreshToken = (String) responseMap.get('refreshToken'); // 必要であれば新しいリフレッシュトークンも取得
                results.add(new HulftSquareAuthResponse(true, newAccessToken, newRefreshToken, 'アクセストークンが正常に更新されました。', res.getStatusCode()));
            } else {
                results.add(new HulftSquareAuthResponse(false, null, null, 'アクセストークンの更新に失敗しました。ステータスコード: ' + res.getStatusCode() + ', レスポンス: ' + res.getBody(), res.getStatusCode()));
            }
        } catch (Exception e) {
            results.add(new HulftSquareAuthResponse(false, null, null, 'エラーが発生しました: ' + e.getMessage(), 500));
        }

        return results;
    }

    /**
     * @description フローからの入力パラメータを定義する内部クラス。
     * 現在のアクセストークンを認証ヘッダー用に含めます。
     */
    public class HulftSquareRefreshRequest {
        @InvocableVariable(label='Refresh API URL' description='HULFT Squareアクセストークン更新APIのエンドポイントURL')
        public String refreshUrl;
        @InvocableVariable(label='リフレッシュトークン' description='更新に使用するリフレッシュトークン')
        public String refreshToken;
        // 新しい入力変数
        @InvocableVariable(label='現在のアクセストークン' description='認証ヘッダーとして使用する現在のアクセストークン (Login APIで取得したもの)')
        public String currentAccessToken; 
    }
    
    /**
     * @description フローへの出力結果を定義する内部クラス。
     * アクセストークン、リフレッシュトークン、成功フラグ、メッセージを返します。
     */
    public class HulftSquareAuthResponse {
        @InvocableVariable(label='成功' description='APIコールが成功したか')
        public Boolean isSuccess;
        @InvocableVariable(label='アクセストークン' description='取得したアクセストークン')
        public String accessToken;
        @InvocableVariable(label='リフレッシュトークン' description='取得したリフレッシュトークン')
        public String refreshToken;
        @InvocableVariable(label='メッセージ' description='APIコール結果に関するメッセージ')
        public String message;
        @InvocableVariable(label='ステータスコード' description='HTTPレスポンスのステータスコード')
        public Integer statusCode;

        public HulftSquareAuthResponse(Boolean isSuccess, String accessToken, String refreshToken, String message, Integer statusCode) {
            this.isSuccess = isSuccess;
            this.accessToken = accessToken;
            this.refreshToken = refreshToken;
            this.message = message;
            this.statusCode = statusCode;
        }
    }
}

03. REST APIジョブのエンドポイントを実行する

Apexクラス:HulftSquareJobExecutor

/**
 * @description HULFT SquareのREST APIジョブのエンドポイントを実行します。
 * アクセストークンのチェックとデバッグログを実装しています。
 */
public class HulftSquareJobExecutor {

    /**
     * @description Flowから呼び出し可能なREST APIジョブ実行メソッド。
     * @param requests ジョブ実行情報を含むリクエストのリスト。通常は単一のリクエスト。
     * @return 処理結果(レスポンスボディ、成功フラグ、メッセージ、ステータスコード)のリスト。
     */
    @InvocableMethod(label='HULFT Square REST APIジョブ実行' description='HULft Square REST APIジョブのエンドポイントを実行します。')
    public static List<HulftSquareJobResponse> executeRestApiJob(List<HulftSquareJobRequest> requests) {
        List<HulftSquareJobResponse> results = new List<HulftSquareJobResponse>();

        // 実行ユーザーの情報をデバッグログに出力
        // System.debug('--- HulftSquareJobExecutor Start ---');
        // System.debug('実行ユーザーID: ' + UserInfo.getUserId());
        // System.debug('実行ユーザー名: ' + UserInfo.getUserName());
        // System.debug('実行ユーザーメール: ' + UserInfo.getUserEmail());

        if (requests == null || requests.isEmpty()) {
            results.add(new HulftSquareJobResponse(false, null, 'リクエストデータがありません。', 400));
            return results;
        }

        HulftSquareJobRequest request = requests[0]; // フローからの呼び出しは通常単一のリクエストを想定

        // デバッグログで入力値を確認
        // System.debug('HulftSquareJobExecutor - Job Endpoint URL: ' + request.jobEndpointUrl);
        // System.debug('HulftSquareJobExecutor - HTTP Method: ' + request.httpMethod);
        // System.debug('HulftSquareJobExecutor - Request Body (raw): ' + request.requestBody); // 修正後の確認のため、元の値をログ出力
        
        // アクセストークンがNullまたは空でないかを確認
        if (String.isBlank(request.accessToken)) {
        // System.debug('HulftSquareJobExecutor - ERROR: Access Token is null or blank.');
            results.add(new HulftSquareJobResponse(false, null, 'エラー: アクセストークンが提供されていません。', 400));
            return results;
        }
        // System.debug('HulftSquareJobExecutor - Access Token (first 10 chars): ' + request.accessToken.substring(0, Math.min(request.accessToken.length(), 10)) + '...');

        try {
            HttpRequest req = new HttpRequest();
            req.setEndpoint(request.jobEndpointUrl); 
            
            String method = (request.httpMethod != null && !String.isBlank(request.httpMethod)) ? request.httpMethod.toUpperCase() : 'POST';
            req.setMethod(method); 

            req.setHeader('Authorization', 'Bearer ' + request.accessToken.trim()); 
            // System.debug('HulftSquareJobExecutor - Authorization Header: Bearer ' + request.accessToken.trim().substring(0, Math.min(request.accessToken.trim().length(), 10)) + '...');

            req.setHeader('Content-Type', 'application/json');

            // ★ポイント: request.requestBody が null の場合は setBody を呼び出さない
            if (request.requestBody != null) {
                req.setBody(request.requestBody);
                System.debug('HulftSquareJobExecutor - Request Body sent: ' + req.getBody());
            } else {
                System.debug('HulftSquareJobExecutor - Request Body: Not set (null).');
            }

            Http http = new Http();
            HttpResponse res = http.send(req);

            System.debug('HulftSquareJobExecutor - Response Status Code: ' + res.getStatusCode());
            System.debug('HulftSquareJobExecutor - Response Body: ' + res.getBody());

            if (res.getStatusCode() >= 200 && res.getStatusCode() < 300) { 
                results.add(new HulftSquareJobResponse(true, res.getBody(), 'REST APIジョブが正常に実行されました。', res.getStatusCode()));
            } else {
                results.add(new HulftSquareJobResponse(false, res.getBody(), 'REST APIジョブの実行に失敗しました。ステータスコード: ' + res.getStatusCode(), res.getStatusCode()));
            }
        } catch (Exception e) {
            System.debug('HulftSquareJobExecutor - EXCEPTION: ' + e.getMessage() + ' at line ' + e.getLineNumber());
            results.add(new HulftSquareJobResponse(false, null, 'エラーが発生しました: ' + e.getMessage(), 500));
        }

        System.debug('--- HulftSquareJobExecutor End ---');
        return results;
    }

    // HulftSquareJobRequest および HulftSquareJobResponse クラスの定義
    public class HulftSquareJobRequest {
        @InvocableVariable(label='Job Endpoint URL' description='HULFT Square REST APIジョブの完全なエンドポイントURL')
        public String jobEndpointUrl;
        @InvocableVariable(label='アクセストークン' description='認証に使用するアクセストークン')
        public String accessToken;
        @InvocableVariable(label='HTTPメソッド' description='ジョブ実行に使用するHTTPメソッド(GET, POST, PUT, DELETEなど)。デフォルトはPOST。')
        public String httpMethod;
        @InvocableVariable(label='リクエストボディ' description='ジョブ実行に含めるリクエストボディ(JSON形式の文字列)')
        public String requestBody;
    }

    public class HulftSquareJobResponse {
        @InvocableVariable(label='成功' description='APIコールが成功したか')
        public Boolean isSuccess;
        @InvocableVariable(label='レスポンスボディ' description='APIからの生のレスポンスボディ')
        public String responseBody;
        @InvocableVariable(label='メッセージ' description='APIコール結果に関するメッセージ')
        public String message;
        @InvocableVariable(label='ステータスコード' description='HTTPレスポンスのステータスコード')
        public Integer statusCode;

        public HulftSquareJobResponse(Boolean isSuccess, String responseBody, String message, Integer statusCode) {
            this.isSuccess = isSuccess;
            this.responseBody = responseBody;
            this.message = message;
            this.statusCode = statusCode;
        }
    }
}
// ★ポイント: request.requestBody が null の場合は setBody を呼び出さない

※コードの中ほどにある上記コメントですが、生成直後は、GetなどRequest Bodyが必要ない場合でも"{ }"が出力される仕様でした
この仕様でリクエストを送信すると、HULFT Squareでは403 Forbeddenを返すのでご注意を!

sandboxにログインし設定画面へ

ユーザ名とパスワードでログイン
image.png
右上の歯車マークから設定を選択
image.png
設定画面が表示される
image.png

Apexクラスをsandboxに登録

設定画面左のクイック検索で、Apexを検索
image.png

Apexクラスを選択し、(一覧上部の小さな)新規ボタンを選択
image.png

作成したApexコード(ここでは、HulftSquareLoginService)を...
image.png

まるっと貼り付けてsaveボタンを選択(Apexクラス名はコードから自動設定)
image.png

HulftSquareLoginServiceを登録しました
image.png

残り2つも同様に、計3つ登録します
image.png

フローの作成

3つのApexクラスを順に処理するフローを作成
01. Login APIでアクセストークンを取得する
02. アクセストークンを更新する
03. REST APIジョブのエンドポイントを実行する

フロー:HSQApiService
実行時に必要な値を変数に代入後、上記01~03を順次実行し、レスポンスを画面に表示
image.png

順次実行について
このフローは、01~03を連続実行しますが、アクセストークンの有効期限を考慮した設計で、APIコール数を最適化できます

要素:Variable1(変数割り当て)

ログインユーザ(メールアドレス)、パスワード、リフレッシュトークン、エンドポイントをフロー変数に代入します
image.png

秘匿性の高い情報の扱いについて
ここではフローに直接値を設定していますが、パスワードやリフレッシュトークンなどの秘匿性の高い情報は、セキュリティ維持のためフローに直接設定せず、カスタムメタデータ型の保護されたパッケージとして保管して、参照するフロー自体のセキュリティ(誰がフローを実行できるか)にも十分注意して設計してください

要素:HULFTSquareLoginAPI(Apexアクション)

Login APIでアクセストークンを取得
image.png

要素:HULFTSquareTokenRefresh(Apexアクション)

アクセストークンを更新
image.png

要素:HULFTSquareJobExecute(Apexアクション)

REST APIジョブのエンドポイントを実行(HTTPメソッドがGETの場合)
image.png

DisplayResponse(画面表示)

HULFT Squareのスクリプト実行結果を画面に表示
image.png

デバッグ実行

フローが完成したのでデバッグボタンで実行(デバッグオプションは初期値)
image.png

無事正常終了(レスポンスは、スクリプトの実行IDと処理結果)
image.png

カスタムリンク

作成したフローを画面から起動するためにカスタムリンクを作成します
対象のオブジェクト(今回は取引先)を検索して選択
image.png

左側のメニューからボタン、リンク、およびアクションを選択
image.png

画面右上の新規ボタンまたはリンクを選択
image.png

以下を設定後、保存ボタンを選択
 表示ラベル:HULFT Squareリクエスト
 名前:hulft_square_request
 説明:(任意)
 表示種別:詳細ページリンク
 動作:新規ウィンドウに表示
 コンテンツリソース:URL
 コンテンツ:/flow/HsqApiService(呼び出すフローの名前)
image.png

カスタムリンクHULFT Squareリクエストが登録できました
image.png

ページレイアウト

左側のメニューからページレイアウトを選択し、Account Layoutを選択
image.png

カスタムリンクを選択肢、HULFT Squareリクエストを、画面下方のカスタムリンク
(ヘダーを表示しない)
にドラッグ&ドロップ、保存ボタンを選択して登録
image.png

取引先画面で任意の取引先を選択し、説明欄の下にHULFT Squareリクエストの表示を確認
image.png

リモートサイトの設定

新規リモートサイトボタンを選択し、HULFT Squareエンドポイントのサイトアドレスを追加
・ログインAPI用:https://app.square.hulft.com
image.png

・ジョブ実行API用:https://<自社ドメイン名>.square.hulft.com
image.png

リンクからフローを実行

取引先画面のHULFT Squareリクエストリンクを選択しHULFT Squareにリクエストを送信
image.png

実行結果

HULFT Squareから返されるメッセージ、レスポンスボディ、ステータスコードを画面に表示
無事レスポンスを受け取ることが出来ました
image.png

おわりに

最後までお読みいただき、ありがとうございます
今回は、クライアントをSalesforceに限定した実現方法をご紹介しましたが、
HULFT SquareのAPIをコールできるクライアント(サービス)は多く存在します
今後も様々なクライアントから、HULFT Squareのスクリプトを動かしてみたいと思います
ではまた、いつか、どこかで...

バッタもん

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?