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
の子要素で利用できます。
そのため、実際の組み込みではPaymentElement
やform
はElements
とは別のコンポーネントに実装します。
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本ペースで更新中です。