Mackerel チームアプリケーションエンジニアの id:astj です。
先日(といっても2ヶ月経ってしまった……)、 Increments さんと Stripe さんの共催による "Qiita x Stripe Meetup" というイベントがあったのですが、そちらで Mackerel での Stripe の利用に関して発表する機会をいただきました。
当日の発表資料はこちらです。
イベントレポート内でも要点を掲載していただいていますが、折角なので発表内容を追補した上で以下に記述しておきます。セルフ書き起こしみたいな趣きです。資料中、あるいは以降の文中でも言及していますが、 SaaS であることというのは継続的に新しい価値をデリバリーしてアップデートし続けることであり、また SaaS を利用するというのはそのデリバリーされる新しい価値を活用していくことが肝要だと(個人的には)認識しています。 Mackerel という SaaS を提供する側の人間として新しい価値をこれからもデリバリーしていきたいですし、 Stripe という SaaS を利用する立場として今後のますますのパワーアップに期待すると共に、そのパワーアップを享受できるようにアップデートしていきたいなと考えています。しつこいようですが個人の所感です。
発表資料は2019年4月時点での内容であり、本エントリの内容は2019年6月時点の内容です。資料ないしエントリを読まれる際は、あくまでそれぞれの当時の情報であるということをご注意いただければと思います。
以下発表内容
株式会社はてなでアプリケーションエンジニアをやっています。ここ3年くらい Mackerel というサービスの開発をしています。 Mackerel では先般クレジットカード決済に利用するサービスを Stripe に移行したのですが、その移行関連の開発を担当したというご縁で今回発表機会をいただきました。
Mackerel
Mackerel はサーバー監視の SaaS です。
監視対象のサーバーに mackerel-agent と呼ばれるデーモンをインストールすると、 agent がサーバーのリソース利用状況などを Mackerel 側に送信し、サーバーとそのリソースの監視/管理を行う SaaS です。(本当はもっと色々な機能がありますが、本論に関係ないのでほどほどに。この辺にいろいろあります! 特長と機能 - 特長 - Mackerel(マカレル): 新世代のサーバー管理・監視サービス )
2014 年のローンチ以来、アクティブな開発をずっと続けてきています。無料でも利用可能ですが制限があり、フル機能を利用するには有料プランを契約していただく必要があります。月単位で利用対象のサーバー台数の平均を根拠にした従量課金制を敷いており、監視対象のサーバー1台1ヶ月あたり1800円というのが基本的な料金体系です。有料プランの支払手段は基本的にクレジットカード払いとしており、そのクレジットカード払い部分に現在では Stripe を利用しています。
Mackerel からの Stripe の利用
実際の Stripe の利用スタイルについては、今のところ非常にシンプルです。
Mackerel の請求単位はオーガニゼーションとなっていますが、2019年4月時点ではそのオーガニゼーションごとに Stripe.js v2 を利用して Stripe 上の Customer を対応づけています*1。 Stripe には定期課金の仕組み ( Stripe Billing ) がありますが、 Mackerel ではそれを利用せず、月1回の決済日にオーガニゼーションごとのサーバー利用台数実績を集計し、Stripe Payments で都度請求を行うことで実現しています。
Stripe 移行当初頃までは Mackerel で実際に決済が走るタイミングは2種類存在しました。サービス初期からご利用いただいていた一部のユーザーさんの場合、有料プランの利用を開始した日に初回の決済を行い、以後の決済も毎月同じ日に行っていました。例えば2月15日に有料プランの利用を開始したユーザーさんの決済は毎月15日に行われていました。一方、ほとんどのユーザーさんの場合は、毎月1日に一括して決済を行っています。こちらの場合、例えば4月20日に有料プランの利用を開始した場合、初回の決済は5/1に10日分の日割り利用料金分を決済し、以降は毎月1日に前月1ヶ月分の決済を行っています。
もともと前者は少数しか残っていなかったのですが、 Stripe への移行完了後しばらくしてから前者の契約形態を全て後者に移したため、現在においては全てのユーザーさんの決済が月初に行われるようになっています。
決済サービスの変遷
Mackerel でのクレジットカード決済は Stripe 経由であると述べましたが、実は Stripe は Mackerel にとって3社目の決済サービスとなります。サービス開始から5年足らずで2度のサービス移行を行ってきたのですが、その経緯について振り返ります。
2014年のサービスローンチ時、クレジットカード決済には Stripe も検討に上がりましたが、この時点では別のサービスを選定しました。2014年の時点でも Stripe はクレジットカード決済の有力な選択肢であったことから、特に USD 経由での決済手段の候補として検討していましたが、けっきょくローンチ時点では JPY 決済のみとなったこともあり、他社を選定することになりました。
しかしながら、当時選定した決済サービスは2016年にサービス終了がアナウンスされ、我々は別の決済サービスへの移行を余儀なくされることになりました。ちょうどこのタイミングと前後して Stripe が日本進出して JPY 決済ができるようになっており再び選択肢に上がったのですが、当時は JCB カードが利用できず、 JCB での決済を希望するユーザー数が無視できないボリュームだったため、ここでも採用には至りませんでした。とはいえ将来的には Stripe を利用したいという思惑もありつつ、決済 API やデータモデリングの近しい他社への移行を選択することになりました。
その後、2018年に Stripe 側で JCB カードが利用可能になったことが発表され、2016年当時の採用ブロッカーはなくなります。
japan.cnet.com
とはいえ当時の利用サービスにおおきな機能的不満もなかったので利用を継続していたのですが、将来的な USD 決済への備えなど事業的な判断により、同じく2018年の秋頃に Stripe への移行を決断することになりました。その後機能開発やデータ移行などを経て、2019年2月の決済より実際に Stripe を利用しています。
決済サービスの移行
ここからは実際に決済サービスを移行する話をします。
決済サービスの移行において技術的に必要な要素は、おおまかにわけると以下の4つではないでしょうか。
- 支払情報登録や決済など、ユーザー向けロジックの実装
- ユーザーの支払情報などのデータの移行
- 実際の決済に用いるサービスの切り換え
- 売上や入金などの集計処理の移行
特に上3つは自明でしょう。一番最後は直接のユーザー向けの機能ではないので油断すると忘れそうになりますが、忘れてはならない重要な要素です。
データの移行を中心とした上3つに関しては、 Stripe オフィシャルのドキュメントがあるのでそれを読み込むのがよいでしょう。
特に Stripe 「へ」の移行であれば後者のドキュメントが対応します。(意外とこのドキュメントにおいても集計処理の話題は出てこないですね。まあ広義では「 Stripe 組み込み」が該当するのでしょうが。。。)
エンドユーザー向けの機能移行
上記の Stripe のドキュメントで「Stripe 組み込みを構築する」として述べられている部分です。一般論としては Stripe のドキュメントを参照して実装しましょう、ということになるでしょう。
順番が前後しますが、 Stripe での登録/決済機能を実装するにあたっては、どのようにデータを移行/保持するかの方針を先に定めておく必要があります。 Mackerel の場合は、異なる決済サービス上の実装を共存させてオーガニゼーションごとに異なる決済サービスで決済できるような実装が既に存在していました。こうすることで、データの移行に先立って実装の移行をリリースすることができるため、内部実装のリリーススケジュールからデータ移行のスケジュールへの依存を下げることができます。(勿論、データ移行より先に内部実装をリリースしなければならないので完全に依存がなくなるわけではないですね。)
このメリットのため、実際に異なる決済サービスでの決済を並行稼働させる計画がない場合でも内部的には異なるサービスを扱える実装を持たせる方が扱いやすかろうと考えています。
移行の実装そのものに関して言えば、 Mackerel で移行前に利用していたサービスと Stripe でモデリングや API の形式は近く、かつ移行前後ともにサービス側の定期課金システムに依存しておらずシンプルに利用している/いたため、 Stripe の組み込みに関して大きく困る面はありませんでした。
顧客データの移行
上記の Stripe のドキュメントでは「Stripe へのデータ移行を現在の決済処理サービスに依頼する」「組み込みを更新して移行を完了する」として述べられている部分です。クレジットカードの生の番号などの情報は我々のデータベースには保持しておらず(ですよね?)決済サービスが持っている訳ですから、この情報を移行元の事業者から移行先の事業者へ移行する必要があります。
Stripe のドキュメントの見出し名にもあるように、ここは自分たちと Stripe だけでは実現できず、移行元と移行先それぞれの事業者、そして我々の三者でスケジュールを調整する必要があり、エンジニアリングの話題からは少し外れますがやや気を遣うところではありました。
移行を行うと、ある時点の移行元での顧客データスナップショットが移行先 (Stripe) に移されます。当然ながら、移行実施後にさらに移行元に対して顧客情報の追加や編集が行われてしまった場合は移行先には反映されないため、移行開始までには移行元に対する更新リクエストが飛ばないようにしておく、つまり顧客情報の追加・変更・削除を停止する必要があります。ビジネス的にこれが受忍できるのであれば単純な停止で問題ないですが、特に新規顧客のコンバージョンを妨げることになるので、前述のように複数の決済サービスを並行稼働させる実装をした上で、後述するように決済プラットフォームを徐々に入れ替えていく仕組みが必要になるかもしれません。
勿論データの移行後は我々のデータベース上で(我々の顧客 id)=>(決済サービスの種別, 決済サービス上の顧客 id)
のマッピングを、(移行元, 移行元での顧客 id)
から (移行先, 移行先での顧客 id)
に更新する必要があります。この対応データについては、 Stripe のドキュメントで説明されている構造の JSON 形式のファイルが Stripe 側から提供されるので、それを元に自分たちのデータベースを更新していく必要があります。このデータの更新にも当然テスト環境での事前確認を行いたくなるところですが、必ずしもテスト環境で本番同様のデータ移行そのものを発生させられるとは限りません。 Mackerel の場合は、テスト環境は手動でデータを再登録した上で、同構造の JSON を手作りして動作確認をしていました。
移行のタイムライン
以上の流れで顧客データが Stripe に移行でき、また利用者(我々、 Mackerel )側のデータベースからも Stripe の顧客データを参照した状態になるのでデータの移行は完成するのですが、実践的な話題でいうと、前述したようにデータ移行の前後では顧客情報の操作をロックしないと整合性が失われてしまう可能性があります。この整合性を保ちつつ、かつ新規の顧客情報を登録可能にして有料契約のコンバージョンを妨げないようにするため、 Mackerel では時系列順に以下のような手順を取りました。
当然、初期状態では全ての顧客情報が移行元にあり、操作は全て移行元のサービスに対して行われます。顧客情報が移行元にしかないので、全ての決済リクエストも移行元に対して行われることになります。
- 全顧客: 移行元
- 変更・削除可
- 決済: 移行元
ここから、まず新規顧客登録のみを移行先 (Stripe) で行うように変更します。この新規に Stripe に登録した顧客の決済は Stripe で行われるようになります。既存顧客のカード情報の更新などは引き続き移行元サービスに対して行い、決済も移行元で行います。すなわち、この状態では前述したような並行稼働の状態になります。
- 新規顧客: Stripe
- 変更・削除可
- 決済: Stripe
- 既存顧客: 移行元
- 変更・削除可
- 決済: 移行元
そして実際のデータ移行に突入します。この期間中は、前述のようにデータの不整合を防ぐため移行元顧客に対する変更、削除を停止することになります。データ整合の観点だけで言うならばこの時点で Stripe で登録しているユーザーさんの顧客情報の変更、削除は行っても構わないのですが、中間状態の仕様を簡便にしまたユーザーさんに対する説明もわかりやすくするために、実際には Stripe 顧客に対する変更、削除も移行元と同様に一時停止しました。
(発表の内容ではこの時点での決済も停止していた、というようにお話ししたかもしれないのですが……) Mackerel での移行においては、この間は決済処理を明に停止することは行いませんでした。ソースコード上はこの停止期間中に決済が多数走る可能性がありましたが、月中のこのタイミングで実際に決済が走る可能性があるのはごく僅かな数であったことから停止せず、実際にごく数件の決済はこの期間中に行われました。
- 新規顧客: Stripe
- 変更・削除不可
- 決済: (Stripe)
- 既存顧客: 移行元
- 変更・削除不可
- 決済: (移行元)
サービス間のデータ移行が完了してから Mackerel 側のデータベースを更新し終わると、 移行元で登録していた顧客情報は Stripe のものを参照するように変わります。
- 全顧客: Stripe
- 変更・削除不可
- 決済: (Stripe)
ここまで移行が完了すれば、変更・削除を再開することで移行が完了します。
- 全顧客: Stripe
- 変更・削除可
- 決済: Stripe
実際のメンテナンスの告知はこちらです。決済サービス間のデータ移行と、その後 Mackerel 側のデータ更新が終了するまでの期間ということで10日の間前述の更新/決済停止状態としました。
mackerel.io
時系列に応じて表を作るとこういう感じでしょうか。
新規顧客 | 既存顧客 | 顧客情報操作 | 決済 | |
---|---|---|---|---|
初期状態 | 移行元 | 移行元 | OK | 移行元 |
移行準備 | Stripe | 移行元 | OK | 両方 |
移行中 | Stripe | 移行中 | 停止 | (両方) |
移行完了 | Stripe | Stripe | OK | Stripe |
今回の移行手段ではデータ移行の最中は決済も停止しない判断を取りました。これは、 Mackerel のほとんどのユーザーさんの決済は月初に集中しており、成否などを目視で十分確認できる数の決済しかこの期間中に行われないことがわかっていたためです。仮にこの期間中の決済数の見込みがもっと多かった場合、決済を一時停止するなどの方法は考えられたかもしれません。
顧客情報の移行を行って
実際の顧客情報の移行そのものは非常にスムーズに行われました。所要期間として提示された期間に十分余裕を持って決済サービス間での顧客データの移行は完了しましたし、マッピングデータの反映でも特に困るところはありませんでした。
また、当然ながら移行した顧客情報を用いた決済も無事行われました。とはいえ、実際は「移行前は決済できたのに移行後は決済に失敗した」というケースがまったく存在しなかった訳ではありません。決済失敗には様々な要件が考えられ一概に決済サービスの変更が原因とは断定できないのですが、明らかな期限切れを除くなんらかの決済トラブルが発生したのは1%程度でした。これは移行前と比べて大差ない水準に収まっています。
売上や入金などの集計処理の移行
前項を無事終えることができればエンドユーザー向け機能は移行完了です。移行先の決済サービス、つまり Stripe だけを用いて支払い情報の登録や実際の決済ができるようになりました。おめでとうございます。
しかしほとんどの場合、ユーザーさんが移行先のサービスを用いて決済できるだけでは不十分でしょう。事業であれば売上を集計する作業が必要になるでしょうし、入金があったときに経理に対して入金の内訳を説明可能にしておく必要があるでしょう。そういった数字の集計処理を Stripe でどのように行うか考える必要があります。
ここで留意しなければならないのは、移行前と移行後でそのまま1:1対応で同じような数字が同じタイミングで得られるとは限らないということです。これは勿論前述のユーザーさん向けの機能開発でも同様(我々のケースではほぼ同等に移行できたが、一般にその保証はない)でしたが、これらの集計処理は開発チームだけでなく、売上を分析する部門、金銭管理をする経理部門などチーム外の関係者が膨らみます。そのため、関係者と連係して必要な集計手段を確立しなおしていく必要があるでしょう。
我々のケースでいうと、決済が行われて売上が発生してから我々の銀行口座に入金されるまでのサイクルが移行元と Stripe で異なることや、 Stripe では入金のスケジュールに関して自由度が高いことから慎重に設計を行うことになりました。 特に、 Stripe ではカードの種類によって決済から入金可能になるまでのタイミングが異なります*2が、 我々の移行元(や恐らく他の多くの決済サービス)ではこの期間はカードの種別に依らず一定です。単純に移行前の集計手段をなぞるだけでは、各部門が必要とする数字が導出できないことが分かっていました。
そのため、月次売上の集計と同じく月次(しかし別日)の売上入金のそれぞれに関して、数字を必要とする各部門と協議しながらそれぞれのレポートを再設計することになりました。再設計は骨が折れますが、同一のサービスを使い続けているとなかなか見なおす機会のないこれらのレポーティングを整理する好機でもありました。
集計・レポートの決定
レポート内容を決定するためには、レポートの利用側が「毎月のどの時点で」「どの数字が必要なのか」「付帯して必要な情報は何か」を知ることと、レポートの提出側(つまりレポートを開発する側)が「毎月のどの時点で」「どの数字が取得可能なのか」や「どのようなデータと紐付け可能か」を知る必要があります。
お互いに手札を揃えて突き合わせて要件を満たす決済/入金スケジュールを決定した結果、我々の場合は「月初に売上を締めたタイミングで売上の合計/内訳が知りたい」「売上金が入金されるタイミングでその入金が属する決済の内訳が知りたい」の2点に集約されました。
集計レポートをつくる
必要な数字がわかれば、後は再び開発側の課題にもどります。 Stripe は管理画面からも様々な条件でデータを抽出できることから、要件次第では開発を行わずに管理画面上の操作で完結できる可能性もあります。特に、決済時点で顧客情報をメタデータに付与しておくことで、管理画面上の検索条件や集計結果に含められることには注目しておくとよいでしょう。
一方で、管理画面だけで要件が満たせない場合も当然考えられます。管理画面からそもそも出力できないデータが必要であったりデータの後処理を行う必要がある場合などは、レポート用の機能を開発する必要があるでしょう。実際、 Mackerel ではこの部分は機能開発することになりました。
今回 Mackerel の集計レポートは、 Stripe 側のデータをマスタデータとして API で取得することを基本として、データを検索する条件を wrap したりデータを整形して提出先の部門で利用しやすくする部分を中心に開発しました。「顧客ごとのホスト台数」「顧客(オーガニゼーション)の名前」などのレポートに必要な情報は決済時点でメタデータに含めています。
余談: Stripe Sigma
ちなみに、 Stripe には SQL 形式で Stripe データを検索できる Stripe Sigma という機能もあります。
非常に強力な機能であるため、求めているレポート手段がこの Sigma だけで完結できるケースも考えられますが、我々の場合は Sigma は採用しませんでした。冒頭で Mackerel の有料プランは「基本的に」クレジットカード払いと触れましたが、一部では請求書払いも提供しており、かつその金額的ボリュームはけっこう大きなものです。そのため、全体的な売上の集計や分析は Stripe の売上データと請求書の売上データを串刺して行いたく、 Stripe 経由の決済に絞った分析機能である Sigma の(移行初期時点での)利用は見送ることになりました。Stripe 経由での決済が全て、あるいは大半ということであれば Sigma による集計・分析が活用できることも多いだろうとは想像しています。
移行しました
ここまで、 Mackerel での移行の実例を中心に説明してきました。 Mackerel での移行は比較的スムーズに実現できたと考えていますが、これは過去に別のサービスからの移行実績があったことに加え、決済サービスの利用の仕方としてはプリミティブな利用だったこと、(特にエンドユーザ向けで)機能の互換性が高かったことに助けられたと感じています。
冒頭で触れたように Stripe には定期課金の仕組み(Billing)があるにも関わらずプリミティブに移行した訳ですが、これはとにかく決済サービス移行自体を滑らかに完遂させることを優先した判断です。決済サービスの移行自体は小さくないプロジェクトでしたが、このプロジェクトを終えた結果辿り着いたのはゴールではなく、「Stripe を決済に利用する」というスタートラインであるという認識でいます。 Stripe Billing や Stripe Elements といったよりリッチなソリューションを導入することで、ユーザーさんと我々双方にとって有益な機能を活用して、 Stripe 導入の恩恵をより一層受けていけたら、という思惑のもとに移行を済ませました。
また、2019年4月の発表時点で支払い情報入力画面の Stripe Elements への入れ替えを進めており、5月末に実際に入れ替えをリリースしています。
Mackerel は SaaS である
冒頭で Mackerel の紹介のところで触れましたが、 Mackerel は継続的にサービス全体を改善してきた SaaS です。SaaS を提供する側としては今後も継続的に改善・デリバリーを進めていきますし、その中で決済機能も”聖域”にせずに継続的に改善を施していくべきだと考えています。
Stripe は SaaS である
そして、 Stripe もまた SaaS で、現時点でも豊富な機能がありますが、それに止まらず継続的に改善され続けています。SaaS を利用する側として、その継続的な改善の恩恵を今後も受けていきたいですし、ますますのパワーアップを楽しみにしています!
*1:5月に Stripe.js v2 から v3 に移行して Stripe Elements を利用するようになりました
*2:JCB 経由で決済されるブランドの場合、入金可能になるまで時間がかかる