StripeをReactで作られたサービスに組み込んでみた話
この記事はStripe Advent Calendar 2017 - Adventar2 日目の記事です。
自分がフロントエンドをやっている、オンラインプログラミング学習サービスCODEPREPに、Stripe決済を組み込んでみた時の話です。
(注意)CODEPREP は2018 年 1 月 4 日をもってプレミアム会員プランを停止したため、このページはもう見ることはできません。
はじめに
CODEPREP は React で作られているので、こちらの React コンポーネントを使ってみました。
自分がやったのは 2017 年 07 月くらいなので、一部内容が最新ではないものがあります。ご注意ください。
仕上がりはこんな感じです。 スタイル周りのカスタマイズが結構できたので、サービスに溶け込ませるように組み込むことができました。
導入方法
紹介するコード概念を説明するものなので、そのままでは動かないかもしれません。ご注意ください。
導入方法はnpm install --save react-stripe-elementsしてから、React コンポーネントを使っていきます。
StripeProvider でアプリケーションをラップする
まず最初にStripeProviderでアプリケーションのルートコンポーネントをラップします。 この時にapiKeyにアプリケーションアクセスキーを設定してください。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// index.jsx
import React from 'react';
import { render } from 'react-dom';
import { StripeProvider } from 'react-stripe-elements';
import Main from './Main.jsx'
const App = () => {
return (
<StripeProvider apiKey="<YOUR_APP_KEY>">
<Main />
</StripeProvider>
);
};
render(<App />, document.getElementById('root'));
Elements で支払いフォームをラップする
次にElementsで支払いフォームをラップします。Elementsは、Stripe の入力フォームをグルーピングするために利用するコンポーネントのようです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// myCheckout.jsx
import React from 'react';
import { Elements } from 'react-stripe-elements';
import CheckoutForm from './CheckoutForm.jsx'
class MyCheckout extends React.Component {
render() {
return (
<Elements>
<CheckoutForm />
</Elements>
);
}
}
export default MyCheckout;
injectStripe で支払いフォームをラップする
さらにinjectStripeで支払いフォームをラップします。
injectStripeは、Stripe の入力フォームの様々な入力イベントを処理するために利用するHigher-Order Component(HOC)です。
公式ドキュメントでは、injectStripeはElementsと一緒に使えないことになっているので、何も考えずElementsとinjectStripeを使うコンポーネントを 2 つに分けた方がいいです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// checkoutForm.jsx
import React from 'react';
import { injectStripe } from 'react-stripe-elements';
class CheckoutForm extends React.Component {
render() {
return (
<form>
ここに入力フォームが入る
</form>
);
}
}
export default injectStripe(CheckoutForm);
カード情報入力フォームを配置する
最後にカード情報入力用のコンポーネントを配置していきます。
Stripe の React コンポーネントには、All-on-one 型のCardElementと、これらを別々に分割したCardNumberElement, CardExpiryElement, CardCVCElementがあります。
自分の場合は、レイアウトを自由にしたかったので、別々に分割されたコンポーネントを利用しました。
かなりラフですが。。。入力部品を配置したらこのような感じになると思います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// checkoutForm.jsx
import React from 'react';
import { injectStripe } from 'react-stripe-elements';
class CheckoutForm extends React.Component {
render() {
return (
<form>
<CardNumberElement />
<CardExpiryElement />
<CardCVCElement />
<button>支払い</button>
</form>
);
}
}
export default injectStripe(CheckoutForm);
これで導入については終わりです。次からは細かなイベントハンドリング方法やカスタマイズについて紹介します。
入力フォームのイベントハンドリング
入力フォームのイベントハンドリング方法について紹介します。
フォームの入力チェック
フォームの入力チェックは Stripe の入力コンポーネントのonChangeにハンドラを設定して処理します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// checkoutForm.jsx
...
class CheckoutForm extends React.Component {
+ constructor(props) {
+ super(props);
+ this.handleChange = this.handleChange.bind(this);
+ }
render() {
return (
<form>
- <CardNumberElement />
+ <CardNumberElement onChange={this.handleChange}/>
<CardExpiryElement />
<CardCVCElement />
<button>支払い</button>
</form>
);
}
+ handleChange(data) {
+ if (!data.complete) {
+ // ERROR
+ console.log(data.error.message);
+ }
+ }
}
onChange ハンドラには次のような Stripe のデータオブジェクトが渡されてくるので、これを元にエラーかどうか判断します。
1
2
3
4
5
6
7
8
9
10
11
{
brand: "amex",
complete: false,
empty: false,
error: {
code: "invalid_number",
message: "カード番号が無効です。",
type: "validation_error"
},
value: undefined
}
エラーかどうかはcompleteを、メッセージはerror.massageを見ればいいと思います。
注意点としては、このオブジェクトは入力フィールド単体で送られてくるので、フォーム全体を管理するためには別途 State などを使って状態を管理する必要があるということです。 ちょっと泥臭い実装ですが、自分はそれぞれの入力フォームのハンドラを別に定義して処理しました。
フォームの送信処理
フォームの送信処理は、フォームのonSubmitにハンドラを設定して処理します。
現在のところフォーム送信処理は
PaymentRequestButtonElementを使った方がエレガントかもしれません。
Stripe への支払い処理にはstripe.createTokenを呼び出して、カード情報と支払い情報を Stripe 上に登録する必要があります。処理が完了すると、Stripe はトークンを発行するのでこれをバックエンドのデータベースなどに保存しておきます。
stripe.createTokenはinjectStripeで設定されるオブジェクトです。injectStripeでラップされたコンポーネントはpropsからこのオブジェクトを利用することが可能です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// checkoutForm.jsx
...
class CheckoutForm extends React.Component {
constructor(props) {
super(props);
+ this.handleSubmit = this.handleSubmit.bind(this);
this.handleChange = this.handleChange.bind(this);
}
render() {
return (
- <form>
+ <form onSubmit={this.handleSubmit}>
<CardNumberElement onChange={this.handleChange}/>
<CardExpiryElement />
<CardCVCElement />
<button>支払い</button>
</form>
);
}
handleChange(data) {
if (!data.complete) {
// ERROR
console.log(data.error.message);
}
}
+ handleSubmit(e) {
+ // ここにバリデーション処理
+
+ this.props.stripe.createToken().then(result => {
+ if (result.error) {
+ // ERROR
+ console.log(result.error.message);
+ return;
+ }
+ // ここで取得したトークンをバックエンドに送信して完了!!
+ console.log(result.token);
+ });
+ }
}
スタイルのカスタマイズ方法
Stripe のコンポーネントはスタイルをカスタマイズすることが可能です。これを行うことで、サービスに溶け込ませるように Stripe を組み込むことができます。
自分の場合は、バリデーションエラーのスタイルをカスタマイズする必要がありました。なので、この手順はほぼ必須かと思います。 最初やり方がわからなかったので Stack Overflow で聞いてみました。
実際に render された後のコードを見るとわかるのですが、Stripe のコンポーネントは.StripeElementというスタイルを持つ DOM にラップされています。
.StripeElementは CSS 命名規則の BEM に則ったいくつかの状態(modifier)を持つので、これらのスタイルを変更することでカスタマイズか可能です。
1
2
3
4
5
6
7
8
9
10
11
.StripeElement {
border: 1px solid #eee;
}
.StripeElement--invalid {
border: 1px solid red;
}
.StripeElement--focus {
border: 1px solid blue;
}
こんな感じでスタイルをカスタマイズできます。
現在は、
Elementsのstyleに CSS スタイルをセットするとスタイルが適用されるようです。便利。
まとめ
自分が Stripe を導入した時の手順でした。 2017 年 7 月にやったのですが、既にいくつかやり方が古くなってるものがありますね。
アップデートが早いようなので、実際に組み込む前に公式のドキュメントを確認した方が良さそうです。
おまけ
導入当時は JCB に対応してなかったので、Stripe が対応してくれた時はチーム全員で大喜びしました。懐かしい記憶です。



