5
3

More than 1 year has passed since last update.

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

Posted at

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

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

事前準備

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

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

無効化されている状態

スクリーンショット 0004-03-10 14.45.33.png

有効になっている状態
スクリーンショット 0004-03-10 14.43.34.png

Next.jsプロジェクトのセットアップ

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

$ npx create-next-app@latest
npx: 1個のパッケージを1.696秒でインストールしました。
✔ What is your project named? … demo-nextjs-use-shopping-cart
Creating a new Next.js app in /Users/sandbox/demo-nextjs-use-shopping-cart.

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

% cd demo-nextjs-use-shopping-cart

また、環境変数でStripeの公開可能キーとシークレットAPIキーを保存しておきましょう。

env.development

NEXT_PUBLIC_STRIPE_PUBLIC_KEY=pk_test_xxx
STRIPE_SECRET_API_KEY=sk_test_xxx

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

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

% npm i use-shopping-cart stripe

use-shopping-cartのProviderを設定する

use-shopping-cartを利用するには、Providerコンポーネントを配置する必要があります。

Next.jsの場合、pages/_app.jsを以下のように変更することで、設定できます。

import '../styles/globals.css'
import type { AppProps } from 'next/app'
+import { CartProvider } from 'use-shopping-cart'

function MyApp({ Component, pageProps }: AppProps) {
-  return <Component {...pageProps} />
+  return (
+    <CartProvider
+      mode={"payment"}
+      cartMode={"client-only"}
+      stripe={process.env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY}
+      successUrl={'https://example.com/success'}
+      cancelUrl={'https://example.com/cancel'}
+      currency={'jpy'}
+    >
+      <Component {...pageProps} />
+    </CartProvider>
+  )
}

export default MyApp

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

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

続いて、useShoppingCartフックを利用して、カート操作や注文のボタンを実装しましょう。

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

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

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

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

export default Home

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

カート機能を実装する

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

カートに商品を追加する

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

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を利用することで、合計金額も取得できます。


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

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

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


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)}>1つ減らす</button>
+                <button onClick={() => incrementItem(cart.price_id)}>1つ増やす</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

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

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

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


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)}>1つ減らす</button>
+                <button onClick={() => incrementItem(cart.price_id)}>1つ増やす</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側が内部的に処理してくれるため、とてもシンプルな実装で対応できます。

Next.jsでStripeの料金データを取り込む

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

Next.jsのSSGモードでサイトを構築している場合、getStaticProps関数内でStripe APIを呼び出すことで、料金データを自動で取り込みできるようになります。

import Stripe from 'stripe'

export const getStaticProps = async () => {
  const stripe = new Stripe(process.env.STRIPE_SECRET_API_KEY, {
    apiVersion: '2020-08-27'
  })
  const prices = await stripe.prices.list()
  return {
    props: {
      prices
    }
  }
}

また、use-shopping-cartのaddItemで使える形にするには、以下のような整形を行いましょう。


export const getStaticProps = async () => {
  const stripe = new Stripe(process.env.STRIPE_SECRET_API_KEY, {
    apiVersion: '2020-08-27'
  })
  const prices = await stripe.prices.list()
  return {
    props: {
-      prices
+      prices: prices.data.map(price => {
+        if (!price.active) return null;
+        return {
+          price_id: price.id,
+          name: price.nickname,
+          price: price.unit_amount,
+          currency: price.currency,
+        }
+      })
    }
  }
}

ビルド時の注意

next buildを実行する場合、環境変数を.env.local.env.developmentのみの保存しているとエラーになります。

Error: You did not provide an API key. You need to provide your API key in the Authorization header, using Bearer auth (e.g. 'Authorization: Bearer YOUR_SECRET_KEY'). See https://stripe.com/docs/api#authentication for details, or we can help at https://support.stripe.com/.

これは、ビルド時に読まれる環境変数ファイルが異なるためですので、Next.jsのドキュメントを参考にファイルを配置するようにしましょう。

終わりに

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

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

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

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

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

-> Stripe Organizationsをフォローして最新情報をQiitaで受け取る

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3