Merpay Tech Fest 2022 は、事業との関わりから技術への興味を深め、プロダクトやサービスを支えるエンジニアリングを知ることができるお祭りで、2022年8月23日(火)からの3日間、開催しました。セッションでは、事業を支える組織・技術・課題などへの試行錯誤やアプローチを紹介していきました。
この記事は、「決済基盤の実践話 / Pragmatic Practices of Payment Foundation」の書き起こしです。
皆さん、こんにちは。Merpay TechFestにご参加いただきありがとうございます。本セッションでは、メルペイの中で作られてきた決済基盤のお話をご紹介します。
自己紹介
最初に、軽く自己紹介をさせてください。社内ではSlackネームである@foghostと呼ばれております。現在、メルペイでソフトウェアエンジニアをしております。2016年11月に入社し、メルペイ立ち上げ時期から決済基盤まわりの開発に携わってきました。現在は、決済基盤チームでTech Leadをやっております。
Agenda
アジェンダはこのようになっています。最初は決済基盤の全体の概要について軽く紹介して、その後いくつかドメインのお話をしていきます。
Vision of Payment Foundation
我々決済基盤チームが目指しているビジョンはスライドの通り、「最高な決済基盤をプロダクトチームに提供し、メルカリグループのミッション達成を実現する」です。
Core Domains of Payment Foundation
以上のビジョンを目指して、現在はメルカリ/メルペイの決済機能だけではなく、メルカリのフリマアプリやメルカリShopsの決済機能も私たちの決済基盤を経由して提供しています。図の中心に記載されているのは、決済基盤を構成するメインのドメインです。
一番手前が決済処理を担当しているサービス群で、その裏側にお客様のお財布・帳簿を管理するドメインや加盟店様向けの売上を精算するドメイン、外部の決済機構と接続するドメインがあります。
お財布・帳簿の管理
最初に、お財布・帳簿の管理のドメインについてお話しします。
決済とは?
まず決済についてですが、決済のイメージは、人によって異なると思います。また、実際の場面によって決済の定義も変わります。
我々は開発の立場として、決済を「様々な経済取引において、参加者達が持てるお財布を操作して、価値の移転もしくは交換を行うこと」と定義し、開発を行っています。
価値の管理 〜お財布・決済アカウント〜
この中で最初に必ず出てくるのは、決済の元になる価値を管理する、お客様が持っているお財布・決済アカウントの話です。
個人のお客様のお財布・決済アカウント
メルペイの場合、お客様には主に個人と加盟店様の2種類があり、お財布・経済モデルがそれぞれ違います。ここでは個人のお客様が持っているお財布の例をあげています。
図の上部に掲載されているのが、内部のお財布・決済アカウントです。資金移動口座や売上金口座、ポイントを管理している口座、またメルペイの場合、メルペイスマート払いという与信の決済機能も提供しているので、与信口座や債権口座もあります。また、外部の決済機能を利用する際、お客様が接続済みのクレジットカードや入出金するための銀行口座も、システム側で管理されています。
加盟店様のお財布・決済アカウント
加盟店様のお財布・決済アカウントはこのようになっています。内部のアカウントには、決済をするたびに売上が貯まる未精算売上口座があります。それから精算のプロセスを完了した後に、確定された売上が精算済み売上口座に入ります。売上が債権として精算される場合は、精算済み債権口座に入ります。
加盟店様の外部アカウントについては、出金用途で銀行口座に登録してもらい、システムの中で管理されています。
価値の変動 〜帳簿・台帳管理〜
次の段階では、アカウントを操作して実際に決済処理をします。処理によって価値は変動するので、正確にトラッキング・管理することも大切です。ここでは帳簿・台帳管理について先にお話しします。
まず帳簿は記帳の目的によるので、実際の形や設計は変わることがあります。
様々な帳簿
たとえば決済リクエストが決済システム経由で処理された後にお客様の残高に変動があった場合、システム帳簿と呼ばれる帳簿・台帳管理システムで残高の動きを記帳します。同じ決済処理のデータは会計システムに連携され、会計帳簿にも記帳されます。
また、一部の口座では法的要件を満たさなければなりません。たとえば資金移動口座だと、お客様向けの未達債務の集計を日々管理する必要があります。そのような法定帳簿の集計も日々行う必要があります。
システム帳簿の仕組み
今回のセッションでは、お客様の残高を動かしたときのシステム帳簿の仕組みを軽くお話しします。
基本帳簿において、お客様が持っている口座種別ごとにアカウントが用意されています。そのアカウントの中で、残高がどのくらいあるのかが管理されています。また、そのアカウントの残高の中身を管理しているのがBalanceItemです。BalanceItemでは、入金された残高を一つひとつのレコードとして管理しています。その分の残高が消費された場合、どの決済トランザクションでいくら消費されたかをBalanceItemLogによって随時トラッキングしています。
また、BalanceItemLogでの記録をもとに、BalanceItemの当時の状態がBalanceItemSnapshotで管理されます。BalanceItemSnapshotでは、ある時点のBalanceItemにおける残高の変動などを取得できるようになっています。
最近の取り組み
お財布・帳簿管理の仕組みについて、最近新しい取り組みがあります。それは、なるべく開発量を減らし、新しい口座種別が増えたときにすぐプロダクトチームに提供可能な状態を目指して作られた仕組みです。
Configurable Value Account
課題は、先ほどご紹介したように、内部決済アカウントの種類がたくさんあリます。
そして、アカウントの種別によって細かい動きが少し変わることがあります。例えば資金移動口座の場合、有効期限なしで消費もしくは滞留の上限があります。図の中の「自動作成しない」とは、お客様が申請しない限り作成されないことをいいます。また、無償ポイントの場合は有効期限がつく、もしくはポイントが付与されると自動作成する動きにしています。
このようにアカウントの種別によって動きが変わります。今までアプリケーションのシステム側の新規でロジックを実装または改修・テストした上で新しい口座種別を開発して提供しています。
この課題を解決するため、新しいシステムでは、開発のコストをできるだけ抑えるために、「有効期限なし」などの動きを属性として定義し、その属性がついた場合に特定の動きをするよう機能(Configurable Value Account)にアップグレードしています。
そうすることで今後新しい口座種別が増えた場合、既に存在している属性をもとに組み立ててConfigを定義すれば新しい口座種別を提供できます。
決済処理 価値の移転・交換
次は、お財布を動かす決済処理をご紹介します。
様々な決済処理
決済には必ず「参加者」がいて、図に記載されている「個人のお客様」や「加盟店様」、「メルペイ」自身も含まれます。
たとえばお客様が加盟店向けに支払いした場合、加盟店決済という決済処理が行われます。お客様が持っているお財布を使って、価値を加盟店様のお財布に移転できるのです。また、個人のお客様同士による個人間送金という機能もあります。これも決済処理の一種であり、個人間で残高を移転できます。
それ以外にも、個人のお客様・メルペイ間の入出金や債権返済、メルペイ・加盟店様間の売上精算・出金も決済処理として抽象化され、機能が作られています。
設計観点 決済処理のモデリング方法
次に、これらの決済処理を開発するために、最初にどう設計すべきかという話をします。
モデリングする際に考慮すべき要素は大きく4つあります。
まず、「参加者が誰と誰」というのがあります。続いて、決済における支払い側と受け取り側の決済アカウント種別です。またそれを一つの決済処理として作るとき、一つのアクションで実施するか、複数のステップに分けるかによって実際にサポートするアクションが決まります。アクションごとに実際のAPIを分けて設計・実装されていくイメージです。
エスクロ決済(Escrow Payment)
実例で説明します。これは実際にメルカリの取引で利用されている、エスクロ決済のモデルです。メルカリではCtoCの取引がされているため、個人のお客様がそれぞれBuyerとSellerになります。
Buyerは自分で使える決済アカウントを持っており、メルカリで商品を購入した時に支払いをします。CreateEscrowとは、支払いの処理になります。それが成功したら、メルカリは預かり金を加盟店売上口座に入金します。メルカリの取引が完了し、お互いに評価が終わったタイミングでSellerに売上を分配します。これもエスクロのアクションの一つになっています。CaptureEscrowが実行された後、売上がSellerのお財布に入ります。
加盟店決済(Charge Payment)
続いての例です。これはメルペイが提供している加盟店決済の決済処理です。表ではコード決済やID決済、ネット決済という形で決済機能を提供していますが、決済基盤においては、どちらも参加者は個人のお客様、支払先は加盟店様になっているため、共通機能として作られています。
スキームとしては、まず個人のお客様が利用可能な決済アカウントを使い、CreateChargeを通して仮処理をします。このときの決済の状態は仮売上であり、売上はまだ確定されていません。あくまでお客様が持っている残高や与信を押さえている状態です。
確定処理のアクション(CaptureCharge)が受け付けられると、お客様の支払額・残高を確定し、加盟店様に売上が付与されます。
このように、設計時は先ほど紹介した考慮すべき四つのポイントを考えながら整理して、既存の決済処理で拡張可能であればその処理として拡張し、なければ新しい決済処理として抽象化して決済処理機能を開発しています。
開発・運用観点 決済処理の整合性担保
続いて、開発・運用観点でお話ししたいことがあります。実際に決済サービスを提供している方が必ず悩まされる整合性担保の問題についてです。
決済処理を行ったとき、すべて同じサービスの中で完結するものはほとんどありません。メルペイの場合はマイクロサービスのアーキテクチャーを採用しているため、決済時は複数のマイクロサービスや外部の決済サービスとやり取りする必要があります。
依存先のサービスやNetworkレイヤーで障害が発生した場合、いかに決済処理を正しく成功させるか、あるいはロールバックして失敗させるかが大切です。
決済処理の整合性担保については、以前ブログで詳しく紹介しました。もし興味のある方はご覧ください。
マイクロサービスにおける決済トランザクション管理 | メルカリエンジニアリング
https://engineering.mercari.com/blog/entry/2019-06-07-155849/
Orchestration-based Saga with StateMachine
今日はどのような仕組みで行っているかを紹介します。基本Sagaの思想に近いです。
決済処理を担当しているサービスの中で状態遷移マシンベースの仕組みが作られていて、一つの決済処理を複数のStateに分けてそれぞれ状態遷移として、定義されて実行できるようにします。
もしエラーが起きて復旧できない場合、右側の補償処理としてロールバックという状態遷移も定義できます。たとえばもしお客様がカード決済に失敗し、かつ残高が消費された場合、ロールバック処理によって残高消費を取り消した上で決済を失敗させることができます。
エラーが起きても自動でリトライやロールバックをし、お客様視点での金銭的な整合性を担保できます。
最近の取り組み
決済の整合性担保に関する仕組みにおいても、最近新しい取り組みがあります。
先ほどの状態遷移マシンの仕組みが、決済処理のサービスの中で作られたのですが、実は他のサービスでも同じ課題があります。より汎用的なソリューションとして、SDKを切り出して他のサービスでも活用できないかと考え始めました。
またSDKとして提供する際に、今までの反省ポイントとしては状態遷移ベースで処理を定義する場合、デベロッパーにとっては不自然な部分があります。プログラミング体験も改善できないかと思って、新しい仕組みではワークフローのインタフェースを採用した機能を提供し、通常のプログラミングと同じ感覚でロジックを組み立てられるようにします。
たとえば決済処理をあるワークフロー関数として定義し、パラメータを渡せば実行できるようになります。ワークフローの中身はそれぞれActivityを定義して、Activity関数およびパラメータを渡して実行できるようになっています。通常のプログラミングにおいては、ワークフロー関数の中で必要なActivity処理を組み立てれば、整合性を担保して実行できるワークフローが作られます。
裏側ではワークフローエンジンで細かく実行ログが保存されています。どこか処理が落ちても後でそれを起点に再実行できるので、必要であれば補償処理が起こせる仕組みになっています。
加盟店精算
最後に、加盟店精算のドメインについてお話しします。
加盟店精算には、クリアリングとセトルメントという2つの部分があります。
クリアリングは、加盟店様の未精算売上もしくは手数料を正しく計算して、最終売上として確定させる処理をいいます。セトルメントは、確定された売上を加盟店様に振込み、または債権があった場合はお客様に請求して返済してもらう処理です。
精算処理
精算処理の全体のオーバービューがこのようになっています。
上位レイヤーから加盟店決済のリクエストが受けられると、決済処理を担当するサービスで処理されます。決済イベントを通じて精算サービスを受け取り、必要に応じて即時手数料のクリーニングを行い、最終的な売上を確定するという精算サイクルがあります。精算のタイミングで、売上クリアリング処理も実行して、売上を確定させます。
そして、確定された売上を決めたタイミングで加盟店様に出金します。ここでは売上セトルメントと呼んでいますが、実質振込みという挙動になっています。加盟店様が登録している銀行口座へ銀行接続サービスを経由して、銀行側に振込みリクエストを依頼する仕組みになっています。
なお、精算の元になる決済データがずれると、加盟店様に間違えた売上が確定されてし舞うので、元データの信頼性を再確認するため、重要なサービスとデータのリコンサイルも行っています。
最近の取り組み
最後に、リコンサイルについて、最近の新しい取り組みを紹介します。昨年のエンジニアリングブログでも軽く紹介していました。
マイクロサービスにおけるリコンサイルの話 | メルカリエンジニアリング
https://engineering.mercari.com/blog/entry/20211222-1df9e3a553/
決済処理の整合性の証明問題
私たちが抱えている課題は、決済処理の整合性をどうやって証明するかということです。
図で表示しているのは、エントリ・入口の決済リクエストです。僕たちの決済基盤も含めて、処理された後に、参加しているマイクロサービスが複数あった場合、それぞれのマイクロサービスはちゃんと正しく期待通りの結果になったかどうかを検証しなければなりません。
その検証をするために、今までは上の図のように処理フローデータのリコンサイルを行っていました。各マイクロサービス間で、バッチやイベントドリブンによって依存先のサービスとリコンサイル処理を実施しています。
課題
ただし、このフローデータのリコンサイル処理については、私たちが感じた課題としては、以上の2点があります。
一つは強制されたリコンサイルの仕組みがないので、マイクロサービスとバッチの実装・実施を忘れた場合、全体整合性が確認されないため不備が発生するリスクがあります。
また、仮に全てのマイクロサービスできちんと処理を実施したとしても、決済処理の整合性を確認したい場合、関わっているマイクロサービスにAPIを用意し、APIを叩いて確認する必要があります。
会計や監査関連にも同じ課題があります。本当は1か所で全体整合性が取れていることを証明できるエビデンスが欲しいのですが、現状はそんなものがまだ作られていません。
新しい取り組みとしては、スライド下に書かれている分散型Processing Tracingの仕組みが作られています。
分散型システムにおけるトレースの仕組みに馴染みのある方だとわかりやすいですが、一つのリクエストにトレースIDやリクエストIDがついています。それが各マイクロサービスや各サービスで処理されたときに、分散型スパンIDで紐づけて、トレースデータが回収された後に、例えばDataDogを使った場合、Dashboard上からリクエストがどうやって複数のサービスをまたいで処理されるかが確認できます。
これと同じ考え方で決済処理が実行された場合、エントリーサービスでProcessingとして定義し、Processing Tracingサービスに登録します。
その後、図の2番の箇所でエントリーサービスに「Processingの処理に参加しています」という通知をします。そして今までバッチで行っていたリコンサイル処理を必要な依存サービスと実施して、問題がなければ図の4番の箇所で結果のレポートを報告してもらいます。報告の中には、処理結果に問題がないことや、エントリーサービスがどのサービスに依存しているかという情報が含まれます。
そして報告された依存サービスも参加者として認知され、同じように図の2番のイベントを経由して、決済処理のPayment Serviceに通知します。そして同じようにPayment Serviceを受け取ったら、依存先のサービスも同じように今までやっていたリコンサイル処理を行った上で、結果を報告してもらいます。さらにその中でも依存サービスがあれば、それも報告してもらえます。
これらの処理を最後まで繰り返せば、一つの決済処理に参加しているすべてのマイクロサービスを認知できます。またサービスの処理結果や既存サービスのリコンサイル結果の報告をしてもらうことも可能です。その結果、全体の整合性を証明できるエビデンスがProcessing Tracingサービスに集められます。
また、共通の仕組みで処理されることでより強制的な仕組みを提供できるため、先ほどあげた全体整合性の確認不備の課題も一定解消できます。
まとめ
本日のセッションのまとめです。最初は決済基盤の全体のドメイン構成についてお話しして、その後いくつかドメイン設計および開発のプラクティスを紹介しました。また、それぞれのドメインにおける最近の取り組みについてもご紹介しました。
以上で本日のセッションを終わりにしたいと思います。ご清聴ありがとうございました。