【初心者向け】Amazon Connectの構築からOutbound APIを叩いて電話を発信するまで

[4月に転職](https://qiita.com/duplicate1984/items/6baca75645434cb074e5)して２週間目でAmazon Connectの構築、CCPのカスタマイズを担当することになりました。
時勢のため在宅のコールセンターを構築したい、などの理由で需要が上がっているようです。

といってもいきなり本番環境で構築ができるわけでもないので、
一旦テスト用のアカウントで構築からAPI叩くところまでやってみることにします。

ちなみにこの時点ではAWSもPHPもLinuxも初めてです。
タイトルの通り初心者向けですが、そもそも私が初心者です。

#構成
　今回の構成は以下の通りで、
　ゴールは「EC2上に作成したPHPスクリプトからAmazon Connect APIを叩いて、指定した電話番号に発信するところまで」とします。

+ AWS EC2 Linux
+ PHP 7.2
+ AWS SDK for PHP 3



#Amazon Connect側の設定
まずはAmazon Connectを設定していきます。

###Amazon Connectインスタンスの設定
Amazon Connectインスタンスの作成から設定までは公式のハンズオンで簡潔に完結します。
https://www.slideshare.net/AmazonWebServicesJapan/amazon-connect-85894458

ので、ここでは割愛します。
早速説明を放棄しましたが、実際上のやつが一番わかりやすかったです。

###問い合わせフローの作成
問い合わせフローに関しても上述のハンズオンで説明されています。
今回はループの追加やDTMF(Dual-Tone Multi-Frequency)入力による分岐の追加もやってみました。
1を押すとオペレータに繋ぎ、2を押すと音楽を再生する、3を押すと通話終了になります。
![スクリーンショット (7).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/503026/8d17e91e-fe2f-e16e-9954-e873d2160a9f.png)


#クライアント側の準備
続いてAPIを叩くクライアント側の準備です。
EC2上に作成します。
PHPはインストールされているとして、APIを叩くのに必要なSDKを入れます。
###AWS SDK for PHPのインストール
以下コマンドでインストールします。

```
composer require aws/aws-sdk-php
```

また、APIのリファレンスは以下参照です。
Aws\ConnectやAws\Credentialが今回使う部分です。
https://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html

###認証情報は設定ファイルに分離する
APIを叩くにあたってAmazon Connectの認証情報が必要なので、
いったん同ディレクトリの外部ファイル（credentials.ini）に保存します。
なお認証情報のハードコーディングは推奨されていません。
https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/guide_credentials.html）
環境変数などから読み込むこともできるそうですが、今回はお試しということで。

```credential.ini
[default]
aws_access_key_id = アクセスキー
aws_secret_access_key = シークレットアクセスキー
```

###スクリプトの作成
下記のようにしました。

1. 認証情報を読み込む
2. クライアントを作成する
3. APIにリクエストを投げる（startOutboundVoiceContact()）

※発信先の電話番号は引数で与えます。

```DemoConnect.php
<?php

require 'vendor/autoload.php';
use Aws\Exception\AwsException;
use Aws\Credentials\CredentialProvider;

if ($argc == 1)
{
    echo "引数に発信先の電話番号を入力してください\n(E.164形式 +nnnnnnnnnnnn)\n";
    return;
}
else
{
    $dest = $argv[1];
}


// 認証情報を保存したiniを読み込む
$ini = __DIR__ . '/credentials.ini';
$credentials = CredentialProvider::ini('default', $ini);


// Amazon Connectのクライアント作成
$client = new \Aws\Connect\ConnectClient([
    'version' => 'latest',
    'region' => 'ap-northeast-1',
    'credentials' => $credentials
]);


// Amazon Connect Outbound APIで発信する
$result = $client->startOutboundVoiceContact([
    'Attributes' => [
        'message' => 'Demo Calling.'
    ],
    'ClientToken' => null,
    'ContactFlowId' => '問い合わせフローのID',
    'DestinationPhoneNumber' => $dest,
    'InstanceId' =>'Amazon ConnectインスタンスのID',
    'QueueId' => 'キューのID'
]);


// 後処理
if ($result['@metadata']['statusCode'] === 200)
{
    echo "Connecting Successed!\n";
}
else
{
    echo "Connecting Error!\n"."Check the response.\n";
    echo $result;
}
return;

?>
```

リクエストボディのID関連は以下の場所から取得できます。
ContactFlowId => 使用する問い合わせフローのURLのうちcontact-flow/以降
InstanceId => Amazon ConnectインスタンスのARNのうちinstance/以降
QueueId => 使用するキューのURLのうちqueue/以降

DestinationPhoneNumberにはE.164形式の電話番号を指定します。
また、サンプルには書いていませんが、他のプロパティとしてSourcePhoneNumberという送信元電話番号を指定することもできます。
ただこれについてはAmazon Connect側で電話番号に問い合わせフローを紐づけている場合には不要となります。

あとはこれを実行して、電話が鳴ればOKです。


#修正
###EC2のインスタンスプロファイルからロールを取得する
さて、上のスクリプトでは外部ファイルとはいえアクセスキーなどが生で保存されていますが、セキュリティ的に問題があります。
よって、アクセスキーはもたず、EC2のインスタンスプロファイルから認証情報を取得するように変更します。

まずは、Amazon Connectへのアクセス権を持ったIAMロールを作成し、それをEC2にアタッチします
https://aws.amazon.com/jp/blogs/news/easily-replace-or-attach-an-iam-role-to-an-existing-ec2-instance-by-using-the-ec2-console/

その後、以下のようにスクリプトを書き換えます。
CredentialProvider::instanceProfile()で作成したロールの認証情報を取得、それを継承したクライアントでリクエストします。
これで認証情報を持つ必要はなくなったのでcredentails.iniは削除します。
スクリプト一本だけで発信できるようになりました。

```DemoConnect.php

// EC2にアタッチされたロールを取得する
$provider = CredentialProvider::instanceProfile();
$memoizedProvider = CredentialProvider::memoize($provider);


// Amazon Connectのクライアント作成
$client = new \Aws\Connect\ConnectClient([
    'version' => 'latest',
    'region' => 'ap-northeast-1',
    'credentials' => $memoizedProvider
]);
```
　　


#まとめ
PHPを書くのは初めてなので、慣習に沿ってないところなど御座いましたらすみません。
~~どうしてもC#er的にif-elseはサンプルのように書きたい。~~
とりあえずの目的は達成できましたし、やっていてとても面白かったです。
調べているとSNSのBotを介してAPIを呼んでいたりする記事もあり、いろいろ応用を想像するのも楽しいですね。

冒頭にも書いた通りこれのほかにCPPのカスタマイズを若干したので、これについてもあとで記事が書ければなーと思います。