Stripe ElementとNext.jsを利用して、クレジットカードの登録画面を構築する

サービスによっては、決済を行う前にクレジットカード情報を入力してもらう動線を作りたい場合があります。
StripeのSetupIntent APIとElementの`confirmSetup`メソッドを利用し、Reactでカード登録フォームを実装する方法を紹介します。


# 前準備

このサンプルでは、Next.jsを利用します。これはSetupIntentの作成にREST APIが必要なため、APIとReactアプリを両方まとめて管理できるためです。

Next.jsでのアプリ起動は、`create-next-app`を利用します。

```bash
% npx create-next-app
```

`.env`ファイルでStripeのAPIキーを環境変数経由で利用できるようにします。

**.env.local**

```env
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
STRIPE_SECRET_KEY=sk_test_xxxx
```

また、実装で利用するライブラリを追加でインストールしましょう。

```bash
% npm install stripe　@stripe/stripe-js　@stripe/react-stripe-js
```

# Step1: Next.jsでSetupIntentを作成するAPIを用意する

支払い方法を保存する場合は、`setupIntent`APIを利用します。

```js
import Stripe from 'stripe'

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
  apiVersion: '2020-08-27'
})
...
const response = await stripe.setupIntents.create({...})

{
  ...
  "client_secret": "seti_xxxxxxxl"
}
```

保存した決済方法を後から利用できるようにするため、顧客（Customer）データの作成も行いましょう。

```js
  const customer = await stripe.customers.create()
  const response = await stripe.setupIntents.create({
    customer: customer.id,
    payment_method_types: ['card']
  })
```

Next.jsの場合、ファイル名がそのままAPIパスになりますので、以下のようなファイルを用意しましょう。

**pages/api/setup-intent.js**

```js
import Stripe from 'stripe'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY)

export default async function handler(
  req,
  res
) {
  if (req.method.toLocaleLowerCase() !== 'post') {
    return res.status(405).end()
  }
  const customer = await stripe.customers.create({
    metadata: {
      app_username: req.body.username
    }
  })
  const response = await stripe.setupIntents.create({
    customer: customer.id,
    payment_method_types: ['card']
  })
  return res.status(200).json({
    client_secret: setupIntent.client_secret,
    customer_id: customer.id,
  })
})
```

`customers.create`で設定しているmetadataは、アプリ側でなにかしらのユーザー管理機能がある場合を想定した設定です。

Stripeの顧客情報からアプリのユーザー情報を取得・アクセスしたい場合などにこのメタデータを利用すると便利です。

# Step2: Reactでカード情報入力フォームを用意する

続いてSetupIntentを利用してカード情報を入力するフォームを実装します。

**pages/index.jsx**

```jsx
import { loadStripe } from "@stripe/stripe-js";
import { Elements, PaymentElement } from "@stripe/react-stripe-js";
import { useEffect, useState } from 'react';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

const Home = () => {
  const [intent, setIntent] = useState(null)
  useEffect(() => {
    fetch('/api/setup-intent', {
      method: 'post'
    }).then(res => res.json())
    .then(data => {
       setIntent(data)
       if (data.customer_id) {
         window.localStorage.setItem('customer_id', data.customer_id)
       }
    })
    .catch(e => {
      console.log(e)
    })
  },[setIntent])
  
  if (!intent || !intent.client_secret) return <p>loading</p>
  const options = {
    appearance:{
      theme: 'stripe',
    },
    clientSecret: intent.client_secret,
  }
  return (
    <Elements stripe={stripePromise} options={options}>
      <PaymentElement  />
    </Elements>
  )
}

export default Home
```

useEffectを利用し、ページが表示されたタイミングでSetupIntentとCustomerを作成しています。
その後、取得したSetupIntentデータをuseStateでStateに格納し、`@stripe/react-stripe-js`ライブラリの`Elements`コンポーネントに渡しています。
また、CustomerのIDの保存にブラウザのlocalStorageを利用しました。あくまでデモ用途ですので、実装時にはfirebaseやCognitoなどのmetadataに保存することをお勧めします。

# Step3: 表示を一度確認する

ここまでで一度表示を確認してみましょう。

```bash
$ yarn dev -p 3000
yarn run v1.22.17
$ next dev -p 3000
ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Loaded env from /Users/hideokamoto/sandbox/element-demo/.env.local
event - compiled client and server successfully in 376 ms (171 modules)
```

エラーが出ていなければ、カード情報を入力するフォームが表示されます。

![スクリーンショット 0004-01-04 18.39.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/837224f0-a260-58fd-f96e-7691bddd79ef.png)


# Step4: 入力したカード情報を保存する

ここからは表示させたカード情報入力フォームのデータを保存する処理を実装します。

**pages/index.jsx**に`Payment`コンポーネントを追加しましょう。

```diff_jsx
import {
 Elements, PaymentElement, 
+ useElements, useStripe
} from "@stripe/react-stripe-js";
import { useEffect, useState } from 'react';

const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY);

 const Payment = () => {
  const stripe = useStripe();
  const elements = useElements();
  return (
    <form onSubmit={async e => {
      e.preventDefault()
      if (!stripe || !elements) return;
      await stripe.confirmSetup({
        elements,
        confirmParams: {
          return_url: 'http://localhost:3000'
        }
      })
    }}>
        <PaymentElement  />
        <button type="submit">Submit</button>
    </form>
  )
}
```

その後、`Home`コンポーネントを以下のように変更します。

```diff_jsx
    <Elements stripe={stripePromise} options={options}>
-        <PaymentElement  />
+        <Payment  />
    </Elements>
```

フォームsubmit時の処理に利用する`useStripe`と`useElements`は、`Elements`の子孫要素でしか利用できません。
そのため、少し手間ですが`Elements`を利用しているコンポーネントとは別のコンポーネントで実装する必要があります。

# Step5: テストカード情報を入力してみる

あとは実際にフォームを動かして動作を確認してみましょう。

テストモードのAPIキーを利用していれば、`4242424242424242`や`5555555555554444`などのテスト用カード番号と、**現在の日付より未来の年月**、ランダムな３桁の数字の３種類でカード情報を入力できます。

https://stripe.com/docs/testing

保存に成功すれば、Stripe Dashboardで顧客データを確認できます。

![スクリーンショット 0004-01-04 18.58.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/0732602f-55d8-91d6-e055-aa06af771f9a.png)

# Tips: Customerデータの更新・登録タイミング

ユーザー名やEmailなどの顧客情報をStripeのCustomerに登録したい場合、今回のサンプルではSetupIntentの作成APIにデータを送信する必要があります。

**pages/index.jsx**

```js
    fetch('/api/setup-intent', {
      method: 'post',
      body: {
        username: getCurrentUsername(),
        email: getCurrentUserEmail(),
      }
    }).then(res => res.json())
```

**pages/api/setup-intent.js**

```diff_javascript
  const customer = await stripe.customers.create({
+   name: req.body.username,
+   email: req.body.email,
    metadata: {
      app_username: req.body.username
    }
  })

```

もしカード情報の入力フォームと一緒に情報を入力させたい場合は、`confirmSetup`より先にAPIを呼び出すことをお勧めします。

```js
await fetch('/api/update-customer', {
  method: 'post',
  body: {
   ...
  }
})
await stripe.confirmSetup(...)
```


# Appendix: 保存後のリダイレクトについて

`stripe.confirmSetup`で保存処理を実行する際、`return_url`に指定したURLへのリダイレクトが実行されます。
そしてリダイレクト後のURlには、以下のクエリ文字列が追加されています。

- setup_intent
- setup_intent_client_secret
- redirect_status

Stripe JS SDKで`retrieveSetupIntent`を実行したい場合などに利用できます。
Next.jsの`useRouter`などを使って取得しましょう。

# [PR] Stripe開発者向け情報をQiitaにて配信中！
2021年12月よりQiitaにて、Stripe開発者のためのブログ記事更新を開始しました。

- [Stripe Updates]：開発者向けStripeアップデート紹介・解説
- ユースケース別のStripe製品や実装サンプルの紹介
- Stripeと外部サービス・OSSとの連携方法やTipsの紹介
- 初心者向けのチュートリアル(予定)

など、Stripeを利用してオンラインビジネスを始める方法について随時更新してまいります。

-> [Stripe Organizationsをフォローして最新情報をQiitaで受け取る](https://qiita.com/organizations/stripe)