Stripe Checkoutの決済ページへのリダイレクト方法を、`redirectToCheckout`から変更する

2022/04現在、ドキュメントではStripe Checkoutで決済ページにリダイレクトする際に、「Stripe.js SDKの`redirectToCheckout`を**利用しない**」方法を紹介しています。

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

この記事では、`redirectToCheckout`を利用しない方法への切り替えについて紹介します。

## `redirectToCheckout`を利用しない理由

2021/09より、ドキュメントで紹介している標準的な方法から、`redirectToCheckout`は外れています。

これにはいくつかの理由がありますが、2022/4時点で最もインパクトの大きい違いは、「カスタムドメインが利用できないこと」です。

https://stripe.com/docs/payments/checkout/custom-domains#troubleshooting-integration

https://qiita.com/hideokamoto/items/d36602a126aa4edf862c

`redirectToCheckout`を利用したリダイレクトでは、カスタムドメインの適用されていないCheckoutページにリダイレクトします。

## `redirectToCheckout`を利用しない方法: サーバー側編

ドキュメントで紹介している方法は、サーバー側でリダイレクトさせるものです。

```js
  const session = await stripe.checkout.sessions.create({
    line_items: [
      {
        // Provide the exact Price ID (for example, pr_1234) of the product you want to sell
        price: '{{PRICE_ID}}',
        quantity: 1,
      },
    ],
    mode: 'payment',
    success_url: `${YOUR_DOMAIN}/success.html`,
    cancel_url: `${YOUR_DOMAIN}/cancel.html`,
    automatic_tax: {enabled: true},
  });

  res.redirect(303, session.url);
```

サーバー側でリダイレクトさせることで、HTTPステータスコードでもリダイレクトを示すことができます。

ただし、この方法に切り替えるには、「JSONを受けるAPIを非同期で呼び出す」のではなく、「ページを遷移する」必要があります。

`form`タグを利用して遷移する方法や、Next.jsなどでリダイレクト用ページを用意する方法などに切り替えましょう。

### `form`タグを利用して遷移する方法

formタグを利用する場合、ボタンの実装は以下のようなイメージです。

```html
<form action="/api/checkout_session" method="POST">
  <input type='hidden' name='price' value="price_xxx" />
  <input type='hidden' name='quantity' value="1" /> 
  <button type='submit'>注文する</button>
</form>
```

サーバー側では、リクエストBodyからinputに設定したデータを取得して利用します。

```js
export default async function handler(req, res) {
    if (req.method.toLocaleLowerCase() !== 'post') {
        return res.status(405).end()
    }
    const { price, quantity } = req.body
    const stripe = new Stripe(process.env.STRIPE_API_KEY, {
        apiVersion: '2020-08-27',
        maxNetworkRetries: 3,
    })
    const session = await stripe.checkout.sessions.create({
        mode: 'payment',
        line_items: [{
          price,
          quantity,
        }],
        success_url: 'http://localhost:3000',
        cancel_url: 'http://localhost:3000/'
    })
    return res.redirect(301, session.url)
}
```

この方法は、複数の商品情報など、送信すべきデータの量が増えた場合の実装が複雑になる可能性があることに注意しましょう。

もしデータの量が多い場合や可変する場合などでは、form以外の方法でリダイレクトさせることも検討しましょう。

### Next.jsでリダイレクト用ページを用意する方法

Next.jsなど、サーバー側の処理も実装できるフレームワークでは、リダイレクト用のページを用意する方法もとれます。

この場合、注文する商品データをCookieやカート用のデーターベースサービスに保存するなどの方法が必要です。

```jsx
import { parseCookies } from "nookies";

export async function getServerSideProps(context) {
    const cookies = parseCookies(context)
    const userId = typeof cookies.userId === "undefined" ? "" : cookies.userId
    // 外部DBまたはCookieなどからカートのデータを受け取る
    const cartItem = await getCartItemByUserId(userId)
    try {
        // セッションを作成する
        const session = await stripe.checkout.sessions.create({ /*..*/ })
    
        res.redirect(303, session.url);
        if (!session || !session.url) {
            throw new Error("Failed to create a new checkout session")
        }
        context.res.writeHead(303, {
            Location: session.url
        })
        context.res.end()
    } catch (e) {
        return {
            props: {
                errorMessage: e.message
            }
        }
    }
}
```

この方法では、`pages/checkout_session.js`のように、Checkout Sessionを作成してリダイレクトする専用のページを用意しています。
成功した場合はリダイレクトされるので、ページのコンポーネントではエラー情報を表示させるか、こちらもTOPページなどにリダイレクトさせる方法などをとります。

## `redirectToCheckout`を利用しない方法: クライアント側編

もしサーバー側でリダイレクトを組み込むのが難しい場合は、クライアント側で`redirectToCheckout`を利用しない実装を行います。

もっとも手早い方法は、`window.open`や`location.href`でリダイレクトまたは別窓でページを開くことです。

```diff_jsx
// Checkout Sessionの結果を受け取るAPI
const session = await fetch('/api/checkout_session').then(data => data.json())
- await stripe.redirectToCheckout({ sessionId: session.id })
+ window.open(session.url) // 別窓にする場合
+ location.href = session.url // 同じ窓で遷移する場合
```

この実装の場合、APIのレスポンスに`session`の`id`ではなく`url`を含める必要があることに注意しましょう。

## 終わりに

`redirectToCheckout`を使わずにCheckoutの決済ページに移動する方法を紹介しました。

APIが利用できなくなるわけではありませんが、ドメインのようにうまく動かない機能もあるため、利用にはご注意ください。