Box Sign: 署名リクエスト作成

Box Sign: Create Sign Request

この工程は、Box 上のファイルから署名用ドキュメントを作成し、署名リクエストを送信します。

Auto Step icon
Basic Configs
工程名
メモ
Configs for this Auto Step
conf_OAuth2
C1: OAuth2 設定 *
conf_SourceFileId
C2: 署名用ドキュメントの作成元ファイル ID *
conf_FolderId
C3: 署名用ドキュメントを保存するフォルダ ID (ルートフォルダは使用できません) *
conf_EmailAddress
C4: 署名者メールアドレス (複数設定する場合、1件ごとに改行してください) *
conf_Password
C5: 署名者ごとのパスワード (1件ごとに改行してください)
conf_EmailSubject
C6: 送信されるメールの件名 (空白の場合、デフォルトの件名が使用されます)#{EL}
conf_EmailMessage
C7: メール本文に含めるメッセージ (空白の場合、デフォルトの文面が使用されます)#{EL}
conf_DaysValid
C8: 期限切れまでの日数 (空白の場合、期限は設定されません)
conf_RequestId
C9: 署名リクエスト ID を保存するデータ項目

Notes

  • Box プラットフォームで Box Sign が有効にされている必要があります(デフォルト:無効)
  • C1: OAuth2 設定で認証を得るユーザは、Box のBusiness プランのユーザーアカウントが必要です
  • ファイル ID は、URL に含まれています https://{sub-domain}.app.box.com/file/(File ID)
  • フォルダ ID は、URL に含まれています https://{sub-domain}.app.box.com/folder/(Folder ID)
  • Box のリフレッシュトークンには、期限があります
  • 「電子すかし」が適用されたファイルを署名の対象にするとエラーとなります

Capture

See also

Script (click to open)
  • 次のスクリプトが記述されている XML ファイルをダウンロードできます
    • box-sign-request-create.xml (C) Questetra, Inc. (MIT License)
    • Professional のワークフロー基盤では、ファイル内容を改変しオリジナルのアドオン自動工程として活用できます


// OAuth2 config sample at [OAuth 2.0 Setting]
// - Authorization Endpoint URL: https://account.box.com/api/oauth2/authorize
// - Token Endpoint URL: https://api.box.com/oauth2/token
// - Scope: sign_requests.readwrite root_readwrite
// - Consumer Key: (Get by Box Developer Console)
// - Consumer Secret: (Get by Box Developer Console)

const MAX_SIGNER_NUM = 35;
const MAX_DAYS_VALID = 730;

const PROCESSING_STATUSES = [
    'converting',
    'created' // 送信完了まで待機するため、created も処理中扱いにする
];

const ERROR_STATUSES = [
    'error_converting',
    'error_sending'
];

function main() {
    //// == 工程コンフィグ・ワークフローデータの参照 / Config & Data Retrieving ==
    const oauth2 = configs.getObject('conf_OAuth2');
    const fileId = decideEditable('conf_SourceFileId', 'Source File ID');
    const folderId = decideEditable('conf_FolderId', 'Folder ID');
    if (folderId === '0') {
        throw 'Root folder cannot be used.';
    }
    const signers = retrieveSigners();
    const subject = configs.get('conf_EmailSubject');
    const message = configs.get('conf_EmailMessage');
    const daysValid = retrieveDaysValid();
    const requestIdDef = configs.getObject('conf_RequestId');

    //// == 演算 / Calculating ==
    const requestId = createRequest(oauth2, fileId, folderId, signers, subject, message, daysValid);
    setData(requestIdDef, requestId);
    if (checkStatus(oauth2, requestId) === false) {
        engine.saveTemporaryData(requestId); // 署名リクエスト ID を一時データとして保存
        return false;
    }
}

function proceed() {
    //// == 工程コンフィグ・ワークフローデータの参照 / Config & Data Retrieving ==
    const oauth2 = configs.getObject('conf_OAuth2');
    const requestId = engine.restoreTemporaryData();
    if (requestId === null) {
        throw 'Sign Request ID has not been saved.';
    }

    //// == 演算 / Calculating ==
    if (checkStatus(oauth2, requestId) === false) {
        return false;
    }
}

/**
  * editable な config から設定値を読み出す
  * 設定値が空の場合はエラー
  * @param {String} confName 設定名
  * @param {String} label エラー出力用ラベル
  * @return {String} value 設定値
  */
function decideEditable(confName, label) {
    let value = '';
    const dataDef = configs.getObject(confName);
    if (dataDef === null) {
        value = configs.get(confName);
    } else {
        value = engine.findData(dataDef);
    }
    if (value === '' || value === null) {
        throw `${label} is blank.`;
    }
    return value;
}

/**
  * 署名者情報 (メールアドレスとパスワード) をデータ項目から読み出す
  * 1つも設定されていない場合、上限件数を超える場合はエラー
  * @return {Array<Object>} signers 署名者 {email, password} の配列
  */
function retrieveSigners() {
    const emails = retrieveFilledArray('conf_EmailAddress', 'Email Address');
    const passwords = retrieveFilledArray('conf_Password', 'Password');

    // 件数チェック
    if (emails === null || emails.length === 0) {
        throw 'No Email Address.';
    }
    if (emails.length > MAX_SIGNER_NUM) {
        throw `Number of Email Addresses exceeds the limit. The maximum number is ${MAX_SIGNER_NUM}.`;
    }

    // 署名者の配列を作成
    const signers = emails.map(email => ({email}));
    if (passwords === null) { // パスワード設定なし
        return signers;
    }
    // パスワード設定あり
    if (passwords.length !== signers.length) { // 件数が一致しない
        throw 'Number of Passwords does not match the number of Email Addresses.';
    }
    passwords.forEach((password, i) => {
        signers[i].password = password
    });
    return signers;
}

/**
  * config から複数行のデータを読み出し、配列を生成する
  * データ項目が設定されていない場合は null を返す
  * データ項目が設定されていて、中身が空の場合はエラー
  * 空行を含む場合はエラー (最後に空行がひとつだけある場合は無視)
  * @param {String} confName config 名
  * @param {String} label エラー出力に使用するラベル
  * @return {Array<String>} array 空文字列を含まない配列
  */
function retrieveFilledArray(confName, label) {
    const dataDef = configs.getObject(confName);
    if (dataDef === null) { // データ項目が設定されていない
        return null;
    }
    const string = engine.findData(dataDef);
    if (string === null || string === '') { // データ項目の中身が空
        throw `Data item for ${label} is set but its content is empty.`;
    }

    const array = string.split('\n');
    if (array[array.length - 1] === '') { // 最後に空行があれば削除
        array.pop();
    }
    // 空行が残っている場合はエラー
    const i = array.indexOf('');
    if (i !== -1) {
        throw `${label} at line ${i+1} is blank.`;
    }
    return array;
}

/**
  * config から署名リクエストが期限切れになるまでの日数を読み出す
  * @return {Number} daysValid 署名リクエストが期限切れになるまでの日数
  */
function retrieveDaysValid() {
    const daysValidStr = configs.get('conf_DaysValid');
    const error = `Days until expiration must be positive integer no greater than ${MAX_DAYS_VALID}.`;
    if (daysValidStr === null || daysValidStr === '') {
        return null;
    }
    const reg = new RegExp('^[1-9][0-9]{0,2}$');
    if (!reg.test(daysValidStr)) {
        throw error;
    }
    const daysValid = parseInt(daysValidStr, 10);
    if (daysValid > MAX_DAYS_VALID) {
        throw error;
    }
    return daysValid;
}

/**
  * Create Sign Request  署名リクエスト作成
  * @param {AuthSettingWrapper} oauth OAuth2 設定
  * @param {String} fileId 元ファイルの ID
  * @param {String} folderId 保存先フォルダの ID
  * @param {Array<Object>} signers 署名者 {email, password} の配列
  * @param {String} subject 送信メールの件名
  * @param {String} message 送信メールに含めるメッセージ
  * @param {Number} daysValid 署名リクエストが期限切れになるまでの日数
  * @return {String} requestId 署名リクエストの ID
  */
function createRequest(oauth2, fileId, folderId, signers, subject, message, daysValid) {
    const jsonBody = {
        signers,
        days_valid: daysValid
    };
    jsonBody['source_files'] = [{
        'id': fileId,
        'type': 'file'
    }];
    jsonBody['parent_folder'] = {
        'id': folderId,
        'type': 'folder'
    };
    if (subject !== '' && subject !== null) {
        jsonBody['email_subject'] = subject;
    }
    if (message !== '' && message !== null) {
        jsonBody['email_message'] = message;
    }

    const url = 'https://api.box.com/2.0/sign_requests';
    const response = httpClient.begin()
        .authSetting(oauth2)
        .body(JSON.stringify(jsonBody), 'application/json; charset=UTF-8')
        .post(url);
    const status = response.getStatusCode();
    const responseTxt = response.getResponseAsString();
    if (status !== 201) {
        engine.log(responseTxt);
        throw `Failed to create Sign Request. status:${status}`;
    }
    return JSON.parse(responseTxt).id;
}

/**
  * データ項目にデータを保存する
  * @param {DataDefinitionView} dataDef データ項目の DataDefinitionView
  * @param {Object} value 保存する値
  */
function setData(dataDef, value) {
    if (dataDef !== null) {
        engine.setData(dataDef, value);
    }
}

/**
  * 署名リクエストのステータスを確認
  * 処理中の場合は false を返し、処理完了の場合は署名リクエスト ID を保存
  * @param {AuthSettingWrapper} oauth OAuth2 設定
  * @param {String} requestId 署名リクエスト ID
  * @returns {boolean} 処理未完了の場合は false
  */
function checkStatus(oauth2, requestId) {
    const status = getSignRequestStatus(oauth2, requestId);
    // エラーステータスの場合、例外をスローする
    if (ERROR_STATUSES.includes(status)) {
        throw `The Sign Request failed in ${status} status.`;
    }
    // 処理中の場合、false を返す
    if (PROCESSING_STATUSES.includes(status)) {
        engine.log(`current status: ${status}`);
        return false;
    }
}

/**
  * 署名リクエストのステータスを取得
  * @param {AuthSettingWrapper} oauth2 OAuth2 設定
  * @param {String} requestId 署名リクエストの ID
  * @return {String} status 署名リクエストのステータス
  */
function getSignRequestStatus(oauth2, requestId) {
    const url = `https://api.box.com/2.0/sign_requests/${requestId}`;
    const response = httpClient.begin()
        .authSetting(oauth2)
        .get(url);
    const status = response.getStatusCode();
    const responseTxt = response.getResponseAsString();
    if (status !== 200) {
        engine.log(responseTxt);
        throw `Failed to get Sign Request. status:${status}`;
    }
    return JSON.parse(responseTxt).status;
}

%d人のブロガーが「いいね」をつけました。