決済システムを開発するときエンジニアは何に気をつけるべきか
皆様、いかがお過ごしであろうか? 今年も 3/4 が終了したが、まだ平成のタスクが山のように残っている野地である。俺の平成はまだ終わらない。
今回はバックエンドエンジニアなら恐らく避けて通れない仕事であろう「決済」を実装する時の話をしようかと思う。
自分は今までライブチャットやクラウドファンディング、 EC 等など、バック / フロントエンドエンジニアとして様々な開発を経験させて頂いてきたが、寿命が縮むような思い出はいつだって決済周りのバグだった。
反面、自分だけでは恐ろしすぎて絶対に自習しないような分野の実務経験を得られたのは受託開発ならではのメリットだったと思う。
この記事が、読者の皆さんが「決済」というシステム開発に初挑戦する時の助けになれば幸いである。
- ドキュメントを確保する
- 開発に必要かつ、入手に時間がかかるモノをなるべく早く確保する
- 何の情報を確保し、何の情報を確保してはいけないのか明確にしておく
- テスト環境と本番環境でどんな設定が変わるかをリストアップする
- フロントエンドに出力してはならない情報が露出していないか注意する
- XSS, CSRF 等の対策が万全かチェックする
- トランザクションを確実に行う
- 決済確定をクリックさせたら直ぐにページを離脱しても決済が完了するようにする
- 二重決済を防止する
- 正常系のみならず、異常系の場合の処理・表示を徹底する
- 本番環境でテストが可能かクライアントと事前に話し合い、可能であれば改修後毎回実行する
- 万が一のエラー時のため、ログを詳細に残す
- DB のバックアップを定期的に取得する
- まとめ
ドキュメントを確保する
当たり前、と言われそうだが、いざ開発を始めようとすると案外手持ちのドキュメントだけでは情報が足りなかったりする。
銀行振り込みや代引き以外のクレジットカード決済やコンビニ決済等は通常、決済は決済代行会社と契約し、その会社が提供している API にアクセスすることになるだろう。
大抵、これら決済代行会社は豊富なドキュメントを提供してくれるが、実は開発資料たと思っていたドキュメントが営業やクライアント向けの資料だったり、フローチャートを分かりやすくまとめたモノだったりする。
会社によってはドキュメントを Web で公開していなかったりするので、開発に必要な資料、特に API のパラメータや送信形式が詳細に説明されているドキュメントをいの一番に確保しておこう。
何より頼もしいのは現実に動いているコードだが、自社に前例がなければ公式が出しているサンプルコード、次点で他人が Qiita やブログで公開しているコードも参考程度にブックマークしておくと良い。
開発に必要かつ、入手に時間がかかるモノをなるべく早く確保する
先述のドキュメントも必須と言えば必須だが、忘れてはならないのがシステムが動くサーバーとドメイン、SSL 環境だ。
例えテストであっても API 自体が https でないと動かない例は多いので早急にサーバー・ドメイン・SSL 環境は用意した方が吉である。
勿論本番・テスト別々に用意しなければならない。
何の情報を確保し、何の情報を確保してはいけないのか明確にしておく
自分も初心者の時は取得できる情報は全てデータベースに取り溜めるもの、と考えていたが、特にクレジットカード番号などは絶対にサーバーへ保存してはならない。
なぜなら万が一データベース情報が外部に漏れたとき、全責任が運営会社(契約にもよるが多くの場合は開発を担当したあなたの会社も含まれる)にふりかかってしまうからだ。
保存してはいけない情報についてはデータベースへ保存しないだけでなく、そもそも運営会社の持っている(=開発しているバックエンドシステムが動く)サーバーに送信しないのが理想的だ。
これに関しては JavaScript を用いて決済代行会社に直接クレジットカード番号などの情報を送信し、返ってきたトークンを運営会社のサーバーに送る、通称「トークン型」決済、または決済代行会社の入力画面にリダイレクトして情報を入力してもらう通称「リンク型」決済にて実現できる。
反面、決済代行会社が返してくる一意な決済ID(会社によってトランザクションID 等、呼び方が変わる)は必ず保持しなくてはならない。
これが無いと後でキャンセル等特別な対応が必要になったときに決済代行会社が対象の決済を特定できず、最悪操作に対応してくれないからだ。
例えば Amanzon Pay では決済方法にもよるが、決済 ID だけでなく実に 6 項目もの情報保存を推奨している。
Amazon Developer FAQ データベース等に保管が必要な情報は何ですか?
これら情報の区分けを開発前にしっかり確認しておこう。
テスト環境と本番環境でどんな設定が変わるかをリストアップする
バックエンドの恐ろしいところはテスト環境で完璧に動作したから丸々コードをサーバーにアップすれば本番でも問題なく動作するわけではないところだ(もちろんフロントエンドはフロントエンドで、違うブラウザで挙動が異なる可能性があるという問題をはらんでいる)。
具体的に言えばシークレットキー(パスワード)や API の URL、決済完了時に送信されるメールアドレスの宛先などが違うため、本番にそのままアップロードしていいファイルとそうでないファイルは早々に区別し、自前のドキュメントに纏めておくべきである。
更にサーバーの設定、PHP で言えば php.ini の設定等がテストと本番で違う場合はさらなる注意が必要である。
使えるなら Docker 等で完璧に同じ環境を構築、または本番サーバー内にテスト環境を構築できるのが望ましいが、やむを得ない場合は本番でもテストが必須であることを肝に銘じて差異を予め調査しておくべきだろう。
フロントエンドに出力してはならない情報が露出していないか注意する
受注のための情報は html の form、もしくは JavaScript の Ajax で送信することになるが、だからといって input type=”hidden” 等にシークレットキー等を仕込んではならない。
またあまりないケースではあるが、セッションの代わりに重要な情報をクッキーや local storage 、URL のクエリパラメータに仕込んだりするのも危険である。
フロントエンドには最低限の情報と、外部に漏れても問題ないトークン情報のみを出力するようにしよう。
XSS, CSRF 等の対策が万全かチェックする
どちらも Web システムを作成するときは定番中の定番項目だが、気をつけないとユーザーの情報が抜き取られたり、意図しない購入が発生したりする。
一番の対策としてはメジャーなフレームワークを正しく使用することだが、フームワークを使用しない場合は自力で対策を施す必要がある。
XSS と CSRF がどんなものか分からないという場合は以下の記事で対策を勉強しておこう。
トランザクションを確実に行う
ユーザーから見れば「普通のページ遷移よりちょっと時間がかかるかな?」程度の決済処理だが、作成してみると分かる通りサーバーサイドでは凄まじく複雑なデータ処理が行われている。
もちろん、失敗し画面にエラーが表示されてしまうのは避けるべきだが、最も恐れるべき事態は「データ A は変更されているのにデータ B の変更に失敗してデータ A のみが更新された状態」になることだ。
この場合はデータ A の更新を無かったことにしないとデータに矛盾が生じてしまう。
具体的に例を出せば、決済処理は済んでいるのに商品を購入したことになっておらず、金額だけ請求しているという状態が生じてしまうというわけだ。
処理順が一番最初の更新処理が失敗しただけなら以降の処理をしないだけで済むが、それ以外の場合はトランザクション処理を行う必要がある。
内部のデータベースに一般的な リレーショナルデータベース(MySQL や PostgreSQL)を採用しているならトランザクションがサポートされているのできっちり対策しておこう。
決済確定をクリックさせたら直ぐにページを離脱しても決済が完了するようにする
外部サイトへページ遷移するタイプの決済方法を採用する場合、外部サイトでの処理終了を自サイト側が確実に検知できるかが開発のネックとなる。
例えばクレジットカード決済の場合、外部サイトで入力された情報が問題なく受け入れられ、請求が確定した瞬間に受注データも確定されたいところだ。
しかし当然それは自サイト外のサーバーで起こっている処理なので、普通に考えれば外部サイトから購入成功ページへリダイレクトしてもらう必要がある。
ただし、リダイレクトに頼るとその瞬間にブラウザの接続を中断されると受注が完了できなくなってしまうという問題が発生する。
使用している API によっては完全に回避できない場合はあるものの、リンク型決済を提供してるサービスの多くは結果通知と呼ばれる、外部サーバーで決済が完了した場合にこちらの時サーバーに決済完了通知を送るサ仕組みを提供していることが多いので調べてみよう。
二重決済を防止する
フロントエンド開発でも良くテスト項目に上がるが、クリック連打対策やは決済システムを開発しているときも重要だ。
バリデーションと同じく、ユーザー体験向上のためフロントエンドでの対策もとって構わないが、サーバーサイドでの対策は必須である。
できることならサーバーサイドのセッションで事前に一意のトークンなどを生成しておき、二回目以降の受注を回避する等の対策は取っておきたい。
正常系のみならず、異常系の場合の処理・表示を徹底する
納期に追われている時などは正常に購入フローが回せるようになった段階で安心しがちだが、異常系の処理も忘れてはならない。
データ不整合は全力で回避し、予想されるバリデーションエラーは全て弾いたとしても、サーバーが一時的に停止したり、ユーザーの回線が止まったりと、開発者ではどうしようもないエラーの種はいくらでも存在する。
重要なのはデータ不整合にの回避だけではない。それらエラーが発生したときにユーザーへ適切な行動を促すのも忘れてはならない必須対策である。
それが再度時間をおいてリトライすべき操作なのか、入力値を変えれば解決するのか、もしくは運営に問い合わせるべきなのかは事前に決めておくべきだろう。
もしかするとエンジニアリングだけではなく、デザインや運用にまで関わってくるかもしれない。
本番環境でテストが可能かクライアントと事前に話し合い、可能であれば改修後毎回実行する
制作の方針次第だが、本番環境に改修をデプロイした後、可能であれば本番環境で決済テストをしたほうがいいだろう。
いくら同一の環境を用意したつもりでも、ヒューマンエラーは発生しうるし、片方のサーバーだけ不調ではないという保証はないため、本当のユーザーがエラーに遭遇する前に安全を確認するべきである。
ただし、ほとんどの場合はクライアントによる受注キャンセル処理が必要になるため、事前にクライアントへ話を通しておくのも忘れてはならない。
万が一のエラー時のため、ログを詳細に残す
いくら気をつけていてもバグは起きる。どんなに大手の企業、どんなに優秀なエンジニアであってもバグは本当にしょっちゅう起こしている。
バグを減らすのは重要だが、それと同じくらい重要なのが、バグが起こったとき、なぜそれが起こったのか確認する手がかりを残しておくことである。
予想できるバグは事前に潰しているハズなので、いざ本番環境でユーザーがバグを発生させたのであればそれはこちらが考え付かなかった処理が起こっていたということで、原因は追及し辛いものとなる。
そんな時に心強いのが、ユーザーがどんな行動をして、サーバーはどのような処理を行ったかという記録であるログデータである。
Web サーバミドルウェアである Apahce や Nginx にもログはあるが、より詳細なログをためておけば解析がぐっと楽かつ現実的なものになるだろう。
特に、ログインしているユーザーが誰なのか、なんの商品をいくつ買おうとしていたのかはアプリケーションでないと知りえない情報なので、受注用のデータベーステーブルのみならず、ログデータとしても記録をのこしておくことを推奨する。
DB のバックアップを定期的に取得する
Web システムの心臓はデータベースである。心臓を失ったサービスは死ぬ。
もちろんデータベースが壊れないように対策を講じるのは重要だが、SQL でテーブルを削除するのは実に簡単である。自分や新人のエンジニアが間違って削除することも考えられなくはない。
なので、定期的なバックアップは Web サービスの運営に必須と言っても過言ではない。
頻度はサーバーの都合やデータ量にもよるが、最悪の事態を回避するためにもデータベースのバックアップは必ず定期的に行っておこう。
まとめ
これ以外にも気をつける点は多々ある。が、それは作成するサービスによって変化する。
結局はそのサービスを開発しているあなたの想像力と実装クオリティ、そして経験がモノを言うだろう。
経験値が無いうちは苦しむことになるだろうが、結局殆どのサービスは収入が無ければ長続きしない。
「お金を払ってもらう」というシステムの需要はいつでもあるし、決済は広告等よりも直接的かつ基本的な収入経路だ。
エンジニアの中でもかなりの割合が手を出したがらない領域であるが、反面、企業にとってはサービスのゴールと言ってもいい処理なので価値あるスキルとなるだろう。
実装することになったら、まぁ頑張れ。超がんばれ。
コメントを付ける