1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Gatsbyで構築したサイトに、Stripeの決済フォーム(Payment Elements)を追加する方法

Posted at

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

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

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



                     Welcome to Gatsby!



This command will generate a new Gatsby site for you in
/Users/hideokamoto/stripe/examples 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?
✔ examples/ stripe-element-gatsby
✔ Will you be using JavaScript or TypeScript?
· TypeScript
✔ 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 stripe-element-gatsby
  
✔ Shall we do this? (Y/n) · Yes
✔ Created site from template
✔ Installed Gatsby
✔ Installed plugins
✔ Created site in stripe-element-gatsby
🎉  Your new Gatsby site  has been successfully created
at /Users/stripe/examples/stripe-element-gatsby.
Start by going to the directory with

  cd stripe-element-gatsby
  
Start the local development server with

  npm run develop
  
See all commands at

  https://www.gatsbyjs.com/docs/gatsby-cli/
  

Stripe.jsライブラリのインストール

Payment Elementsを導入するために必要なライブラリを追加しましょう。

$ npm install @stripe/stripe-js @stripe/react-stripe-js

Publishable APIキーを環境変数経由で設定

続いてPayment Elementsを利用するために必要な「公開可能APIキー(Publishable APIキー)」を追加します。

テスト環境用に、.env.developmentファイルを作成しましょう。

GATSBY_STRIPE_PUBLIC_KEY=pk_test_xxx
GATSBY_API_URL=http://localhost:3000

Gatbyでは、GATSBY_からはじまる環境変数は、フロントエンドのコードからも取得できます。

決済フォームを表示したい場所に、Payment Intent作成ボタンを実装

Payment Elementsなど、Stripe.js SDKが提供する要素を表示するためには、Payment IntentsまたはSetup IntentsおよびOrder API(v2)のclient_secretが必要です。

これはサーバー側の処理で作成しますので、別途以下のようなAPIを用意しましょう。

Express.jsで簡単に実装したサンプル

if (process.env.NODE_ENV !== 'production') {
    require('dotenv').config({
        path: '.env.development'
    });
}
const express = require('express')
const app = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

const stripe = require('stripe')(process.env.STRIPE_SECRET_API_KEY)

//CROS対応(あくまでデモ用)
app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "*");
    res.header("Access-Control-Allow-Headers", "*")
    next();
})

app.listen(3000, () => {
    console.log("Start on port 3000.")
})

// Payment Intent作成
app.post('/create_payment_intent', async (req, res) => {
    const amount = req.body.amount || 1099
    const currency = req.body.currency || 'jpy'
    try {
        const paymentIntent = await stripe.paymentIntents.create({
            amount,
            currency,
        })
        res.status(200).json({
            client_secret: paymentIntent.client_secret,
        })
    } catch (e) {
        console.log(e)
        console.log(e.code)
        res.status(e.statusCode || 500).json({
            code: e.code,
            message: e.message,
        })
    }  
})

APIを呼び出して、作成したPaymentIntentを取得する

用意したAPIを呼び出すコンポーネントを用意しましょう。

Client Secretが必要ですので、取得した値はuseStateに格納します。


export const StripeElementRoot:FC<PropsWithChildren<{}>> = ({children}) => {
    const [paymentIntentClientSecret, setPaymentIntentClientSecret] = useState<string | undefined>(undefined)
    const createPaymentIntent = async (amount: number, currency: string) => {
        try {
            const result = await fetch(process.env.GATSBY_API_URL as string + 'create_payment_intent', {
                method: 'post',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    amount,
                    currency,
                })
            })
            const response = await result.json()
            if (!result.ok) {
                console.log(response)
                throw new Error(response.message)
            }
            setPaymentIntentClientSecret(response.client_secret)
        } catch (e) {
            console.log(e)
        }
    }
    return (
      <button onClick={createPaymentIntent}>Order</button>
    )
}

Payment Elementsを呼び出す

Client Secretが取得できれば、あとはElementsを呼び出すだけです。

+ import { Elements, PaymentElement } from "@stripe/react-stripe-js";
+ import { loadStripe } from "@stripe/stripe-js";
    return (
+     <>
        <button onClick={createPaymentIntent}>Order</button>
+       {paymentIntentClientSecret ? (
+         <Elements
+            stripe={loadStripe(process.env.GATSBY_STRIPE_PUBLIC_KEY as string)}
+            options={{
+                appearance: {
+                  theme: "flat",
+                },
+                clientSecret: paymentIntentClientSecret,
+            }}
+         >
+.         <form onSubmit={async(e) => {
+            e.preventDefault()
+            console.log(true)
+          }}>
+            <PaymentElement />
+            <button type='submit'>Buy</button>
+          </form>
+         </Elements>
+        ): null}
+     </>
    )

注文確定処理は、別コンポーネントを作成して実装

注文を確定させるための処理などを取得できるuseStripeフックは、Elementsの子要素で利用できます。

そのため、実際の組み込みではPaymentElementformElementsとは別のコンポーネントに実装します。

const PaymentComponent:React.FC = () => {
  const stripe = useStripe()
  const elements = useElements()
  return (
    <form onSubmit={async(e) => {
      e.preventDefault()
      if (!stripe || !elements) return
      stripe.confirmPayment({
        elements,
        confirmParams: {
          return_url: 'http://localhost:8000'
        }
      })
    }}>
      <PaymentElement />
      <button type='submit'>Buy</button>
    </form>

  )
}

[応用] gatsby-browser.tsxとContextを使った汎用化

ElementsをPayment IntentsなどのClient Secretの有無で出し分ける必要があるため、コード量が多くなりがちです。

もし汎用化したい場合は、以下のようなProviderを用意しておくのも手かもしれません。

src/use-stripe-elements.tsx

import { Elements } from "@stripe/react-stripe-js"
import React, { createContext, FC, PropsWithChildren, useContext, useState } from "react"
import { loadStripe } from "@stripe/stripe-js"

const StripeElementContext = createContext<{
    createPaymentIntent: (amount: number, currency: string) => Promise<void>
    hasClientSecret: boolean
}>({} as any)

export const useStripeElement = () => useContext(StripeElementContext)

export const StripeElementProvider:FC<PropsWithChildren<{}>> = ({children}) => {
    const [paymentIntentClientSecret, setPaymentIntentClientSecret] = useState<string | undefined>(undefined)
    const createPaymentIntent = async (amount: number, currency: string) => {
        try {
            const result = await fetch(process.env.GATSBY_API_URL as string + 'create_payment_intent', {
                method: 'post',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    amount,
                    currency,
                })
            })
            const response = await result.json()
            if (!result.ok) {
                console.log(response)
                throw new Error(response.message)
            }
            setPaymentIntentClientSecret(response.client_secret)
        } catch (e) {
            console.log(e)
        }
    }
    const ElementProvider = () => {
        if (!paymentIntentClientSecret) return <>{children}</>
        return (
            <Elements
                stripe={loadStripe(process.env.GATSBY_STRIPE_PUBLIC_KEY as string)}
                options={{
                    appearance: {
                      theme: "flat",
                    },
                    clientSecret: paymentIntentClientSecret,
                }}
            >
                {children}
            </Elements>
        )
    }
    return (
        <StripeElementContext.Provider
            value={{
                createPaymentIntent,
                hasClientSecret: !!paymentIntentClientSecret,
            }}
        >
            <ElementProvider />
        </StripeElementContext.Provider>
    )
}

gatsby-browser.tsx

import React from 'react'
import { WrapRootElementBrowserArgs } from 'gatsby'
import { StripeElementProvider } from './src/use-stripe-elements'

export const wrapRootElement = ({ element }: WrapRootElementBrowserArgs) => {
    return (
        <StripeElementProvider>
            {element}
        </StripeElementProvider>
    )
}

src/payment-demo.tsx


const IndexPage = () => {
  const { createPaymentIntent, hasClientSecret } = useStripeElement()
  if (hasClientSecret) {
    return (
        <StripePaymentElement />
    )
  }
  return (
    <button onClick={() => createPaymentIntent(100, 'jpy')}>Create</button>
  )
}

src/payment-element.tsx


const StripePaymentElement = () => {
  return (
      <form onSubmit={async(e) => {
        e.preventDefault()
        console.log(true)
      }}>
        <PaymentElement />
        <button type='submit'>Buy</button>
      </form>
  )
}

Documents

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

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

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

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

1
0
2

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?