データ連携

自前のWebサーバーに設置したフォームで登録された情報をSaleforceに登録するにはどうしたらいいでしょうか?

  • Webサーバー側でフォーム登録時に定型メールを作成し、Salesforceのメールサービス機能で連携する
  • SalesforceのREST APIまたはSOAP APIを使い、Webサーバー側でフォーム登録時にAPI経由で登録する
  • Salesforceのメールtoリード、メールtoケースの機能を利用する
  • Pardotを導入する

どの方法でも可能ですが、フォームの見栄えは重視し、かつ、あまり費用を掛けずに実現するには、前の1つが候補となります。掛かる手間は同じくらいです。

今時、システム間の連携と言えば、REST APIを使うことが多いので、2番目の方法をご紹介したいと思います。

証明書を作成する

SalesforceのREST APIを使うには、事前に認証を行う必要があります。認証の方法はいくつかありますが、サーバー同士の連携を実現したいので、JWTを使ったOAuth連携を使います。

そのためには、デジタル証明書が必要です。openssl コマンドで自己証明書を作成できますので、それを利用します。

# 証明書を保存するフォルダを作成する
>> mkdir cert
>> cd cert

# RSA非公開鍵を生成する
>> openssl genrsa -des3 -passout pass:<<任意のパスワード>> -out server.pass.key 2048
Generating RSA private key, 2048 bit long modulus
........................+++
..................................................+++
e is 65537 (0x10001)

# 鍵ファイルを作成する
>> openssl rsa -passin pass:<<任意のパスワード>> -in server.pass.key -out server.key
writing RSA key

# RSA非公開鍵ファイルを削除する
>> rm server.pass.key 

# 証明書を作成する
>> openssl req -new -key server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:Japan
string is too long, it needs to be less than  2 bytes long
Country Name (2 letter code) []:jp
State or Province Name (full name) []:iwate
Locality Name (eg, city) []:takizawa
Organization Name (eg, company) []:pitadigi
Organizational Unit Name (eg, section) []:.
Common Name (eg, fully qualified host name) []:.
Email Address []:.

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:

# SSL証明書を作成する
>> openssl x509 -req -sha256 -days 365 -in server.csr -signkey server.key -out server.crt
Signature ok
subject=/C=jp/ST=iwate/L=takizawa/O=pitadigi
Getting Private key

# 作成したファイルを確認する
>> ls
server.crt      server.csr      server.key

Salesforceで設定する

接続アプリケーションを作成する

次にSalesforceで接続アプリケーションを作成します。設定-アプリケーション-アプリケーションマネージャーを開き、新規接続アプリケーションをクリックします。

OAuth設定の有効化ON
コールバックURLJWTの場合は不要ですが、必須項目なので「https://localhost:8443/RestTest/oauth/_callback」でOK
デジタル署名を使用ONにして作成した証明書(server.crt)を選択
選択したOAuth範囲データのアクセスと管理
ユーザに代わっていつでも要求を実行
Webサーバフローの秘密が必要ON

接続アプリケーションを設定する

作成した接続アプリケーションの設定を変更します。設定-アプリケーション接続アプリケーション-接続アプリケーションを管理するを選択します。

作成した接続アプリケーションを開き、以下の設定を行います。

許可されているユーザサーバー同士の連携で利用しますので、使用するユーザは限定した方がいいため、管理者が承認したユーザのみで利用できるようにします。
「管理者が承認したユーザは事前承認済み」を選択します。
プロファイル使用するユーザが属しているプロファイルを選択します。ここではシステム管理者としていますが、より特定するには専用のプロファイルを作成した方がいいです。

Apexクラスを呼び出す側のプログラムを準備する

REST APIでApexクラスを呼び出す手順は以下の通りです。

  • 作成した秘密鍵を使ってJWTを作成する
  • 作成したJWTを使ってSalesforceに認証を要求しアクセストークンを取得する
  • 取得したアクセストークンを使ってApexクラスを呼び出す

今回はPHPで実装してみます(全く使ったことはありませんが・・・)

JWTを作成する

JWTを作成するためのライブラリは、lcobucci/jwtを使います。

必要なライブラリをインストールする

composerを使って、必要なライブラリをインストールします。

>> composer require lcobucci/jwt

JWTを作るコードを書く

JWTを作るコードを書きます。

SalesforceのヘルプにJWTに関するものがあります。ここを見るとJWTのペーロードに何を設定するかが書いてあります。

https://help.salesforce.com/articleView?id=remoteaccess_oauth_jwt_flow.htm&type=5

これを見ながらJWTを作成します。ログインURLは使用する環境によって異なります。

<?php

require_once './vendor/autoload.php';

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;

// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// コンシューマ鍵
define('CLIENT_ID', <<接続アプリケーションのコンシューマ鍵>>);
// ユーザID
define('USER_ID', 'xxxxx@example.com');

function createjwt() {

    $signer = new Sha256();
    $privateKey = new Key('file://cert/server.key');
    $time = time();
    
    $token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
                            ->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
                            ->relatedTo(USER_ID) // sub: SalesforceユーザID
                            ->expiresAt($time + 3 * 60) // exp: 3分以内
                            ->getToken($signer,  $privateKey);

    return $token;
}

$jwt = createjwt();

echo $jwt;

Salesforceにアクセスする

認証を行う

作成したJWTを使ってSalesforceに認証を投げるコードを書きます。

REST APIを呼び出すにはいくつかの認証がサポートされています。JWTを使うメリットは以下の通りです。

  • ユーザのパスワードを使わないため、セキュアな情報を渡す必要がない
  • 証明書を使うため、パスワードよりセキュア
  • 標準的なテクノロジーを使うので、他のAPIを使う時に役立つ

これで、アクセスするURLと使用するBearerトークンが取得できます。

<?php

require_once './vendor/autoload.php';

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;

// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// 認証URL
define('AUTH_URL', LOGIN_URL . '/services/oauth2/token');
// コンシューマ鍵
define('CLIENT_ID', <<接続アプリケーションのコンシューマ鍵>>);
// ユーザID
define('USER_ID', 'xxxxxxx@example.com');
// 認証タイプ
define('GRANT_TYPE', 'urn:ietf:params:oauth:grant-type:jwt-bearer');


function createjwt() {

    $signer = new Sha256();
    $privateKey = new Key('file://cert/server.key');
    $time = time();
    
    $token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
                            ->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
                            ->relatedTo(USER_ID) // sub: SalesforceユーザID
                            ->expiresAt($time + 3 * 60) // exp: 3分以内
                            ->getToken($signer,  $privateKey);

    return $token;
}

function auth() {
    $jwt = createjwt();

    $post = array(
        'grant_type' => GRANT_TYPE,
        'assertion' => $jwt,
    );

    $curl = curl_init();
    curl_setopt( $curl, CURLOPT_URL, AUTH_URL );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
    curl_setopt( $curl, CURLOPT_POSTFIELDS, $post );
    $buf = curl_exec( $curl );
    if ( curl_errno( $curl ) ) {
        exit;
    }
    curl_close( $curl );
    
    $json = json_decode( $buf );

    $accinfo = array(
        // アクセスするためのURL
        'instance_url' => $json->instance_url,
        // アクセスするために使用するBearerトークン
        'access_token' => $json->access_token,
    );

    return $accinfo;
}

$accinfo = auth();

Apexクラスを呼び出す

取引先責任者に登録するApexクラスを作成します。

先頭で@RestResource(urlMapping='/resttest/*')と宣言します。これで認証で取得したURL+@RestResource(urlMapping='/resttest/*')でアクセスできます。

呼び出すメソッドは@HttpPostアノテーションで宣言します。パラメータは1項目ずつがを独立して宣言します。REST APIで呼び出す時は、JSONでパラメータを受け渡します。

{
   "firstname": "苗字",
   "lastname": "名",
   "email": "Eメールアドレス"
}
// アクセスするURL(/services/apexrest/resttest/)
@RestResource(urlMapping='/resttest/*')
global with sharing class RestTest {
    // POSTで呼び出すメソッド
    // パラメータはjsonで渡す
    @HttpPost
    global static String doPost(
        String firstname,   // 名
        String lastname,    // 苗字
        String email        // Eメール
    ) {
        // 取引先オブジェクトを作成する
        Contact con = new Contact();

        // 指定されたパラメータを設定する
        con.FirstName = firstname;
        con.LastName = lastname;
        con.Email = email;
    
        // 取引先責任者を保存する
        try {
            upsert con;
        }
        catch(DmlException e) {
            return e.getMessage();
        }

        return 'OK';
    }
}

Apexクラスを呼び出すには、認証で取得したURLとアクセストークンを使います。

ヘッダーContent-Type: application/json;charset=UTF-8
Authorization: Bearer 取得したアクセストークン
URL取得したURL /services/apexrest/resttest/
    $url = $accinfo['instance_url'] . '/services/apexrest/resttest/';

    $data = array(
        'firstname' => 'Koji',
        'lastname' => 'Matae',
        'email' => 'test@test.jp',
    );

    $header = array(
        'Content-Type: application/json;charset=UTF-8',
        'Authorization: Bearer ' . $accinfo['access_token'],
    );

    $curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
	curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
	curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true );
    curl_setopt($curl, CURLOPT_VERBOSE, true);
	$result = curl_exec($curl);
    curl_close($curl);
    
    echo var_dump($result);

これで、REST API経由で取引先を登録するApexクラスを呼び出す事ができます。

REST API経由でSOQLを実行してデータを取得、更新することも可能です。

SOQLを直接呼び出すと、REST API経由のアクセスのバリエーションが必要以上に増え、メンテナンス性が低下する可能性があります。

こうした状況を避けるためにも、Apexクラスを作成し、その中で必要な処理を書いてREST APIで呼び出すことをオススメします。

最後に、PHPの全ソースです。

<?php

require_once './vendor/autoload.php';

use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Signer\Key;
use Lcobucci\JWT\Signer\Rsa\Sha256;

// ログインURL
// 本番: https://login.salesforce.com
// Sandbox: https://test.login.salesforce.com
// スクラッチ組織: https://test.saleforce.com
define('LOGIN_URL', 'https://test.salesforce.com');
// 認証URL
define('AUTH_URL', LOGIN_URL . '/services/oauth2/token');
// コンシューマ鍵
define('CLIENT_ID', <<コンシューマ鍵>>);
// ユーザID
define('USER_ID', 'test@example.com');
// 認証タイプ
define('GRANT_TYPE', 'urn:ietf:params:oauth:grant-type:jwt-bearer');

function createjwt() {

    $signer = new Sha256();
    $privateKey = new Key('file://cert/server.key');
    $time = time();
    
    $token = (new Builder())->issuedBy(CLIENT_ID) // iss: コンシューマ鍵
                            ->permittedFor(LOGIN_URL) // aud: SalesforceログインURL
                            ->relatedTo(USER_ID) // sub: SalesforceユーザID
                            ->expiresAt($time + 3 * 60) // exp: 3分以内
                            ->getToken($signer,  $privateKey);

    return $token;
}

function auth() {
    $jwt = createjwt();

    $post = array(
        'grant_type' => GRANT_TYPE,
        'assertion' => $jwt,
    );

    $curl = curl_init();
    curl_setopt( $curl, CURLOPT_URL, AUTH_URL );
    curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
    curl_setopt( $curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1 );
    curl_setopt( $curl, CURLOPT_POSTFIELDS, $post );
    $buf = curl_exec( $curl );
    if ( curl_errno( $curl ) ) {
        exit;
    }
    curl_close( $curl );
    
    $json = json_decode( $buf );

    $accinfo = array(
        // アクセスするためのURL
        'instance_url' => $json->instance_url,
        // アクセスするために使用するBearerトークン
        'access_token' => $json->access_token,
    );

    return $accinfo;
}

function callapex($accinfo){
    $url = $accinfo['instance_url'] . '/services/apexrest/resttest/';

    $data = array(
        'firstname' => 'Koji',
        'lastname' => 'Matae',
        'email' => 'test@test.jp',
    );

    $header = array(
        'Content-Type: application/json;charset=UTF-8',
        'Authorization: Bearer ' . $accinfo['access_token'],
    );

    $curl = curl_init();
	curl_setopt($curl, CURLOPT_URL, $url);
    curl_setopt($curl, CURLOPT_HEADER, false);
    curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
	curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
	curl_setopt($curl, CURLOPT_POST, true);
    curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, true );
    curl_setopt($curl, CURLOPT_VERBOSE, true);
	$result = curl_exec($curl);
    curl_close($curl);
    
    echo var_dump($result);
}

$accinfo = auth();

callapex($accinfo);