SalesforceからHULFT Squareを動かしてみた

# ミッション
### Salesforceから、HULFT Squareのスクリプトを実行する

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

# HULFT Square（サービス側）の設定
HULFT SquareのAPIマネジメントは、プログラミング知識がなくても、データ連携のためのAPIを簡単に作成・公開できます
具体的には、以下の3つの手順で設定が完了します
1. HTTPメソッドと実行スクリプトの紐づけ（APIプロジェクト）
2. アクセス権限の設定（APIクライアント）
3. エンドポイントの公開（REST APIジョブ）

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

https://www.hulft.com/help/ja-jp/HULFTSquare/Content/TOP/Tutorial/tutorial_Bearer.htm

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

### Bearer認証を使用してAPIを呼び出す
上記実現のために、**フロー**と**Apexクラス**を利用します

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

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

::: note
#### JavaScriptの直接記述は（classic互換で簡単にできちゃうけど）非推奨！
Salesforceは、ボタンやリンクに直接JavaScriptを書けますが、classic互換の機能です
ボタン（リンク）の設定画面の動作でJavaScriptを選択すると、非推奨のメッセージが表示されます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/4e01ee81-bc35-4458-a714-4bc31e3c6446.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ジョブのエンドポイントを実行する`

::: note
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;
        }
    }
}
```

::: note info
|// ★ポイント: request.requestBody が null の場合は setBody を呼び出さない
|:-|

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

## sandboxにログインし設定画面へ
ユーザ名とパスワードでログイン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/cd82a01b-6cb7-4b3d-84ff-c78ca0086f37.png)
右上の歯車マークから設定を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/6b58cb73-f44e-4e7b-84b1-c6a6da13e352.png)
設定画面が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/4b3c6946-034b-4bdc-a0ea-2f565de4d238.png)

## Apexクラスをsandboxに登録
設定画面左のクイック検索で、**Apex**を検索
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/c0b04e64-4bdd-4258-b4ab-35a7016ac0d9.png)

Apexクラスを選択し、（一覧上部の小さな）**新規**ボタンを選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/8e9721d5-330b-48e8-a64d-9f49757b0141.png)

作成したApexコード（ここでは、HulftSquareLoginService）を...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/f12e79ef-055a-472e-8480-358b8043bb63.png)

まるっと貼り付けて**save**ボタンを選択（Apexクラス名はコードから自動設定）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/9ab6ecaa-ec38-4fa6-a2a6-02516ec47c29.png)

HulftSquareLoginServiceを登録しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/294ef59a-0e2c-496c-ba35-e91302d33746.png)

残り2つも同様に、計3つ登録します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/4ff9bbbc-b047-415f-aae1-8c35afa6024a.png)

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

**フロー：HSQApiService**
実行時に必要な値を変数に代入後、上記01～03を順次実行し、レスポンスを画面に表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/099eff4d-7bc2-4404-9007-bf9cf766c44e.png)

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

#### 要素：Variable1（変数割り当て）
ログインユーザ（メールアドレス）、パスワード、リフレッシュトークン、エンドポイントをフロー変数に代入します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/17b8cbd4-ad91-45ae-9d14-37406d5c4656.png)

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

#### 要素：HULFTSquareLoginAPI（Apexアクション）
Login APIでアクセストークンを取得
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/9aae6fb6-16b7-4f22-8107-bc8827157906.png)

#### 要素：HULFTSquareTokenRefresh（Apexアクション）
アクセストークンを更新
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/fa3ee229-29b3-4b1c-90b7-2fa93cbea188.png)

#### 要素：HULFTSquareJobExecute（Apexアクション）
REST APIジョブのエンドポイントを実行（HTTPメソッドがGETの場合）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/321e57de-f84f-4510-8482-9dd374bd0340.png)

#### DisplayResponse（画面表示）
HULFT Squareのスクリプト実行結果を画面に表示
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/4c0be099-1817-444d-9a2d-b1454462a632.png)

# デバッグ実行
フローが完成したのでデバッグボタンで実行（デバッグオプションは初期値）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/9f5df76f-1a5f-4394-a410-03a46b9b3a38.png)

無事正常終了（レスポンスは、スクリプトの実行IDと処理結果）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/8bef876d-f9aa-4f5b-a072-f4ea40960da6.png)

# カスタムリンク
作成したフローを画面から起動するためにカスタムリンクを作成します
対象のオブジェクト（今回は取引先）を検索して選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/8a4f59ac-ed48-4167-ad22-5e2d7a410313.png)

左側のメニューから**ボタン、リンク、およびアクション**を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/2dbc1d3d-5833-4706-bcfb-9138f8310dc1.png)

画面右上の**新規ボタンまたはリンク**を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/6486fe40-1b5c-4c1a-b5b5-3985f0dab025.png)

以下を設定後、保存ボタンを選択
　表示ラベル：**HULFT Squareリクエスト**
　名前：**hulft_square_request**
　説明：（任意）
　表示種別：**詳細ページリンク**
　動作：**新規ウィンドウに表示**
　コンテンツリソース：**URL**
　コンテンツ：**/flow/HsqApiService**（呼び出すフローの名前）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/5dfb32ce-a596-46da-9320-00924d27bb66.png)

カスタムリンクHULFT Squareリクエストが登録できました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/00bbd641-40fa-42f2-a7bc-8eb08b798804.png)

# ページレイアウト
左側のメニューから**ページレイアウト**を選択し、**Account Layout**を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/8755a920-5d14-4a85-85fa-566d39f25314.png)

カスタムリンクを選択肢、**HULFT Squareリクエスト**を、画面下方の**カスタムリンク
（ヘダーを表示しない）** にドラッグ＆ドロップ、保存ボタンを選択して登録
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/c9cd8e36-0f25-481b-b753-8258d30002c3.png)

取引先画面で任意の取引先を選択し、説明欄の下に**HULFT Squareリクエスト**の表示を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/f353ed57-b82e-4ee7-8fa8-59d36e809485.png)

## リモートサイトの設定
**新規リモートサイト**ボタンを選択し、HULFT Squareエンドポイントのサイトアドレスを追加
・ログインAPI用：https://app.square.hulft.com
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/e16af0b0-c9c0-464b-89a3-a5d88cb5ad91.png)

・ジョブ実行API用：https://<自社ドメイン名>.square.hulft.com
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/77871a82-713d-46ee-9107-540761e6fed3.png)

## リンクからフローを実行
取引先画面の**HULFT Squareリクエスト**リンクを選択しHULFT Squareにリクエストを送信
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/b2227c80-12bd-4458-a51a-0dfffbb6aaab.png)

## 実行結果
HULFT Squareから返されるメッセージ、レスポンスボディ、ステータスコードを画面に表示
無事レスポンスを受け取ることが出来ました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/207446/48ba61d2-23e0-4e2c-bb9d-b190d8546f7d.png)

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

バッタもん