Twilio Pay を使って電話決済を体験する(上級編)

# はじめに
これは、2019/03/21(木・祝)開催 [JP_Stripes Connect 2019](https://connect2019.jpstripes.com/)のワークショップ『「電話でペイ！」(Twilio Pay)を試してみよう！』で使用する資料です。

[Twilio <Pay> を使って電話決済を体験する(初級編)](https://qiita.com/takeshifurusato/private/b3e74383113c9723e11c)
[Twilio <Pay> を使って電話決済を体験する(中級編)](https://qiita.com/takeshifurusato/private/af96ecc7ce69dec8f42c)
Twilio <Pay> を使って電話決済を体験する(上級編) ←こちら

Twilio を使って電話決済を体験する(上級編)では、初級編・中級編で作成したアプリケーションを利用して、２者(オペレータとクライアント）で電話で話をしている途中に、Twilio\<Pay>によるクレジットカード入力(状況はWebページで確認)できるものを作成します。

# 前提
こちらは、[Twilio <Pay> を使って電話決済を体験する(中級編)](https://qiita.com/takeshifurusato/private/af96ecc7ce69dec8f42c)にて、StripeやTwilioの設定・構築ができていることが前提となります。

# 注意事項
[Twilio <Pay> を使って電話決済を体験する(初級編)](https://qiita.com/takeshifurusato/private/b3e74383113c9723e11c)および、[Twilio <Pay> を使って電話決済を体験する(中級編)](https://qiita.com/takeshifurusato/private/af96ecc7ce69dec8f42c)の注意事項に記載した費用が発生します。
また、TwilioConferenceとOutgoingAPIを利用します。その際に、料金が発生します。くわしくは[こちら](https://twilio.kddi-web.com/price/)

# 得られるもの
Twilio三者通話(Conference)を利用して、Twilio<Pay>の状況を電話で確認することが体験できます。
例えば、テレフォンショッピングなどの決済で、オペレータと会話しながら、電話決済を行う場合などに利用できます。

# ざっくり構成
<img width="749" alt="スクリーンショット 2019-03-14 18.10.40.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/629bdade-3f4e-344b-b509-1fc840755f78.png">

1. 発信：TwilMLApp(ブラウザフォン)からTwiiloFunctionsを実行、TwilioConferenceを生成し、自信のケータイ電話に発信する。※ブラウザフォンとケータイ電話とがTwilioConference内で通話できる状態となる。
2. 実行：ブラウザからTwilioFunctionを実行
3. 決済番号に発信：実行されたTwilioFunctionsが、決済番号に発信しTwilioConferenceと接続する(ブラウザフォン、ケータイ、決済番号への発信の三者間通話を構築)。決済番号は初級で作成した仕組みにしたがい、\<Pay>の案内を発話、その内容がTwilioConferenceを介しブラウザフォンとケータイから聞こえる。


# 事前準備
- [Twilio <Pay> を使って電話決済を体験する(初級編)](https://qiita.com/takeshifurusato/private/b3e74383113c9723e11c)を実施していること。
- [Twilio <Pay> を使って電話決済を体験する(中級編)](https://qiita.com/takeshifurusato/private/af96ecc7ce69dec8f42c)を実施していること。

# ワークショップ
以下の手順になります。

- TwilioConferenceを使った電話転送の仕組みを作成する
- TwilioConferenceにTwilio\<Pay>を割り込みさせる
- 状況確認画面にTwilio\<Pay>割り込みボタンを付ける。
- テストする。

## TwilioConferenceを使った電話転送の仕組みを作成する
今回、TwilioConferenceという複数人で通話する仕組みを利用します。
はじめに、TwilioConferenceの設定を変更します。
Twilioの管理コンソールから**Programmable Voice** →　**Conferences** → **設定** を選択します。
**AGENT CONFERENCE**を選択し、**保存**をクリックします。
![スクリーンショット 2019-03-07 13.47.14.png](https://qiita-image-store.s3.amazonaws.com/0/110436/0cd19135-4fca-77bf-3552-d09e444045ea.png)

クライアントからの電話を受け、オペレーターへ電話をつなぐ処理をTwilioFunctionsで作成します。
まずは、Functionで利用する定数(環境変数)を便宜上登録しておきます。
Twilioの管理コンソールから**Runtime** →　**Functions** → **設定** を選択します。
![スクリーンショット 2019-03-05 13.19.22.png](https://qiita-image-store.s3.amazonaws.com/0/110436/d2204cf4-55bd-0f31-c493-5b4911d90b65.png)
**Environmental Variables**にある**赤いボタンプラス**をクリックし、以下のKEY-VALUEを追加します。
入力が終わったら、画面下のSaveをクリックします。

| KEY | VALUE|
|:-- |:-- |
|PAY_NUMBER|初級編で購入した電話番号をE.164 形式(+81で始まる形式)で入力|
|PAY_MY_MOBILE_NUMBER|オペレータ用番号(自分のケータイ番号)をE.164 形式(+81で始まる形式)で入力|

Twilioの管理コンソールから**Runtime** →　**Functions** を選択し、**赤いプラスボタン**をクリック、Functionを新規作成します。
テンプレートはBlankを選択し、Createをクリックします。表示された画面で、以下のように入力し、Saveをクリックします。 

| 項目名 | 設定値 |
|:--|:--|
| FUNCTION NAME | PayForwarding |
| PATH | /pay-forwarding |
| ACCESS CONTROL | チェックあり |
| EVENT | (未選択) |
| CODE | 以下参照 |

```javascript
exports.handler = function(context, event, callback) {
    const toNumber = event.To;

    // Conferenceを生成して発信元と接続
    const twiml = new Twilio.twiml.VoiceResponse();
    let dialParams = {}
    dialParams.callerId = toNumber
    dial = twiml.dial(dialParams)
    dial.conference({
        region: 'jp1',
        endConferenceOnExit: true,
    }, 'payConference')  
    
    // 自身のケータイへ発信、Conferenceに参加させる
    const client = context.getTwilioClient() 
    client
    .conferences('payConference')
    .participants.create({
        region: 'jp1',
        to: context.PAY_MY_MOBILE_NUMBER,
        from: toNumber,
        endConferenceOnExit: true,
        earlyMedia: true,
    })
    .then(participant => {
        console.log(participant.sid)
        callback(null, twiml)
    })
    .catch(error => {
        console.log(error)
        callback(error)
    })      
};
```
![スクリーンショット 2019-03-08 16.47.12.png](https://qiita-image-store.s3.amazonaws.com/0/110436/95d1fff5-5a3b-df6d-9213-bfeeff37c7ba.png)

## 一旦、動作確認をします。
動作確認は、TwiML Appsを使ってブラウザフォンで行います。
Twilioの管理コンソールから**Programmable Voice** → **TwiML** → **TwiML Apps**を選択します。表示された画面で**Create new TwiML App**をクリックし、新規作成をおこないます。
![スクリーンショット 2019-03-07 11.43.56.png](https://qiita-image-store.s3.amazonaws.com/0/110436/1e064202-4ed2-ccf9-94f5-fc20a03dc48e.png)
表示された画面に以下のように入力をします。入力し終わったら、**Create**をクリックします。

| 項目名 | 設定値 |
|:--|:--|
| Properties FRIENDLY NAME | PayForwardingTest |
| Voice REQUEST URL | さきほど作成したFunction「PayForwarding」のPATH |

![スクリーンショット 2019-03-07 11.57.00.png](https://qiita-image-store.s3.amazonaws.com/0/110436/0d0b4d6c-58f3-3736-a9ed-48f8b52acd01.png)
作成されると、TwiMLAppsの一覧画面が表示されますので、作成した「PayForwardingTest」をクリックします。
![スクリーンショット 2019-03-07 11.58.06.png](https://qiita-image-store.s3.amazonaws.com/0/110436/1d115cb6-020b-761a-6738-c0b62b0cb957.png)
表示された画面の右側にある**Call**をクリックします。
![スクリーンショット 2019-03-07 11.59.46.png](https://qiita-image-store.s3.amazonaws.com/0/110436/29d79edd-ff68-24a5-bca5-1730566ea843.png)
ブラウザフォンが表示され、Functionが実行され、オペレータ用番号(自分のケータイ番号)に電話がかかってきます。
通話ができることを確認してください。
![スクリーンショット 2019-03-07 12.03.52.png](https://qiita-image-store.s3.amazonaws.com/0/110436/8428cd41-fbac-d429-8c3b-5e263e094798.png)

## TwilioConferenceにTwilio\<Pay>を割り込みさせる
クライアントとオペレーターとでTwilioConferenceを介して通話ができるようになりました。
この通話に、Twilio\<Pay>を割り込みさせる処理をTwilioFunctionsで作成します。
Twilioの管理コンソールから**Runtime** →　**Functions** を選択し、**赤いプラスボタン**をクリック、Functionを新規作成します。
テンプレートはBlankを選択し、**Create**をクリックします。表示された画面で、以下のように入力し、Saveをクリックします。 
これは、受付用電話番号から決済用電話番号に電話をかける処理になります。

| 項目名 | 設定値 |
|:--|:--|
| FUNCTION NAME | PayGuideStart |
| PATH | /pay-guide-start |
| ACCESS CONTROL | チェックなし |
| EVENT | (未選択) |
| CODE | 以下参照 |

```javascript
exports.handler = function(context, event, callback) {
    const client = context.getTwilioClient();
    client
    .conferences('payConference')
    .participants.create({
        region: 'jp1',
        to: context.PAY_NUMBER,
        from: context.PAY_NUMBER,
    })
    .catch(error => {
        console.log(error);
        callback(error);
    });  
};
```
ここでのポイントは、toとfromを同じ番号としておくことです。

## 状況確認画面にTwilio\<Pay>割り込みボタンを付ける。
中級編で作成した状況確認画面に、先ほど作成したTwilio\<Pay>の割り込み処理を呼び出すボタンを追加します。
以下のURLからファイルをダウンロードしてください。
https://heady-sea-5045.twil.io/assets/paystatus_advanced.zip

Twilioの管理コンソールから**Runtime** → **Assets**を選択します。
**Add an Assets**をクリックし、上記zipファイルを展開したものをアップロードします。
※中級編ですでに同名のファイルをアップロード済みですので、上書きしてください。
以下、内容を記載しておきます。Guideボタンを追加しています。

```html:paystatus.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Twilio Pay Status Page</title>
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.2/js/bootstrap.min.js"></script>
    <script src="https://media.twiliocdn.com/sdk/js/sync/releases/0.8.4/twilio-sync.min.js"></script>
    <script src="./paystatus.js?version=1.1"></script>
  </head>
  <body>
    <div class="container">
        <h3>Twilio Pay Status</h3>
        <div class="form-group">
            <label>Status</label>
            <label id="Status"></label>
        </div>
        <div class="form-group">
            <label>PaymentCardNumber</label>
            <input id="PaymentCardNumber" type="text" class="form-control" readonly>
        </div>
        <div class="form-group">
            <label>ExpirationDate</label>
            <input id="ExpirationDate" type="text" class="form-control" readonly>
        </div>
        <div class="form-group">
            <label>SecurityCode</label>
            <input id="SecurityCode" type="text" class="form-control" readonly>
        </div>
        <button id="btnClear" type="button" class="btn btn-danger">Clear</button>
        <!-- こちらのボタンを追加 -->
        <button id="btnGuide" type="button" class="btn btn-info">Guide</button>
        </div>
  </body>
</html>
```
```javascript:paystatus.js
let syncDoc, syncClient;

$(function() {
    
    $.getJSON('../pay-status-sync-token', function (tokenResponse) {
        syncClient = new Twilio.Sync.Client(tokenResponse.token, { logLevel: 'info' });
        syncClient.document('PayStatus')
        .then(function(doc) {
            syncDoc = doc;
            syncDoc.on('updated', event => {
                console.log("Sync updated.");
                console.log(event);
                $('#PaymentCardNumber').val(event.value.PaymentCardNumber);
                $('#ExpirationDate').val(event.value.ExpirationDate);
                $('#SecurityCode').val(event.value.SecurityCode);
                $('#Status').html(event.value.Status);
            });
        })
        .catch(function(error) {
            console.log(error);
        });

        syncClient.on('tokenAboutToExpire', () => {
            $.getJSON('../pay-status-sync-token', function (tokenResponse) {
                syncClient.updateToken(tokenResponse.token);
            });
        });
    });

    $('#btnClear').on('click', function() {
        const value = {
            PaymentCardNumber : "",
            ExpirationDate    : "",
            SecurityCode      : "",
            Status            : "",
        };
        syncDoc.set(value);
    });

    // こちらの処理を追加
    $('#btnGuide').on('click', function() {
        $.ajax({
                url:'../pay-guide-start',
                type:'POST',
            })
            .done( (data) => {
                alert('Sending completed');
            }
        );
    });

});
```
Assetにアップロードできたら、htmlファイルのPATHのコピーボタンをクリックし、コピーしておきます。

## テストする
さきほど動作確認で利用したTwiMLAppsにてテストを行います。

- TwiML Appsを使ってブラウザフォンでテストを行ってください。Twilioの管理コンソールから**Programmable Voice** → **TwiML** → **TwiML Apps**を選択、上記で作成した**PayForwardingTest**をクリックし、表示された画面で**Call**をクリックします。
- 通話途中で、状況表示画面の**Guide**ボタンをクリックします。
- この処理は初級編で作成した決済番号へ電話をかける処理です。**トライアルアカウントの場合は、ガイダンスが流れますので、聞いた後、ケータイ電話でなにかキーをプッシュしてください**
- 通話中にTwilio\<Pay>の音声が流れるので、クレジットカード番号、有効期限、セキュリティコードを入力します。
- 随時、状況表示画面が更新されていることを確認します。
<img width="505" alt="スクリーンショット 2019-03-05 17.59.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/a0406d2b-2c51-30ea-c903-2da5da505ec8.png">

# お片付け
Twilioアカウントをアップグレードされた場合、そのままにしておくと別途費用が発生します。
以下の内容に従い、お片付けをすることを推奨します。
## Stripeアカウントについて
Stripeアカウントは、本番利用しないと料金は発生しませんので、何もしなくてOKです。
今後ご利用にならない場合は、アカウント自体を削除してください。

## Twilioアカウントについて
**Number Phones**については、そのままにしてくと費用が発生します。それ以外はそのままにしておいても費用は発生しませんが、外部からアクセスされたことにより、通話などが発生し、結果費用が発生する場合がありますので、必要なければ削除を推奨します。

### Number Phones
1つの電話番号につき108円/月の費用が発生します。
Twilioの管理コンソールから**Phone Numbers** →　**番号の管理** → **アクティブな電話番号** を選択します。
<img width="1075" alt="スクリーンショット 2019-03-05 18.09.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/726ac221-cf41-2e56-0439-b1177a1cfe85.png">
該当の番号をクリックし、表示された画面の下部にある**この番号をリリースする**をクリックします。
<img width="922" alt="スクリーンショット 2019-03-05 18.10.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/cb71e549-2e2d-76a1-a244-6879201e07cc.png">

### TwilioAssets
アップロードしたファイルを削除します。そのままにしていても費用は発生しませんが、不用意にアクセスされた場合、費用が発生することがあります。

- 画面にアクセスするとTwilioSyncが動作し、費用が発生します。
- 画面にアクセスし**Guide**ボタンをクリックすると、発信するため通話料が発生します。
Twilioの管理コンソールから**Runtime** →　**Aeets** を選択し、該当のファイルの右側にあるゴミ箱アイコンをクリックします。
<img width="1193" alt="スクリーンショット 2019-03-05 18.18.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/8ca27b31-fe0e-742f-592f-abbe4a50ecf0.png">

### TwilioFunctions
作成したFunctionを削除します。そのままにしていても費用は発生しませんが、不用意にアクセスされた場合、費用が発生することがあります。

- 作成したFunctionsによっては、発信をするため通話料が発生します。
Twilioの管理コンソールから**Runtime** →　**Functions** を選択します。
<img width="1054" alt="スクリーンショット 2019-03-05 18.21.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/d36b4548-4da4-e4a6-4bbb-55c4a278566d.png">
該当のFunctionをクリックし、表示された画面の下部にある**Delete this Function**をクリックします。
<img width="871" alt="スクリーンショット 2019-03-05 18.23.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/87da410a-5c73-1c8c-fade-ce292dc24ba5.png">

### Twilio TwiML Bins
作成したTwiML Binsを削除します。そのままにしていても費用は発生しません。削除する場合は以下のように行います。
Twilioの管理コンソールから**Runtime** →　**TwiML Bins** を選択します。
<img width="951" alt="スクリーンショット 2019-03-05 18.31.26.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/a95b2288-b56a-fabd-cf25-36c8f34cc28a.png">
該当のTwiMLを選択し、表示された画面の下部にある**Delete this TwiML Bin**をクリックします。
<img width="867" alt="スクリーンショット 2019-03-05 18.32.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/0bed9ba9-602c-6ec7-8078-7d14a60cf9e4.png">

### TwilioSync
作成したTwilioSyncを削除します。
利用しなければ費用は発生しませんが、気になる場合は、削除しておきます。
※例えば、TwilioAssetでアップロードしている決済状況表示画面にアクセスするだけで、利用したことになります。
Twilioの管理コンソールから**Sync** →　**Service** を選択します。
<img width="897" alt="スクリーンショット 2019-03-05 18.38.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/16bce74a-2312-4249-b147-b57d555ce89f.png">
作成したSyncServiceの名前を選択、表示された画面の下部にある**Delete this service**をクリックします。
<img width="779" alt="スクリーンショット 2019-03-05 18.40.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/110436/0d0a8076-7c10-6f7d-eb52-c87103b158a1.png">

# 補足
この状態だと、クレジットカード番号をプッシュしている音声がオペレータ役に聞こえてしまうので、聞こえないようにConferenceのMute機能を実装する必要があります。

# お礼
今回の資料を作成するにあたり、以下を参考にさせていただきました。ありがとうございます。
https://qiita.com/mobilebiz/items/4490fe5a03c5192ce06f