Gatsbyで作るサイトに、use-shopping-cartを使ってStripe Checkoutを利用したECカート機能を実装する

以前「カート機能を簡単に実装できるフックライブラリ」としてuse-shopping-cartを紹介しました。

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

今回は、このuse-shopping-cartをGatsbyで作成した静的サイトで利用する方法を紹介します。

# 事前準備

今回紹介する方法では、REST APIなどを使用せず、クライアント側の組み込みのみでCheckoutのセッションを開始します。

そのため、事前にStripeダッシュボードの設定ページで、「クライアント側のみの組み込み」を有効にしましょう。
直接飛ぶ場合: https://dashboard.stripe.com/settings/checkout

**無効化されている状態**

![スクリーンショット 0004-03-10 14.45.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/367b6099-e605-c6b4-441d-d7e70fcae674.png)


**有効になっている状態**
![スクリーンショット 0004-03-10 14.43.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/a086dfc0-008d-634e-3f21-0b46351c6c37.png)


# Gatsbyプロジェクトのセットアップ

まずはGatsbyプロジェクトをセットアップしましょう。
今回はuse-shopping-cart組み込みがメインのため、設定はデフォルトのままにしています。

```bash
$ npm init gatsby
npx: 3個のパッケージを2.144秒でインストールしました。
create-gatsby version 2.9.0



                     Welcome to Gatsby!



This command will generate a new Gatsby site for you in
/Users/sandbox with the setup you select. Let's
answer some questions:


What would you like to call your site?
✔ · My Gatsby Site
What would you like to name the folder where your site will be created?
✔ sandbox/ my-gatsby-site
✔ Will you be using a CMS?
· No (or I'll add it later)
✔ Would you like to install a styling system?
· No (or I'll add it later)
✔ Would you like to install additional features with other plugins?No items were selected



Thanks! Here's what we'll now do:

    🛠  Create a new Gatsby site in the folder my-gatsby-site
  
? Shall we do this? (Y/n) › Yes
```

セットアップが終わったら、プロジェクトのディレクトリへ移動します。

```bash
% cd my-gatsby-site
```

また、環境変数でStripeの公開可能キーを保存しておきましょう。

**env.development**
```
GATSBY_STRIPE_PUBLIC_KEY=pk_test_xxx
```

# use-shopping-cartライブラリを追加する

続いてuse-shopping-cartをインストールしましょう。

```bash
% npm i gatsby-plugin-use-shopping-cart use-shopping-cart
```

# use-shopping-cartの設定を行う

`gatsby-plugin-use-shopping-cart`を使うことで、use-shopping-cartのProvider設定をプラグイン側に任せることができます。
公開可能APIキーなどの設定は`gatsby-config.js`で行いましょう。

```js
/** @type {import('gatsby').GatsbyConfig} */
require("dotenv").config({
  path: `.env.development`,
})

module.exports = {
  siteMetadata: {
      title: ``,
      siteUrl: `https://example.com`,
  },
  plugins: [
    {
      resolve: `gatsby-plugin-use-shopping-cart`,
      options: {
        mode: "payment",
        cartMode: "client-only",
        stripePublicKey: process.env. GATSBY_ STRIPE_PUBLIC_KEY,
        successUrl: "https://example.com/success", // url must start with http or https
        cancelUrl: "https://example.com/cancel", // url must start with http or https
        currency: "JPY",
        billingAddressCollection: true,
      },
    },
  ]
}

```

これでセットアップは完了です。

# use-shopping-cartでサイトを実装する

Gatsbyプラグインで、Providerの設定は完了しています。
あとは、useShoppingCartフックを利用して、カート操作や注文のボタンを実装しましょう。

## 「今すぐ注文」ボタンを作成する

まずは１つの商品をすぐに注文できるボタンを作りましょう。
今すぐ注文ボタンを作るには、`checkoutSingleItem`を利用します。

```jsx
import * as React from "react"
import { useShoppingCart } from 'use-shopping-cart'

const IndexPage = () => {
  const { checkoutSingleItem } = useShoppingCart()
  return (
    <main>
      <div>
        <button onClick={() => {
          checkoutSingleItem('price_xxxx')
        }}>今すぐ注文</button>
      </div>
  </main>
)

export default IndexPage
```

これで、`今すぐ注文`をクリックするとStripeの注文ページにリダイレクトされるようになります。

## カート機能を実装する

続いて、商品を追加・変更できるカート機能を作りましょう。

### カートに商品を追加する

カートへの追加は、`addItem`を利用します。

```jsx
import * as React from "react"
import { useShoppingCart } from 'use-shopping-cart'

const IndexPage = () => {
  const { addItem } = useShoppingCart()
  return (
    <main>
      <div>
        <button onClick={() => {
          addItem({
            name: 'バナナ',
            price_id: 'price_xxxxx',
            price: 400
          })
        }}>バナナを追加する</button>
        <button onClick={() => {
          addItem({
            name: 'パン',
            price_id: 'price_xxxxx',
            price: 400
          })
        }}>パンを追加する</button>
      </div>
  </main>
)

export default IndexPage
```

これで、use-shopping-cart内部にあるstoreに商品のデータを追加できるようになりm最多。

### カートの中身を表示する

追加したカートの中身をみるには、`cartDetails`を利用します。
また、`formattedTotalPrice`を利用することで、合計金額も取得できます。

```jsx

import * as React from "react"
import { useShoppingCart } from 'use-shopping-cart'

const IndexPage = () => {
  const { formattedTotalPrice, cartDetails } = useShoppingCart()
  return (
    <main>
      <div>
        <ul>
          {Object.values(cartDetails).map((cart: any) => {
            return (
              <li key={cart.id}>
                {cart.name} - {cart.formattedPrice} * {cart.quantity} = {cart.formattedValue}
              </li>
            )
          })}
          <li>合計: {formattedTotalPrice}</li>
        </ul>
      </div>
  </main>
)

export default IndexPage
```

これで、カートの中身をユーザーに表示できるようになりました。

![スクリーンショット 0004-03-10 15.09.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/5b285167-94fd-7525-477c-dfa5b59bd140.png)

### カートの商品数を変更できるようにする

カートの数量を変更する関数もフックに用意されています。

```diff_jsx

import * as React from "react"
import { useShoppingCart } from 'use-shopping-cart'

const IndexPage = () => {
-  const { formattedTotalPrice, cartDetails } = useShoppingCart()
+  const { formattedTotalPrice, cartDetails, decrementItem, incrementItem, removeItem } = useShoppingCart()
  return (
    <main>
      <div>
        <ul>
          {Object.values(cartDetails).map((cart: any) => {
            return (
              <li key={cart.id}>
                {cart.name} - {cart.formattedPrice} * {cart.quantity} = {cart.formattedValue}
+                <button onClick={() => decrementItem(cart.price_id)}>１つ減らす</button>
+                <button onClick={() => incrementItem(cart.price_id)}>１つ増やす</button>
+                <button onClick={() => removeItem(cart.price_id)}>削除</button>
              </li>
            )
          })}
          <li>合計: {formattedTotalPrice}</li>
        </ul>
      </div>
  </main>
)

export default IndexPage
```

これらを組み合わせることで、とても簡素ですが通販に欠かせないカートシステムを実装できました。

![スクリーンショット 0004-03-10 15.13.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2366300/6fe2bee8-25fe-e51b-6960-d2e516ae2b39.png)


### 注文ページ(Stripe Checkout)へ移動する

最後にStripe Checkoutへのリダイレクトするボタンを追加しましょう。

注文ページへのリンクは`redirectToCheckout`を利用します。

 
```diff_jsx

import * as React from "react"
import { useShoppingCart } from 'use-shopping-cart'

const IndexPage = () => {
-  const { formattedTotalPrice, cartDetails, decrementItem, incrementItem, removeItem } = useShoppingCart()
+  const { formattedTotalPrice, cartDetails, decrementItem, incrementItem, removeItem, redirectToCheckout } = useShoppingCart()
  return (
    <main>
      <div>
        <ul>
          {Object.values(cartDetails).map((cart: any) => {
            return (
              <li key={cart.id}>
                {cart.name} - {cart.formattedPrice} * {cart.quantity} = {cart.formattedValue}
+                <button onClick={() => decrementItem(cart.price_id)}>１つ減らす</button>
+                <button onClick={() => incrementItem(cart.price_id)}>１つ増やす</button>
+                <button onClick={() => removeItem(cart.price_id)}>削除</button>
              </li>
            )
          })}
          <li>合計: {formattedTotalPrice}</li>
        </ul>
        <button onClick={() => redirectToCheckout()}>注文する</button>
      </div>
  </main>
)

export default IndexPage
```

カート内の商品情報などは、use-shopping-cart側が内部的に処理してくれるため、とてもシンプルな実装で対応できます。

# GatsbyでStripeの料金データを取り込む

ここまでは、料金・商品データをベタ打ちしていました。

ですが、Gatsbyプラグインを利用することで、商品・料金データの取り込みも行えます。

## Gatsbyプラグインと環境変数を設定する

まずはGatsbyのビルド時にStripeのデータを取り込むためのプラグインを追加します。

```bash
% npm i gatsby-source-stripe
```

続いて`gatsby-config.js`で接続のための設定を追加します。

```diff_javascript
  plugins: [
+    {
+      resolve: `gatsby-source-stripe`,
+      options: {
+        objects: ['Price'],
+        secretKey: process.env.STRIPE_SECRET_API_KEY,
+        downloadFiles: false,
+      },
+    },
    {
      resolve: `gatsby-plugin-use-shopping-cart`,

```

また、環境変数でStripeのシークレットAPIキーも保存しておきましょう。

**env.development**
```diff
GATSBY_STRIPE_PUBLIC_KEY=pk_test_xxx
+STRIPE_SECRET_API_KEY=sk_test_xxx
```

## 料金・商品データを取得する

GatsbyからStripeへの接続準備はできました。
あとはGraphQLを利用してデータを取得するだけです。

```jsx
import * as React from "react"
import { graphql, StaticQuery } from 'gatsby'

const IndexPage = () => {
  return (
    <main>
      <StaticQuery
        query={graphql`
          query ProductPrices {
            prices: allStripePrice(
              filter: { active: { eq: true }, currency: { eq: "jpy" } }
              sort: { fields: [unit_amount] }
            ) {
              edges {
                node {
                  id
                  currency
                  unit_amount
                  product {
                    id
                    name
                  }
                }
              }
            }
          }
        `}
        render={({ prices }) => (
          <ul>
            {prices.edges.map(({ node }) => {
              const price = {
                price_id: node.id,
                name: node.product.name,
                price: node.unit_amount,
                currency: node.currency,
              }
              return (
                <pre key={node.id}>
                  <code>{JSON.stringify(node, null, 2)}</code>
                </pre>
              )
            })}
          </ul>
        )}
      />
  </main>
)

export default IndexPage

```

## use-shopping-cartを組み込む

最後に、use-shopping-cartの`addItem`を組み込んで、商品をカートに追加できるようにします。


```diff_jsx
import * as React from "react"
+import { useShoppingCart } from 'use-shopping-cart'
import { graphql, StaticQuery } from 'gatsby'

const IndexPage = () => {
  const { addItem } = useShoppingCart()
  return (
    <main>
      <StaticQuery
        query={graphql`
          query ProductPrices {
            prices: allStripePrice(
              filter: { active: { eq: true }, currency: { eq: "jpy" } }
              sort: { fields: [unit_amount] }
            ) {
              edges {
                node {
                  id
                  currency
                  unit_amount
                  product {
                    id
                    name
                  }
                }
              }
            }
          }
        `}
        render={({ prices }) => (
          <ul>
            {prices.edges.map(({ node }) => {
              const price = {
                price_id: node.id,
                name: node.product.name,
                price: node.unit_amount,
                currency: node.currency,
              }
              return (
+                <li key={price.price_id}>
+                  <button onClick={() => addItem(price)}>
+                    {price.name}を追加する
+                  </button>
+                </li>
              )
            })}
          </ul>
        )}
      />
  </main>
)

export default IndexPage
```

# 終わりに

サーバー側の処理でCheckoutセッションを開始していないため、機能に制限があることに注意が必要です。
ですが、Gatsbyのみで完結するサイトにEC機能を組み込みたい場合、use-shopping-cartとgatsby-source-stripeの組み合わせがとても便利です。

Stripeを使ったオンライン決済の第一歩として、ぜひお試しください。

# [PR] Stripe開発者向け情報をQiitaにて配信中！

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

など、Stripeを利用してオンラインビジネスを始める方法について週に２〜３本ペースで更新中です。

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