Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

「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クラウドサインとの連携についてお話しします。まず、本稿の開発部分とシステム構成は下記になっております。

f:id:medley_inc:20210715210552p:plain

処理内容の詳細は後ほど述べますが、概要としては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を織り交ぜて詳細化すると下図のようになります。

f:id:medley_inc:20210715210621p:plain

実装方法としてはクラウドサイン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: base64API提供側が対応している場合は例外になります)。そのため、文字列とバイナリデータを結合し一つの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の導入ひとつとっても検討を尽くし、既存のシステムと有機的に結合させることで「徹底的に合理性を追求した組織基盤や、仕掛けづくり」を行っています。

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

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

www.medley.jp