開発情報・ナレッジ

投稿者: SPIRERS ナレッジ向上チーム 2023年9月6日 (水)

カスタムプログラムを使ってzoomのURLを発行するアプリ アプリ、サイト設計・構築

ver.2.26のアップデートにより、DBトリガにカスタムプログラムアクションが追加されました。
このアップデートによりDBトリガの登録、更新、削除トリガをきっかけに、PHPプログラムを実行できるようになりました。
今回は、オンライン商談システムで日程確定後にzoom のURLを発行し、teams に通知するアプリを作成しています。
カスタムプログラムアクションだけでなく、活用方法の参考にしてください。

この記事は アプリ、サイト設計・構築 のフェーズとなります。

関連記事はこちら

全体図

全体のフローは下記になります。
① ユーザが商談依頼を登録
② 日程調整担当者が日程を調整し、日程情報及び担当部署を設定し更新
③ 会議URL発行ステータス変更時にカスタムプログラムで、zoomの会議作成し通知
 ④でキャンセル選択時にzoomの会議を削除
④ 営業担当が承認/キャンセルやメモなどを更新

DB構成

DBの構成となります。
「ステータス」「営業ステータス」と備考に「プログラムによる自動入力」の記載がある項目がトリガで使用しています。
その他、入力項目は自由に追加ください。

▼ 部署DB
表示名 識別名 タイプ 詳細設定 備考
部署名 departmentName テキスト
部署メールアドレス departmentMail メールアドレス ※ teams の通知URLを入力することでteamsに通知できます。

▼ 商談DB
表示名 識別名 タイプ 詳細設定 備考
会社名 companyName テキスト ユーザ入力項目
名前 name テキスト ユーザ入力項目
メールアドレス mail メールアドレス ユーザ入力項目
目的 business セレクト 【セレクト項目】
1:サービスに関する相談
2:開発に関する相談
3:運用に関する相談
4:パートナーに関する相談
ユーザ入力項目
お問い合わせ内容 contactText テキストエリア ユーザ入力項目
第一希望日 meetingDate1 日時 ユーザ入力項目
第二希望日 meetingDate2 日時 ユーザ入力項目
第三希望日 meetingDate3 日時 ユーザ入力項目
ステータス zoomStatus セレクト 【セレクト項目】
1:未発行
2:会議URL発行
3:処理中
4:発行済み
5:会議取り消し依頼
6:キャンセル

【デフォルト値】
未発行
日程調整担当者入力項目
担当部署 deploy 参照フィールド 【参照先DB】部署DB
【参照レコード数】1レコード
【ラベルフィールド】(ID)
日程調整担当者入力項目
確定商談日 startTime 日時 日程調整担当者入力項目
zoomURL zoomURL テキスト プログラムによる自動入力
zoomURLユーザ通知フラグ noticeFlg セレクト 【セレクト項目】
1:通知しない
2:通知する

【デフォルト値】
通知しない
プログラムによる自動入力
zoom用パスワード zoomAccessPassword テキスト プログラムによる自動入力
zoomミーティングID zoomMeetingID テキスト プログラムによる自動入力
営業ステータス salesStatus セレクト 【セレクト項目】
1:承諾
2:キャンセル
営業担当者入力項目
営業側メモ salesMemo テキストエリア 営業担当者入力項目

トリガの設計イメージ

DBの作成後、レコードアクショントリガを設定します。
非同期アクションのカスタムプログラムアクションだけではなく、メール配信、レコードアクションが必要となります。
トリガの処理は、下記になります。

ユーザが商談依頼
更新トリガの実装になるため、データ登録時はトリガは動作しません。
日程調整担当者が日程登録
①PHPの分岐処理で「zoomURL発行ステータス」が、「発行依頼」の場合にプログラムを実行します。
 - zoom API で設定日時でURL発行
 - SPIRAL ver.2 API で URL などの情報を更新する
ZoomのURLをAPIで更新処理
②レコードアクションでステータスを発行済みと通知フラグを通知するに更新します。
 経路「API」
 条件「zoomURL発行ステータス」が、「発行処理中」

③非同期トリガのメール配信でユーザと部署に通知をします。
 条件「zoomURLユーザ通知フラグ」が、「通知する」
 ※ ユーザと部署への通知は、メール配信を2つ用意する必要があります。
取り消し処理
④PHPの分岐処理「zoomURL発行ステータス」が、「会議取り消し依頼」の場合にプログラムを実行します。
 - zoom API で会議を削除
 - SPIRAL ver.2 API で URL などの情報を空にした状態で更新
取り消し処理と営業担当者が営業側メモを更新
⑤レコードアクションで通知フラグをなしに更新します。
 経路「すべて」
 条件「zoomURL発行ステータス」が、「発行処理中」以外 かつ 「zoomURLユーザ通知フラグ」が、「通知する」

トリガ 更新トリガの設定

レコードアクション
zoomURL発行通知トリガ
表示名 zoomURL発行通知トリガ
アクション先 自DB(お問い合わせマスタDB)
アクション先への操作 更新
①処理中
APIにて、zoomURLの発行された際に通知フラグを立てます。
処理名 処理中
発動条件 指定する
経路条件:すべての経路の経路
簡易条件:指定する
└ステータス = 処理中
処理タイプ 更新
処理マッピング アクション先DBフィールド:ステータス = '4'
ステータスを発行済みに変更
※格納値設定は関数に切り替える必要があります
エラー処理 全てエラー終了

②処理中以外
zoomURLの発行処理以外は通知フラグを落とす処理を設定します。
発動条件 指定する
経路条件:すべての経路の経路
簡易条件:指定する
└ステータス <>
└zoomURLユーザ通知フラグ = 通知する
処理タイプ 更新
処理マッピング アクション先DBフィールド:zoomURLユーザ通知フラグ = '1'
通知しないに変更
※格納値設定は関数に切り替える必要があります
エラー処理 全てエラー終了
非同期アクション - カスタムプログラム
zoom連携処理
表示名 zoom連携処理
発動条件 指定する
経路条件:一部の経路(操作画面)

PHP
<?php
// ver.2 設定値
define('API_URL', 'https://api.spiral-platform.com/v1');
define('API_KEY', '');
define('APP_ROLE', '');
define('APP_ID', '');
define('DB_ID', '');
// zoom 設定値
define('ZOOM_API_MAIL','');
define('ZOOM_ACCOUNT_ID','');
define('ZOOM_CLIENT_ID','');
define('ZOOM_CLIENT_SECRET','');

$recordData = $SPIRAL-> getRecord();
$commonBase = CommonBase::getInstance();
// 発行依頼
if(isset($recordData['item']) && $recordData['item']['zoomStatus'] == 2){
  $arrayPost = array();
  $arrayPost['topic'] = '【'.$recordData['item']['companyName'].'】'.$recordData['item']['name'].'様';
  $arrayPost['start_time'] = $recordData['item']['startTime'];
  $arrayPost['timezone'] = 'Asia/Tokyo';
  $arrayPost['password'] = $commonBase->makeRandStr(10);
  $zoomCreateResult = $commonBase->zoomCreate($arrayPost,base64_encode(ZOOM_CLIENT_ID.':'.ZOOM_CLIENT_SECRET));
  // データ更新
  $UpdateData = array(
    'noticeFlg'  => '2',
    'zoomStatus'  => '3',
    'zoomURL'  => $zoomCreateResult['join_url'],
    'zoomAccessPassword'  => $zoomCreateResult['password'],
    'zoomMeetingID' => (string)$zoomCreateResult['id'],
  );
   $resultRecordUpdate = $commonBase->apiCurlAction('PATCH', '/apps/'. APP_ID. '/dbs/'. DB_ID. '/records/'. $recordData['item']['_id'], $UpdateData);
}
// 取り消し
if(isset($recordData['item']) && ($recordData['item']['zoomStatus'] == 5 || $recordData['item']['salesStatus'] == 2)){
    $arrayPost = array();
    $arrayPost['schedule_for_reminder'] = false;
    $arrayPost['cancel_meeting_reminder'] = false;
    $zoomDeleteResult = $commonBase->zoomDelete($recordData['item']['zoomMeetingID'],base64_encode(ZOOM_CLIENT_ID.':'.ZOOM_CLIENT_SECRET),$arrayPost);
    // データ更新
    $UpdateData = array(
        'zoomURL'  => '',
        'zoomAccessPassword'  => '',
        'zoomMeetingID' => '',
        'zoomStatus'  => '6',
    );
    $resultRecordUpdate = $commonBase->apiCurlAction('PATCH', '/apps/'. APP_ID. '/dbs/'. DB_ID. '/records/'. $recordData['item']['_id'], $UpdateData);
}

class CommonBase {
    /**
     * シングルトンインスタンス
     * @var UserManager
     */
    protected static $singleton;

    public function __construct() {
        if (self::$singleton) {
            throw new Exception('must be singleton');
        }
        self::$singleton = $this;
    }
    /**
     * シングルトンインスタンスを返す
     * @return UserManager
     */
    public static function getInstance() {
        if (!self::$singleton) {
            return new CommonBase();
        } else {
            return self::$singleton;
        }
    }

    /**
     * zoom create
    */
    public function zoomCreate($parameters,$token){
      // アクセストークンの取得
      $commonBase = CommonBase::getInstance();
      $setTokenHeaders = array(
        'authorization:Basic '.$token,
      );
      $setTokenURL = 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id='.ZOOM_ACCOUNT_ID;
      $zoomToken = $commonBase->curlSend($setTokenURL,$setTokenHeaders,'','POST');

      // 会議URLの発行
      $setMeetingHeaders = array(
        'authorization:'.$zoomToken['token_type'] . $zoomToken['access_token'],
        'content-type: application/json;',
      );
      $setMeetingURL = 'https://api.zoom.us/v2/users/'.ZOOM_API_MAIL.'/meetings';
      $res = $commonBase->curlSend($setMeetingURL,$setMeetingHeaders,json_encode($parameters),'POST');
      return $res;
    }

    /**
     * zoom Delete
    */
    public function zoomDelete($meetingID,$token,$parameters){
        // アクセストークンの取得
        $commonBase = CommonBase::getInstance();
        $setTokenHeaders = array(
          'authorization:Basic '.$token,
        );
        $setTokenURL = 'https://zoom.us/oauth/token?grant_type=account_credentials&account_id='.ZOOM_ACCOUNT_ID;
        $zoomToken = $commonBase->curlSend($setTokenURL,$setTokenHeaders,'','POST');
  
        // 会議URLの削除
        $deleteMeetingHeaders = array(
            'authorization:'.$zoomToken['token_type'] . $zoomToken['access_token'],
            'content-type: application/json;',
        );
        $deleteMeetingURL = 'https://api.zoom.us/v2/meetings/'.$meetingID;
         $res = $commonBase->curlSend($deleteMeetingURL,$deleteMeetingHeaders,json_encode($parameters),'DELETE');
        return $res;
    }

    function curlSend($url,$headers,$jsonData=null,$method = null){
        $curl = curl_init($url);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_URL, $url);
        curl_setopt($curl, CURLOPT_HTTPHEADER , $headers);
        curl_setopt($curl, CURLOPT_POST , true);
        if ($method == 'POST') {
          curl_setopt($curl, CURLOPT_POSTFIELDS , $jsonData);
          curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');
        }
        if ($method == 'DELETE') {
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == 'GET') {
          curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'GET');
        }
        curl_exec($curl);
        $response = curl_multi_getcontent($curl);
        curl_close($curl);        
        return json_decode($response, true);
    }

    public function makeRandStr($length) {
        $str = array_merge(range('a', 'z'), range('0', '9'), range('A', 'Z'));
        $r_str = null;
        for ($i = 0; $i < $length; $i++) {
            $r_str .= $str[rand(0, count($str) - 1)];
        }
        return $r_str;
    }

     /**
     * V2用 API送信ロジック
     * @return Result
     */
    public function apiCurlAction($method, $addUrlPass, $data = null, $multiPart = null, $jsonDecode = null) {
        $header = array(
            'Authorization:Bearer '. API_KEY,
            'X-Spiral-Api-Version: 1.1',
        );
        if($multiPart) {
            $header = array_merge($header, array($multiPart));
        } else {
            $header = array_merge($header, array('Content-Type:application/json'));
        }
        if(APP_ROLE){
            $header = array_merge($header, array('X-Spiral-App-Role: '.APP_ROLE));
        }
        // curl
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_URL, API_URL. $addUrlPass);
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
        if ($method == 'POST') {
            if ($multiPart) {
                curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
            } else {
                curl_setopt($curl, CURLOPT_POSTFIELDS , json_encode($data));
            }
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == 'PATCH') {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        if ($method == 'DELETE') {
            curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
            curl_setopt($curl, CURLOPT_CUSTOMREQUEST, $method);
        }
        $response = curl_exec($curl);
        if (curl_errno($curl)) echo curl_error($curl);
        curl_close($curl);
        if($jsonDecode){
            return $response;
        }else{
            return json_decode($response, true);
        }
  }
}
ver.2 設定値
API_URL 3行目 リクエスト先URLの固定部分です。固定値ですので特に変更する必要はありません。
API_KEY 4行目 発行したAPIキーを設定してださい。別途権限の付与が必要になります。
APP_ROLE 5行目 設定したアプリロールの識別名を入れてください。全権限の場合は値は空で大丈夫です。
APP_ID 6行目 レコード操作を行うDBがあるアプリのIDを設定してください。
DB_ID 7行目 レコード操作を行うDBのIDを設定してください。
操作するDBが複数ある場合は「DB_ID_識別名」など適宜定数を追加してください。
・補足
define("API_KEY", $SPIRAL->getEnvValue("apikey"));
上記はAPIキーの記述を「PHP環境変数設定」を利用した場合のコードとなります。
このように定数を設定する時に、SPIRAL ver.2 機能の「PHP環境変数設定」を利用することで、
本番環境とテスト環境でキーが異なっていても、コードを書き換える必要がなく、保守性が高まります。
APIキーなど複数ページで使用するものは、環境変数設定をしておきましょう。

その他詳しくはサポートサイト「PHP環境変数」をご参照ください。

zoom 設定値
ZOOM_MAIL 9行目 zoomに登録しているメールアドレスを入力してください。
ZOOM_ACCOUNT_ID 10行目 zoom アプリの Account ID を入力してください。
ZOOM_CLIENT_ID 11行目 zoom アプリの Client ID を入力してください。
ZOOM_CLIENT_SECRET 12行目 Client Secret
zoomの設定値はMarketplaceで確認ができます。
確認方法
手順1
Marketplaceにログインを行い、① の Build App を押下する
手順2
Server-to-Server OAuth の ② Create を押下し、アプリを作成する
App Nameは、任意のアプリ名を設定ください。 手順3
アプリを作成すると Account ID と Client ID と Client Secretが確認できます
手順4
Informationで Company Name と Name と Email を入力します
手順5
サイドメニューのScopesからスコープを追加してください。
今回のAPIに必要なスコープは、Meeting の View and manage all user meetings のみとなります。
手順6
設定内容をすべて入力後に Activation より ③ Activate your app ボタンを押下して、アプリをアクティブにしてください。
【補足】
カスタムプログラムの動作チェックは、手動実行でも確認ができます。
手動実行では、「var_dump」や「print」など内容を出力する関数が使用できるので、
いきなり有効化で更新の確認をせず、手動実行でIDを指定する形でデバックを行いながら動作チェックを行いましょう。
非同期アクション - メール配信
▼ 日程確定通知 - ユーザ
▼基本設定
表示名 ユーザ宛日程確定通知
DB 商談DB
経路条件 すべて
▼宛先
指定方法 レコード
フィールド メールアドレス
配信条件 条件付き配信
条件抽出(簡易):[zoomURLユーザ通知フラグ][等しい][通知する]
配信エラー除外 1回以上
▼差出人、コンテンツ
差出人、コンテンツ 差出人メールアドレスや件名・文面を自由に入力
 ※ 発行されたzoomのURLやログインするためのパスワードを忘れずに入力ください。
▼ 日程確定通知 - 営業
▼基本設定
表示名 営業宛日程確定通知
DB 商談DB
経路条件 すべて
▼宛先
指定方法 レコード
フィールド 担当部署 > 通知用部署メールアドレス
配信条件 条件付き配信
条件抽出(簡易):[zoomURLユーザ通知フラグ][等しい][通知する]
配信エラー除外 1回以上
▼差出人、コンテンツ
差出人、コンテンツ 差出人メールアドレスや件名・文面を自由に入力
※ 発行されたzoomのURLやログインするためのパスワードを忘れずに入力ください。
【補足】
部署DBもメールアドレスに teams の通知用メールアドレスを登録しておくことで、teamsへの通知が可能となります。
メールアドレス取得方法は、Teams に登録通知を行う方法 の「メールを利用する場合」を参考にしてください。
以上でトリガの設定が完了となります。
トリガが「無効」状態になっている場合、動作しませんので、「有効」になっていることを確認してください。

商談依頼フォーム作成

商談依頼フォームでは、下記のフィールドで設定いたします。
フィールド
会社名
名前
メールアドレス
目的
お問い合わせ内容
第一希望日
第二希望日
第三希望日

具体的な作成方法については、割愛します。
知りたい方は、動画で学ぶ SPIRAL ver.2アプリ設定 ~ Level1 お問い合わせフォーム Chapter3 ~ を確認してください。

商談依頼フォームの作成が完了したら、アプリ作成完了です。
動作確認をしっかり行ったうえで、リリースしてください。

最後に

設定後は動作確認を必ず行い、動作に問題がないか確認をしてください。
また、不具合やほかのやり方が知りたい等あれば、下記の「コンテンツに関しての要望はこちら」からご連絡ください。


関連記事はこちら
解決しない場合はこちら コンテンツに関しての
要望はこちら