はじめに
node.jsを使ったwebアプリを作成しています。フロント側はReactに任せ、Expressで構築したWebAPIでサーバーサイドの処理をするといったよくある構成です。
このWebアプリ内でクレジットカードによる決済機能を追加したいと考え、いろいろ調べた結果、手数料が安く評価も高いstripeというサービスを利用してみることにしました。
以下からは、stripeのテスト環境を用いて、クレジットカード情報を入力し、決済が完結するまでの実装フローを書いていきます。
以下よりstripeアカウントを取得(無料)している前提で進めていきます
https://dashboard.stripe.com/register
フロント側(React)の処理
準備
npmのライブラリを使用しますが、stripeのライブラリをネットで調べると「
react-elements-stripe 」と「react-stripe-checkout」の2つが多くヒットします。
どちらを使ってもいいと思いますが、前者は自前のアプリケーションの画面内に埋め込む、後者は別画面でといった感じ。この記事では後者のreact-stripe-checkout のほうで進めていきます。
https://github.com/stripe/react-stripe-elements
https://github.com/azmenak/react-stripe-checkout
npm i react-stripe-checkout
続いてReactアプリのpublicディレクトリ内にあるindex.htmlのヘッダで
Stripe.jsの読み込みを追加します
<script src="https://js.stripe.com/v3/"></script>
実装
決済ページを表示したいコンポーネントにCheckoutコンポーネントを追加
以下ではお試しで20000円の決済フォームを作成します。
import React, { Component } from 'react';
import Checkout from './Checkout';
class **** extends Component {
~~
render() {
return (
~~0
<Checkout
amount={20000}
/>
);
}
}
Checkoutコンポーネントを作成
import React from 'react';
import StripeCheckout from 'react-stripe-checkout';
class Checkout extends React.Component {
constructor(props) {
super(props);
}
onToken = (token) => {
console.log(token);
}
render() {
return (
<div>
<StripeCheckout
token={this.onToken}
stripeKey="*****"
image="https://stripe.com/img/documentation/checkout/marketplace.png"
name="お支払"
panelLabel="お支払"
amount={this.props.amount}
currency="JPY"
locale="ja"
>
<Button>お支払へ</Button>
</StripeCheckout>
</div>
);
}
}
export default Checkout;
stripeKeyはstripeのダッシュボードのAPIキーのページに表記されている公開キーの値を指定します。
ここまでで、追加したコンポーネント内で以下のようにボタンを表示させることができ、クリックすると決済用のフォームを表示させることができます。
stripeのドキュメントにあるテスト用のクレジットカード情報を入力してみましょう。
4242 4242 4242 4242 + 任意の有効期限 + 任意の3桁の数字
他の全てのテスト用カード情報は以下に載っています。
https://stripe.com/docs/testing#cards
処理が完了すると、ブラウザコンソールに以下のようなレスポンスが返ってくればOK
このレスポンス内のid(tok_***)の値を使って次に決済の処理を追加していきます。
ダッシュボードをみるとトークンを発行した履歴を確認することができます。
決済処理を追加
トークンを受け取ったあとにサーバー側APIを叩く処理を追加します。
Checkoutコンポーネントに処理を追加
import React from 'react';
import StripeCheckout from 'react-stripe-checkout';
import axios from 'axios';
class Checkout extends React.Component {
constructor(props) {
super(props);
this.state = { token: ""}
}
onToken = (token) => {
this.setState({token: token})
}
componentDidUpdate(){
if(this.state.token !== "") {
this.checkOut(this.state.token.id)
}
}
checkOut(token){
axios.post('localhost:****/api/v1/charge',
{
token: token,
amount: this.props.amount
})
.then(response => {
console.log(response)
}).catch(err => {
console.log('err:', err.response);
});
}
render() {
return (
<div>
<StripeCheckout
token={this.onToken}
stripeKey="*****"
image="https://stripe.com/img/documentation/checkout/marketplace.png"
name="お支払"
panelLabel="お支払"
amount={this.props.amount}
currency="JPY"
locale="ja"
>
<Button>お支払へ</Button>
</StripeCheckout>
</div>
);
}
}
export default Checkout;
このサンプルでは、先ほどの処理で取得したトークンの値をstateに入れ、stateが更新されたタイミングでAPIを叩くようにしています。
APIの中身は後ほど記載します。
ここまででフロント側は終わり。(決済後の画面遷移などはレスポンスを受け取ったあとの部分で自由に作るといいです。)
サーバーサイド(Express)の処理
準備
サーバー側でもstripeのライブラリ等をインストールします
npm i stripe
npm i cors
実装
先ほどの/api/v1/chargeにポストしたときのルーティング部に以下を追加します
const express = require('express');
const router = express.Router();
const cors = require( 'cors' );
const stripe = require("stripe")("SECRET_KEY");
router.use( cors() );
router.post('/', async (req, res) => {
let token = req.body.token
let amount = req.body.amount
try {
let {status} = await stripe.charges.create({
amount: amount,
currency: "jpy",
description: "An example charge",
source: token
});
return res.json({status});
}
catch (err) {
logger.error(err)
return res.status(error.status).json(error)
}
})
module.exports = router;
SECRET_KEY部にはstripeダッシュボードから確認できるシークレットキーの値を指定します。
フロント側から受け取ったトークンと金額の値を取得し、stripeのcharge用のAPIを叩き、結果をフロントに返します。
これで、フロント側、サーバー側共に実装が完了しましたので、どちらも起動した状態で先ほどと同様のテストをしてみると、ダッシュボードで決済まで実行されているのが確認できると思います。
クレジットカード情報は自前のアプリ側で持つ必要がないため安心ですね。
最後に
対価を伴わない決済(例えば寄付など)であれば上記で解説した処理にエラー処理を追加するくらいで使えそうですが、ECサイトなどで使用する場合には、API側で支払いIDと購入された商品情報、送付先などをDBに保持したりといった処理を追加していく必要がありますね。
今回参考にさせていただいた記事
https://code-log.hatenablog.com/entry/2019/09/01/234124
https://qiita.com/maecho/items/0d6f9fb3f575cf189b02