カートに入れました

Gatsbyjsで作ったサイトにStripeの決済機能を組み込み、ショッピングカート機能をつける

GatsbyのプロジェクトにStripe決済をつける方法は サーバー不要Gatsbyプロジェクトで商品を売る方法。stripeの決済機能実装。

上のリンクの続きになります

パッケージをインストール

yarn add use-shopping-cart

コードを追加

use-shopping-cartを使用するにはプロバイダーコンポーネントの中に商品を含めないといけないのでgatsby-browser.jsにプロバイダーコンポーネントを追加する

gatsby-browser.js
import './src/style.css'
import 'prismjs/themes/prism.css'

import React from "react"
import { CartProvider } from 'use-shopping-cart'

export const wrapRootElement = ({ {element} }) => {
  return (
    <CartProvider
      mode={"payment"}
      cartMode={"client-only"}
      stripe={process.env.GATSBY_STRIPE_PUBLISHABLE_KEY}
      successUrl={'https://hi1t0.com/checkout-success'}
      cancelUrl={'https://hi1t0.com/products'}
      currency={'jpy'}
    >
      {element}
    </CartProvider>
  )
}

今すぐ買うボタン

プロダクトカードコンポーネントを書き換える

src/component/products/ProductCard.js
import React, { useState } from "react"
// import getStripe from "../../utils/stripe"
import { useShoppingCart } from 'use-shopping-cart'

...

const formatPrice = (amount, currency) => {
  ...
  return numberFormat.format(price)
}

const ProductCard = ({ product }) => {
  const [loading, setLoading] = useState(false)
  const { checkoutSingleItem } = useShoppingCart()

  const handleSubmit = async event => {
    event.preventDefault()
    setLoading(true)
    // price_id
    const price = new FormData(event.target).get("priceSelect")
    const { error } = await checkoutSingleItem(price)
    // const stripe = await getStripe()
    // const { error } = await stripe.redirectToCheckout({
    //   mode: "payment",
    //   lineItems: [{ price, quantity: 1 }],
    //   successUrl: `${window.location.origin}/checkout-success/`,
    //   cancelUrl: `${window.location.origin}/products`,
    // })

    if (error) {
      console.warn("Error:", error)
      setLoading(false)
    }
  }

  return (
    <div className="column is-6">
      <form onSubmit={handleSubmit} className="has-text-centered">
        <div style={cardStyles}>
          {
            product.images.length > 0 ?
            <img src={product.images[0]} alt={product.name}/>
            : ''
          }
          <fieldset style={cardMain}>
            <legend style={legendStyles}>
              <h4>{product.name}</h4>
              <p>{product.description}</p>
            </legend>
            <label style={priceStyles}>
              価格{" "}
              <select name="priceSelect" style={selectBox}>
                {product.prices.map(price => (
                  <option key={price.id} value={price.id}>
                    {formatPrice(price.unit_amount, price.currency)}
                  </option>
                ))}
              </select>
            </label>
          </fieldset>
          <button
            disabled={loading}
            style={
              loading
                ? { ...buttonStyles, ...buttonDisabledStyles }
                : buttonStyles
            }
          >
            今すぐ買う
          </button>
        </div>
      </form>
    </div>
  )
}

export default ProductCard

商品追加ボタン

src/component/products/ProductCard.js
const ProductCard = ({ product }) => {
  const [loading, setLoading] = useState(false)
  const [priceId, setPriceId] = useState(product.prices[0]?.id)
  const { checkoutSingleItem, addItem } = useShoppingCart()

  const onChangePrice = event => {
    setPriceId(event.target.value)
  }

  const handleAddItem = event => {
    setLoading(true)
    const priceIndex = product.prices.findIndex(p => p.id === priceId)
    addItem({price_id: product.prices[priceIndex].id, price: product.prices[priceIndex].unit_amount, currency: product.prices[priceIndex].currency, name: product?.name,})
    setLoading(false)
  }

  const handleSubmit = async event => {
    event.preventDefault()
    setLoading(true)
    // price_id
    const price = new FormData(event.target).get("priceSelect")
    const { error } = await checkoutSingleItem(price)

    if (error) {
      console.warn("Error:", error)
      setLoading(false)
    }
  }

  return (
    <div className="column is-6">
      <form onSubmit={handleSubmit} className="has-text-centered">

        ...

            <label style={priceStyles}>
              価格{" "}
              <select name="priceSelect" style={selectBox} onChange={onChangePrice}>
                {product.prices.map(price => (
                  <option key={price.id} value={price.id}>
                    {formatPrice(price.unit_amount, price.currency)}
                  </option>
                ))}
              </select>
            </label>
          </fieldset>
          <button
            type="button"
            onClick={(e) => handleAddItem(e)}
            disabled={loading}
            style={
              loading
                ? { ...buttonStyles, ...buttonDisabledStyles }
                : buttonStyles
            }
          >
            追加する
          </button>
          <button
            disabled={loading}
            style={
              loading
                ? { ...buttonStyles, ...buttonDisabledStyles }
                : buttonStyles
            }
          >
            今すぐ買う
          </button>

...

カートページ作成

src/pagesにcart.jsファイルを追加

中身は

src/pages/cart.js
import React from "react"

import Layout from "../components/Layout"
import { useShoppingCart } from 'use-shopping-cart'

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

export default CartPage

ページ表示

  • カートページ(商品追加していない)

  • 商品の追加するを押す

  • 再びカートページ(2点追加)

  • 注文するをクリック

2点分の合計金額で注文できるようになりました

ビルドしたらエラー出た

error Building static HTML failed for path "/cart/"

  16 |   return function useDispatch() {
  17 |     var store = useStore();
> 18 |     return store.dispatch;
     | ^
  19 |   };
  20 | }
  21 | /**


  WebpackError:TypeError: Cannot read property 'dispatch' of undefined

gatsby-ssr.jsファイルを作り内容をgatsby-browser.jsと同じとする

gatsby-ssr.js
import React from "react"
import { CartProvider } from 'use-shopping-cart'

export const wrapRootElement = ({ element }) => {
  return (
    <CartProvider
      mode={"payment"}
      cartMode={"client-only"}
      stripe={process.env.GATSBY_STRIPE_PUBLISHABLE_KEY}
      successUrl={'https://hi1t0.com/checkout-success'}
      cancelUrl={'https://hi1t0.com/cart'}
      currency={'jpy'}
    >
      {element}
    </CartProvider>
  )
}

これでビルドは通り本番環境にも反映された

参考