React + Netlify FunctionsでStripeの決済処理を実装する

この記事はJAMstack Advent Calendar 2019 21日目の記事です。

JAMstackのアドベントカレンダーは7日目も参加しています。
qiita.com

こんにちはかみむらです。この記事ではReactでUIを作成して、Netlify Functionsを使ってStripeの決済処理を実装していきます。Stripeで決済処理するためにはサーバーを必要とするため、Netlify Functionsで作成したLambda関数を使って処理していきます。


JAMstackアーキテクチャの絶対条件として、サーバーに依存しない静的なサイトである必要があります。そのため、サーバーを伴う動的な処理(認証やコンテンツ管理、決済処理など)は、外部のAPIを使って実装していきます。今回はJAMstackなサイトに決済処理を実装する一例として、Stripeを使います。

JAMstackアーキテクチャの詳細については、こちらも一読してください。

code-log.hatenablog.com

今回書いたコードはこちらにあります。

github.com

github.com

前提

Stripe

Stripeのアカウントを作成して、APIキーを取得してください。APIキーはStripeのダッシュボードからテスト API キーの取得で確認できます。

f:id:kamimura-dev:20191220121808p:plain

stripe.com

Stripeのドキュメント

stripe.com

プロジェクトの作成

まずはcreate-react-appでプロジェクトを作成していきます。

$ npx create-react-app my-app

そして、アプリケーションが無事に起動するか試してみましょう。

$ yarn start

react-stripe-elementsの導入

Reactで簡単にStripeのUIを導入するためのコンポーネントライブラリを使います。こちらのライブラリです。
github.com

react-stripe-elementsのインストール

$ yarn add react-stripe-elements

そして、Stripe.jsを読み込みます。プロジェクトのpublicフォルダにあるindex.htmlファイルのHeadタグ内に下記のscriptを追加します。

public/index.html

  <head>
    <title>React App</title>
    <script src="https://js.stripe.com/v3/"></script>
  </head>

チェックアウトフォームの作成

Netlify Functionsで作成したLambda関数に、クライアントからリクエストを送って決済処理を行います。Promiseベースのaxiosを使うと、シンプルにリクエストの処理を書くことができます。

axiosのインストール

$ yarn add axios

そして、こちらがチェックアウトフォームの内容です。

src/CheckoutForm.js

import React, {useState} from 'react';
import axios from 'axios';
import {CardElement, injectStripe} from 'react-stripe-elements';

const IS_PROD = process.env.NODE_ENV === 'production';

const ENDPOINT = IS_PROD ? 'netlify_url' : 'http://localhost:9000/charge';

const CheckoutForm = (props) => {
  const [amount, setAmount] = useState(2000);

  const handleSubmit = async (e) => {
    try {
      const {token} = await props.stripe.createToken({name: 'Name'});

      const res = await axios.post(
        ENDPOINT,
        {
          amount: amount,
          body: token.id,
        },
        {
          headers: {
            'Content-Type': 'application/json',
          },
        },
      );

      if (res) {
        console.log('ok');
      }
    } catch (err) {
      console.log(err);
    }
  };
  return (
    <div>
      チェックアウト
      <CardElement />
      <button onClick={handleSubmit}>チェックアウト</button>
    </div>
  );
};

export default injectStripe(CheckoutForm);

環境変数で本番環境とローカル環境のエンドポイントを分けています。netlify_urlには後ほどNetlify Functionsへデプロイしたときに発行されるエンドポイントを入れます。

const ENDPOINT = IS_PROD ? 'netlify_url' : 'http://localhost:9000/charge';

useStateでamountの合計値を設定します。今回は2000円決済します。

const [amount, setAmount] = useState(2000);

createToken()を呼び出して、カード情報と支払い情報をStripe上に登録します。ここで利用できるようになるinjectStripe()でnameキーの値として、'Name'文字列を渡しています。そして、そこで発行されるトークンをLambda関数に送ります。

 const {token} = await props.stripe.createToken({name: 'Name'});

      const res = await axios.post(
        ENDPOINT,
        {
          amount: amount,
          body: token.id,
        },
        {
          headers: {
            'Content-Type': 'application/json',
          },
        },
      );

こちらがApp.jsの内容です。StripeProviderコンポーネントは、Stripeを初期化し、APIキーを渡しています。your_apiではSripeのダッシュボードで確認したAPIキーを入れます。

src/App.js

import React from 'react';
import {Elements, StripeProvider} from 'react-stripe-elements';
import CheckoutForm from './CheckoutForm';

const App: React.FC = () => {
  return (
    <div className="App">
      <StripeProvider apiKey="your_api">
        <Elements>
            <CheckoutForm />
        </Elements>
      </StripeProvider>
    </div>
  );
}
export default App;

Netlify Functionsで決済処理

次にNetlify FunctionsでLambda関数を作成していきます。新規のプロジェクトを作成しましょう。

$ mkdir my-functions && cd my-functions
$ yarn init -y

そして、今回必要なパッケージをインストールします。

$ yarn add stripe netlify-lambda dotenv

こちらはLambda関数をデプロイするときの定義をします。netlify.tomlを追加します。
netlify.toml

[build]
  command = "yarn run build"
  functions = "build"
  Publish = "build"

Lambda関数を実行するscriptsも追加します。ここではyarn run devlocalhost:9000で実行することができます。yarn run buildでビルドすることができます。

  "scripts": {
    "dev": "netlify-lambda serve ./functions",
    "build": "netlify-lambda build ./functions"
  }

.envファイルを作成して、Stripeのシークレットキー環境変数に入れます。このキーは外部に漏らしてはいけないので保護します。
.env

STRIPE_SECRET_KEY=""

こちらがLambda関数のロジックです。
functions/charge.js

require('dotenv').config();

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const headers = {
  'Access-Control-Allow-Origin': 'http://localhost:3000',
  'Access-Control-Allow-Headers': 'Content-Type',
};

exports.handler = async function(event) {
  if (event.httpMethod !== 'POST') {
    return {
      statusCode: 200,
      headers,
      body: 'Not POST Request',
    };
  }

  const data = JSON.parse(event.body);

  if (event.httpMethod === 'POST') {
    try {
      await stripe.charges.create({
        amount: parseInt(data.amount),
        currency: 'jpy',
        description: 'An example charge',
        source: data.body,
      });
      return {
        statusCode: 200,
        headers,
        body: event.body,
      };
    } catch (err) {
      console.log(err);
    }
  }
};

ここではStripeライブラリを使って初期化します。先ほど指定した.envの変数を読み取ります。

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

そしてStripeクライアントライブラリを使用して、リクエストされたトーク金額で決済処理を作成しています。ここではjpy(円)を指定しています。

 await stripe.charges.create({
        amount: parseInt(data.amount),
        currency: 'jpy',
        description: 'An example charge',
        source: data.body,
      });

ローカルで実行

ローカルで決済処理を確認するために、ReactアプリケーションとNetlify Functionsを2つ起動する必要があります。

Netlify Functions

$ yarn dev

Reactアプリケーション

$ yarn start

4242 4242 4242 4242VISAカードでテストすることができます。必要項目を入力後、チェックアウトをクリックしてみましょう。

f:id:kamimura-dev:20191220181624p:plain

テストカードのドキュメントはこちらを読んでください。
stripe.com

そして、Stripeのダッシュボードを見てみましょう。設定した2000円が決済されてることが確認できます。

f:id:kamimura-dev:20191220182907p:plain

これで、Stripeの決済処理が完成しました。

Netlifyへデプロイ

今回作成したNetlify Functionsをデプロイしていきます。あらかじめ今回のプロジェクトをGitHubにプッシュしてください。

www.netlify.com

GitHubと連携後、ビルド設定をしていきます。

f:id:kamimura-dev:20191220222623p:plain

そして、Stripeのシークレットキーを環境変数にいれます。設定は以上です。デプロイしてみましょう。

f:id:kamimura-dev:20191220222723p:plain
これでデプロイが完了しました。

f:id:kamimura-dev:20191220223703p:plain

最後に

簡易的な決済処理を実装しました。今後、JAMstackアーキテクチャとStripeを用いた事例が増えてくればいいですね。