TwilioのLookup APIを利用して、Stripe Elementsの決済画面に電話番号の誤入力を予防する仕組みを追加する

この記事は、[Twilio Advent Calendar 2022](https://qiita.com/advent-calendar/2022/twilio) 16日目の記事です。

ECやサブスクリプションビジネスにおいて、顧客との連絡手段を確保することはとても重要です。

中でも電話番号は、商品の発送時や、本人確認などのSMSを送信する時にも利用することがあります。

Stripeの場合、「リダイレクト型決済」の**Checkout**では、電話番号の情報入力もStripe側に任せることができます。

https://stripe.com/docs/payments/checkout/phone-numbers

ただしElementsでサイトやアプリに埋め込む場合には、フィールドの作成などある程度の開発が必要です。

この記事では、ユーザーの操作ミスで、誤った電話番号が入力されたまま申し込みが完了しないように、TwilioのLookup APIを利用したバリデーションを組み込む方法を紹介します。

https://www.twilio.com/docs/lookup/api

## 電話番号の検証付き決済フォームを実装する

### Step1: クイックスタートのアプリをDLする

今回はStripeの組み込み作業を省力化するため、クイックスタートのサンプルアプリを利用します。

![スクリーンショット 2022-12-15 17.28.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/7874e3ec-30c8-d3a1-7afc-4ae0dd272d19.png)

https://stripe.com/docs/payments/quickstart

記事と同じ設定で進めたい方は、以下の設定で[アプリ全体をダウンロード]ボタンをクリックしましょう。

- プラットフォーム: **Web**
- フロントエンド: **React**
- バックエンド: **Node**
- フロー: **カスタムの決済フロー**

:::note info
stripe.com/docs　のサンプルアプリは、ダッシュボードにログインした状態でDLしましょう。
サンプルコード内のAPIキーが、現在ログインしているアカウントのものに自動で切り替わります。
:::

DLしたアプリのライブラリインストールと、サービスの起動を行いましょう。

```bash
% npm install
% npm start
```

http://localhost:3000/checkout にアクセスすると、決済フォームが表示されます。

![スクリーンショット 2022-12-15 17.07.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/04b9d1f7-41b9-43c5-535f-516b8d9a9887.png)


### Step2: 電話番号入力フォームを追加する

サンプルアプリの用意ができましたので、早速電話番号の入力フォームを追加します。

まずは必要なライブラリを追加します。

```bash
$ npm install validator twilio
```

`src/CheckoutForm.jsx`を次のように変更しましょう。

```diff_javascript
      <PaymentElement id="payment-element" options={paymentElementOptions} />
+      <div
+        style={{
+          display: "flex",
+          justifyContent: "start",
+          flexDirection: "column",
+          margin: "10px auto",
+        }}
+      >
+        <label htmlFor="phone">Phone:</label>
+        <input
+          type="tel"
+          id="phone"
+          style={{
+            border: "1px solid rgb(230, 230, 230)",
+            width: "100%",
+            padding: "7px",
+            boxSizing: "border-box",
+          }}
+          />
+      </div>
      <button disabled={isLoading || !stripe || !elements} id="submit">
```

電話番号入力フォームが表示されました。

![スクリーンショット 2022-12-15 18.54.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/1ad3b0f3-dcd1-f6ba-394e-dbb388559a48.png)

入力された番号を、React Hookでstateに格納しましょう。

```diff_javascript
  const [isLoading, setIsLoading] = useState(false);
+  const [phoneNumber, setPhoneNumber] = useState('')

  useEffect(() => {

...
        <input
          type="tel"
          id="phone"
          style={{
            border: "1px solid rgb(230, 230, 230)",
            width: "100%",
            padding: "7px",
            boxSizing: "border-box",
          }}
+          onChange={e => {
+            setPhoneNumber(e.target.value)
+          }}
          />
```


### Step3: 入力された電話番号を検証する

決済処理の中で、電話番号の検証を行いましょう。

まずは検証のためのAPIを用意します。

`server.js`を次のように変更します。

```diff_javascript
const express = require("express");
+const { Twilio } = require("twilio");
+const client = new Twilio('username', 'password')

...

+app.post("/verify-phone-number", async (req, res) => {
+  const { 'phone_number': phoneNumber } = req.body;
+  try {
+    await client.lookups
+      .phoneNumbers(`+81${phoneNumber}`)
+      .fetch({ type: ['carrier'] });
+    res.status(200).send()
+  } catch (e) {
+    if (e.code === 20404) {
+      res.status(400).json({
+        message: "Invalid phone number"
+      })
+      return
+    }
+    res.status(e.status).json(e)
+  }
+});

app.listen(4242, () => console.log("Node server listening on port 4242!"));

```

TwilioのLookup APIを利用して、電話番号の検索を行います。

存在しない電話番号の場合には、エラーを返します。

:::note warn
今回は便宜上国コード(`+81`)を直接設定しています。
実際の運用では、入力フォーム側でコードを設定するか、自動判定するように実装しましょう。
:::

続いて`src/CheckoutForm.jsx`で、追加したAPIを呼び出す処理を追加しましょう。

```diff_javascript

+  const [errorMessage, setErrorMessage] = useState('');
  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!stripe || !elements) {
      // Stripe.js has not yet loaded.
      // Make sure to disable form submission until Stripe.js has loaded.
      return;
    }

    setIsLoading(true);

+    const result = await fetch('/verify-phone-number', {
+      method: "POST",
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify({
+        phone_number: phoneNumber
+      })
+    });
+    if (!result.ok) {
+      const { message } = await result.json();
+      setErrorMessage(message);
+      setIsLoading(false);
+      return;
+    }
    const { error } = await stripe.confirmPayment({
      elements,
      confirmParams: {
        // Make sure to change this to your payment completion page
        return_url: "http://localhost:3000",
      },
    });
```

これで、フォームのSubmit処理の中で、電話番号の検証も行えるようになりました。

日本以外または存在しない電話番号を入力すると、エラーが発生し、決済が中断されるようになります。

![スクリーンショット 2022-12-15 19.34.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/1a559aa5-8604-0992-defb-a3ed7cf01ed8.png)

## Verify APIでの、より強力な検証

Twilioでは、電話番号の確認だけでなくSMSや音声通話で確認コードを送信することもできます。

https://www.twilio.com/docs/verify/api

https://www.twilio.com/blog/build-fast-checkout-with-sms-verification-using-stripe-and-twilio-jp

この機能を利用することで、より強力な本人確認を実装できます。