株式会社メドレーDeveloper Portal

2021-07-16

「ChatOps 稟議」×「電子契約締結の API 連携」でワークフローの生産性を追求した話

はじめに

はじめまして、コーポレートエンジニアの山下です。

2020 年にSlackを活用した ChatOps 稟議ワークフローを内製で開発したのですが、さらに、2021 年 4 月にこの Slack 稟議と電子契約システムであるクラウドサインを連携させて、電子契約をもっと便利に使い、生産性の向上を実現しましたのでお話しいたします。

まず、当社の稟議システムは2020 年 12 月の当社の記事のおさらいになりますが、稟議の作業が Slack 上で完結する、ChatOps による稟議ワークフローとなっております。本稿については 2021 年 7 月に執筆しておりますので丁度導入から 1 年程経過し、その間大きなトラブルも無く、今も当社の極めて迅速な意思決定の一助になっています。ChatOps による稟議ワークフローについては、直近、2021 年 6 月に LayerX 社がLayerX ワークフローの新機能として発表し、サービスとしても提供され、日経新聞でも取り上げられていることから、今現在のパラダイムとして、先進的で有効な一手法であったと再認識しております。

今回、新型コロナウイルス感染拡大防止に伴うリモートワークの加速という状況もあり、当社で 2021 年 4 月に電子契約システムとしてクラウドサインを導入しました。電子契約に限らず、契約押印作業は稟議の後続作業に当たるため、ただ導入して使用するのみならず、クラウドサインの API を利用して稟議上にあるデータを電子契約に送信させることでシームレスな連携を実現しています。本稿では当社が行ったシームレスな連携手法について詳細をご説明いたします。

TeamSpirit とクラウドサインの API 連携について

実装概要

弊社の稟議システムであるTeamSpiritとクラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。

20210715210552.png

処理内容の詳細は後ほど述べますが、概要としては TeamSprit(Apex)からクラウドサインの API をコールし、クラウドサイン上で作成した契約文書へ稟議に記載されている契約書ファイルや先方担当者等の情報を連携する仕組みとなっております。これにより契約担当者はクラウドサインにログイン後、下記の 3 ステップで先方に送信できるようになっています。

  1. 記載内容の確認
  2. 押印・署名箇所の設定
  3. 先方への送信

クラウドサインを使用して契約文書を一から作成する場合のユーザ作業と、当社で採用した API 連携行った場合のユーザ作業を比較したものが下記の表です。作業が半分程度削減されたことが分かります。

作業項番一から作成する場合API 連携を利用した当社の場合
1ログインログイン
2契約文書の作成(件名、契約文書としての宛名設定等)なし
3契約書ファイルのアップロードなし
4先方の送信先設定なし
5押印欄の設定押印欄の設定
6先方への送信先方への送信

実装

今回の開発で使用したクラウドサイン APIは下記の 5 つの API を使用しました (※以降、クラウドサイン API に倣い、変数を表現する場合は{}で括ります)。

API 種類使用用途
post /tokenアクセストークンの取得
post /document契約文書の作成
put /documents/{documentID}/attribute契約文書の作成で設定できない、細かい項目の設定
post /documents/{documentID}/filesファイルのアップロード
post /documents/{documentID}/participants先方の送信先設定

全体像で記載したクラウドサインの連携部について、上記の API を織り交ぜて詳細化すると下図のようになります。

20210715210621.png

実装方法としてはクラウドサイン API のリファレンスを参照し、テスト実行時に出力されるcurlコマンドを参考に同様のレスポンスを得るように Apex で HTTP リクエストを実装しました。アクセストークンの取得を例にとると下記のようになります。

API リファレンスでの curl コマンド例

curl -X 'POST' \
'https://api.cloudsign.jp/token' \
-H 'accept: application/json' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=xxxxxxyyyyyyzzzzzz'

Apex でのリクエスト実装例

HttpRequest req = new HttpRequest();
req.setMethod('POST');
req.setEndpoint('https://api.cloudsign.jp/token');
req.setHeader('accept', 'application/json');
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setBody('client_id=' + 'xxxxxxyyyyyyzzzzzz');

私自身、Apex もクラウドサイン API もこの案件を担当するまで触ったことがありませんでしたが、リクエストの試行から実装まで 2 週間かからない程度で実装することができました。

ただし、実装や運用にあたっては下記 2 点について注意が必要になります。

  1. Apex からクラウドサインへのファイルのアップロードは単純ではない
  2. アクセストークンの有効期限はクラウドサインでコントロールされる

1. Apex からクラウドサインへのファイルのアップロードは単純ではない

ファイルのアップロードについては今回使用した API の中で、唯一、テスト実行のcurlと Apex のリクエスト実装で差分が生まれます。まず、その差分を確認するために curl コマンド例と Apex のリクエスト実装例で header、body にセットしている値を比較してみます。

API リファレンスでの curl コマンド例

curl -X 'POST' \
'https://api.cloudsign.jp/documents/{document_id}/files' \
-H 'accept: application/json' \
-H 'Authorization: AAAAAABBBBBBCCCCCC' \
-H 'Content-Type: multipart/form-data' \
-F 'name=テスト' \
-F 'uploadfile=@テスト.pdf;type=application/pdf'

Apex でのリクエスト実装例

HttpRequest req = new HttpRequest();
req.setMethod('POST');
     req.setEndpoint('https://api.cloudsign.jp/documents/{document_id}/files');
req.setHeader('accept', 'application/json');
req.setHeader('Authorization', ‘AAAAAABBBBBBCCCCCC’);
req.setHeader('Content-Type', 'multipart/form-data; boundary={boundary}');  // ※1
req.setBodyAsBlob({multipartBody});  // ※2

主な違いは ※1, ※2 とコメントした部分になります。

Apex では HTTP リクエストの値を手で書いていくことになるので、テスト実行例のようにcurlがよしなに処理している部分(-F オプションの部分や Apex で記載している boundary)も実装しなければなりません。これが単純に実装できない理由になります。

boundary については multipart/form-data を送信する際に必要な境界でヘッダーでどの文字列が境界であるかを設定します。curlの-F オプションで定義していた文字列とファイル指定部分は、Apex でファイル(バイナリ)を扱うため、その body に含まれる文字列も含めて Blob 型で扱う必要があります(Content-Transfer-Encoding: base64に API 提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの Blob にする方法は下記になります。

  1. 「バイナリデータ」、「body の開始からバイナリデータまでの文字列」、「バイナリデータ以降から終端までの文字列」の 3 グループに分ける。
  2. 3 グループをそれぞれBase64で符号化する。
  3. 符号化した「バイナリデータ」と「body の開始からバイナリデータまでの文字列」について、Base64 のデータパディングを示す”=”が含まれないように改行コードで調整する。
  4. 「body の開始からバイナリデータまでの文字列」、「バイナリデータ」、「バイナリデータ以降から終端までの文字列」の順で結合する。
  5. 結合した Base64 のデータを復号して、一つの Blob とする。

2. アクセストークンの有効期限はクラウドサインでコントロールされる

アクセストークンやその有効期限は token API を発行した際のレスポンスとしてクラウドサインから発行されます。

発行されたレスポンス例

{
     "access_token": "AAAAAABBBBBBCCCCCC",
     "token_type": “xxxx”,
     "expires_in": 3600
}

このレスポンスの内、expires_in の値がトークンの有効期限になります。掲題の通り、有効期限の管理はクラウドサイン側で行われ、有効期限内に再度トークンのリクエストを行った場合、経過した時間だけ expires_in の値が小さくなった結果が返ってきて、access_token などは同じ値が取得されます。有効期限内に token API を再度実行した結果が下記になります。

有効期限切れ前に token API を発行した際のレスポンス例

{
     "access_token": "AAAAAABBBBBBCCCCCC",
     "token_type": “xxxx”,
     "expires_in": 762
}

一方、有効期限後にトークンのリクエストを実行すると、それまでと異なるアクセストークンを取得し、新しい有効期限が設定されます。

有効期限切れ後に token API を発行した際のレスポンス例

{
  "access_token": "XXXXXXYYYYYYZZZZZZ",
  "token_type": "xxxx",
  "expires_in": 3600
}

そのため、API 連携が一度動いた後、有効期限ぎりぎりでもう一度 API 連携が動いてしまった場合、タイミングが悪いと契約文書の作成から最終処理である先方の送信先設定までのプロセス内のどこかから、トークンの有効期限切れが発生する可能性が想定されます。実際に期限切れが発生した場合、発生時以降に発行したその回の API 連携処理が失敗します。

トークンの有効期限切れが発生した際、API リファレンスより HTTP ステータスコードが 401 かつエラー内容が”unauthorized”で応答されることから、当社ではこのエラーを受けた場合にトークンを再取得して処理をリトライするように実装しました。

押印文書作成を例にとると下記のような実装イメージになります。

//クラウドサイン上に押印文書を作成し、作成した文書 ID を取得する
public String getDocumentId(String authToken, String title, String message){

    ・・・中略・・・

    HTTPResponse res = http.send(req);

    if (res.getStatusCode() == 200){
        ・・・正常に終了した際の処理・・・
    }

    //タイミングが悪く token がタイムアウトした場合、トークンを取得し直して、リトライする
    else if (res.getStatusCode() == 401){
        //レスポンスの内容を確認するため、エラーレスポンスの中身を取得する
        Map<String, Object> responseBody = new Map<String, Object>();
        responseBody = (Map<String, Object>)JSON.deserializeUntyped(res.getBody());
        String errorVal = (String)responseBody.get('error');

        //リファレンス上、アクセストークンが無効(有効期限切れ)の場合、'unauthorized’となる
        if (errorVal.equals('unauthorized')){
            //クラウドサインのアクセストークンの再取得
            authToken = getAuthToken();
            //単純再帰で再実行する。
            documentId = getDocumentId(authToken, title, message);
        }
        ・・・中略・・・
    }
    ・・・以下省略・・・
}

実装を終えて

上記を実装した結果、稟議と入力内容が同じ、または、稟議から生成できる内容は全てシステム連携で自動生成するため、押印担当は稟議とクラウドサインの画面を並べて転記するような煩雑な作業を必要としない環境になりました。また、契約書の製本、郵送等の紙媒体であるが故の事務の削減ができるようになる等の、電子契約を導入することのそもそものメリットも併せて享受しています。

当社では 2021 年 4 月後半からクラウドサインを導入しましたが、2021 年 6 月時点ではすでに月間で締結した契約書の「3 割以上」が電子契約を活用しており、押印担当の展望として今後も利用を拡大していく予定です。

コーポレートエンジニア募集中

メドレーのコーポレート部門では、本稿のように、SaaS の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。

面白そう!興味がある!と感じた方は、ぜひ当社採用ページからご応募お願いします!

最後までお読みいただきありがとうございました。

https://www.medley.jp/jobs/

株式会社メドレーDeveloper Portal

© Medley Developer Portal