Amazon Cognito User Poolsに登録するユーザーの住所情報を、StripeのAddress Elementで収集する [React編]

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

Webサービスやアプリケーションをリリースする際、ユーザー管理機能としてCognito User Poolsは欠かすことができません。

AWS Amplifyでも、Auth機能を利用するとこのサービスがセットアップされるなど、さまざまな場面で登場します。

この記事では、Cognito User Poolsで管理しているユーザー情報のうち、住所情報を入力するフォームを、Stripeが用意するAddress Elementsで実装する方法を紹介します。

![スクリーンショット 2022-11-19 10.06.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/1f035708-20aa-6c5b-d97c-0b614def2579.png)

## Address Elementsで住所入力フォームを作成する

Stripeの埋め込み型住所入力フォームは、`@stripe/react-stripe-js`ライブラリを利用します。

```bash
$ npm i @stripe/stripe-js @stripe/react-stripe-js
```

また、AWS AmplifyのSDKもインストールしましょう。

```bash
$ npm i aws-amplify
```

フォームを表示させるために、Provider要素の`Elements`と一緒に`AddressElement`を配置しましょう。

```typescript
import { loadStripe } from '@stripe/stripe-js'
import { AddressElement, Elements } from '@stripe/react-stripe-js'

export default function Root() {
  return (
    <Elements
      stripe={loadStripe('pk_test_から始まるStripeのテスト用公開可能APIキーを配置')}
      options={{
        appearance: {
          theme:  'stripe',
        },
      }}
    >
      <form>
          <AddressElement
            options={{
              mode: 'billing',
              allowedCountries: ['JP'],
              fields: {
                phone: 'always'
              }
            }}
          />
          <button type='submit'>保存する</button>
      </form>
    </Elements>
  )
}
```

このコンポーネントを読み込むと、住所フォームが表示されます。

![スクリーンショット 2022-11-30 16.35.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/5d98cc1b-0225-d4c0-9dba-e898c400916d.png)

郵便番号をセットすると、残りのフォームが表示されます。

![スクリーンショット 2022-11-21 13.19.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/fb35dd1e-9cd5-0a20-6d0a-04f05bb32eef.png)

### 日本以外の住所を受け付ける方法

`AddressElement`の`allowedCoutries`を削除、またはサポートしたい国を追加してください。

```diff_javascript
          <AddressElement
            options={{
              mode: 'billing',
-              allowedCountries: ['JP'],
              fields: {
                phone: 'always'
              }
            }}
          />
```

入力フォームが変わり、指定した国によってフォームが変わるようになります。

![スクリーンショット 2022-11-19 10.05.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/d62a36c8-44ee-3c68-ab1e-66e44481a60e.png)

### [optional] オートコンプリートをサポートする

Google Map APIを利用したり、決済フォームと併せて配置することで、オートコンプリートを有効できます。

![スクリーンショット 2022-11-21 13.20.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/be132584-4893-5330-9016-de74c07b995d.png)

それぞれの方法は、以下の記事をご覧ください。

https://qiita.com/hideokamoto/items/19b07b7d543974913648

https://qiita.com/hideokamoto/items/25e1cccb03b049b066fd

## Amplify SDKで入力した情報をCognito User Poolsに反映する

まずは入力された情報を、`useState`でハンドルしましょう。

```diff_typescript
import { loadStripe } from '@stripe/stripe-js'
import { AddressElement, Elements } from '@stripe/react-stripe-js'
+import { useState } from 'react'

export default function Root() {
+  const [userProfile, setUserProfile] = useState<{
+    address?: string;
+    phone_number?: string;
+    locale?: string;
+    name?: string;
+  }>({})
  return (
    <Elements
      stripe={loadStripe('pk_test_から始まるStripeのテスト用公開可能APIキーを配置')}
      options={{
        appearance: {
          theme:  'stripe',
        },
      }}
    >
      <form>
          <AddressElement
            options={{
              mode: 'billing',
              allowedCountries: ['JP'],
              fields: {
                phone: 'always'
              }
            }}
+            onChange={e => {
+              const {
+                address,
+                phone,
+                name,
+              } = e.value
+              setUserProfile({
+                address: address.state,
+                locale: address.country,
+                phone_number: phone,
+                name,
+              })
            }}
          />
          <button type='submit'>保存する</button>
      </form>
    </Elements>
  )
}
```

もしCognito User Poolsに、State以外の情報も保存したい場合は、次のようなコードを追加しましょう。

```diff_typescript
              const {
                address,
                phone,
                name,
              } = e.value
+              const supportKeys = ['state', 'city', 'line1', 'line2'] as const
+              const userAddress = supportKeys.map(key => {
+                if (!address[key]) return
+                const value = address[key]
+                return value
+              }).filter(Boolean).join(' ')
              setUserProfile({
-                address: address.state,
+                address: userAddress,
                locale: address.country,
                phone_number: phone,
                name,
              })
```

あとはAmplify SDKを利用して、保存するだけです。

```diff_typescript
import { loadStripe } from '@stripe/stripe-js'
import { AddressElement, Elements } from '@stripe/react-stripe-js'
import { useState } from 'react'
+import { Auth } from 'aws-amplify'

export default function Root() {
  const [userProfile, setUserProfile] = useState<{
    address?: string;
    phone_number?: string;
    locale?: string;
    name?: string;
  }>({})
  return (
    <Elements
      stripe={loadStripe('pk_test_から始まるStripeのテスト用公開可能APIキーを配置')}
      options={{
        appearance: {
          theme:  'stripe',
        },
      }}
    >
      <form
        onSubmit={async e => {
          e.preventDefault()
+          const user = Auth.currentAuthenticatedUser()
+          Auth.updateUserAttributes(user, userProfile)
        }}
      >
          <AddressElement
            options={{
              mode: 'billing',
              allowedCountries: ['JP'],
              fields: {
                phone: 'always'
              }
            }}
            onChange={e => {
              const {
                address,
                phone,
                name,
              } = e.value
              setUserProfile({
                address: address.state,
                locale: address.country,
                phone_number: phone,
                name,
              })
            }}
          />
          <button type='submit'>保存する</button>
      </form>
    </Elements>
  )
}
```

## 入力済みのデータを表示させる方法

`AddressElement`は、`defaultValue`を利用して保存済みのデータを表示できます。

```jsx
  <AddressElement
    options={{
      mode: 'billing',
      defaultValues: {
        name: "John due",
        address: {
          line1: '千代田区千代田',
          line2: '11',
          city: '千代田区',
          country: 'JP',
          postal_code: '100-0001',
          state: '東京都'
        }
      }
    }}
 />
```

`Auth`クラスを利用してデータを取得して、`defaultValues`に値を設定しましょう。

## AddressElementを利用するメリット

グローバルにサービスを提供する場合、住所入力フォームを国ごとに変更する必要があります。

しかし「国の情報を取得」して、「その国に対応した住所入力フォームを提供」する作業は手間がかかります。

StripeのAddressElementを利用することで、入力フォームのローカライズと、データのハンドルを簡単に行えます。

![スクリーンショット 2022-11-30 16.59.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/de462112-e951-b7b9-d0d9-e2a8e0b80e6a.png)