LoginSignup
3

More than 1 year has passed since last update.

posted at

Organization

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

以前「カート機能を簡単に実装できるフックライブラリ」として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で受け取る

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
What you can do with signing up
3