静的MPM決済を支える技術

susho氏:こんばんは。「静的MPM決済を支える技術」ということでsushoが発表させていただきます。

最初に自己紹介です。

社内ではsushoと呼ばれているので、ここでもそうさせていただいております。Twitterは@susho0220でやっています。バックエンドエンジニアで、メルペイのCode Paymentチームに所属しています。Go歴は4年ぐらいです。僕も2018年までYahoo! JAPANに在籍していまして、そこでオブジェクトストレージ「Dragon」の開発に従事していました。

今日のアジェンダです。

最初に「静的MPMとは」というお話をさせていただいて、そのあとは静的MPMを使ったマイクロサービスアーキテクチャについてお話します。最後は、マイクロサービスアーキテクチャで実装する上での不整合対策についてお話できればと思います。

まず「静的MPMとは」です。

静的MPMとは、店舗で紙などに印刷された固定のQRコードを提示して、それをお客様が読み込んで決済するというものです。メリットは、店舗側はQRコードを配置するだけで導入可能なので、CPMなどの他のQRコード決済の仕組みと違い、POSなどが不要なので導入コストが非常に安いです。

考えれるデメリットとしては、QRコードが改ざんされる恐れがあります。これは昔の話ですが、改ざん防止のために固定のQRコードをガラスに貼ったり、特殊なステッカーに印刷したり、決済時に強制的に音を出させたり、結果を店舗のメールアドレスに通知することで改ざん防止しています。

静的MPMのマイクロサービスアーキテクチャ

6月27日に「メルペイ」のお客さま読み取り式コード決済機能をリリースしました(プレスリリース)。現在順次利用店舗を拡大していっています。

メルペイの静的MPMの仕様ですが、JCBやVisa、Mastercardなどの国際ブランド6社からなる技術団体「EMVCo」というものがあり、そこで策定された共通規格「EMV」を採用しています。

また、QRコードのエンコードとデコードでは自社で開発したOSSのライブラリ「go-emv-code」というものを使っています。

続いて、この静的MPMのマイクロサービスアーキテクチャについて説明したいと思います。概要になってしまいますが、順を追って説明をしていきたいと思います。

メルペイでは各マイクロサービスがGCPのGKE上に構成されています。

これは静的MPMのざっくりしたシーケンスですが、まずはGoogle Cloud Load BalancerからMerpay GatewayというAPIゲートウェイにHTTP+JSONやHTTP+ProtocolBuffersのリクエストがきて、そこで内部通信に使っているgRPCへのプロトコル変換やルーティング、認証を行っています。

この認証にはAuthority Serviceというマイクロサービスがあり、ここで認証トークンを生成しています。この認証トークンはすべてのマイクロサービスが検証をする必要があり、各マイクロサービスはAuthority Serviceとバックグラウンドで通信をして、トークンの鍵を取得しています。

その他の処理が行われたあと、Merpay APIにリクエストが飛んできます。ここはいわゆるBFFの機能を担っています。

そしてMerpay APIからStaticMPM Serviceという、今回主に実装したマイクロサービスにリクエストが飛んできます。ここではまずQRコードの解釈を行います。QRコードの中に入っている店舗情報のキーになるIDを取ってきて、Partner Serviceから店舗情報を取得し、実際の決済処理のために決済共通の処理を行うTransaction Serviceにリクエストを投げ、決済基盤であるPayment Serviceにリクエストが投げられます。

これが成功したら、通知処理を担うNotification Serviceにリクエストを飛ばして通知を行います。だいぶざっくりですが、こんな感じになっています。

決済シーケンスについて

続いて決済シーケンスについて、もう少し踏み込んだ部分を説明したいと思います。最初のメルカリアプリ内でQRコードを読み取り、Mercari AppからMerpay APIにQRコードと一緒にセッション生成リクエストが投げられます。

そこからStaticMPM Serviceにリクエストがきて、StaticMPM ServiceがQRコードの解釈・店舗情報の取得・セッショントークンの生成を行います。セッショントークンについては後述します。

そこから店舗情報とセッショントークンを返し、Mercari App内で購入画面が表示され、金額入力、購入ボタンを押し、また決済リクエストが飛んできて、StaticMPM Serviceではセッショントークンの検証と決済処理、通知処理を行い、お客様のアプリに完了画面が表示されるというシーケンスになっています。

StaticMPM Serviceの機能

StaticMPM Serviceについてどんな機能があるのかを説明します。

静的MPM決済のドメインに関するマイクロサービスでQRコード解釈では、go-emv-codeを使っています。アプリでQRコードを解釈することも考えたんですが、今後のさまざまなQRコードの仕様に対応する必要があり、拡張性を高くしたかったのでバックエンド側で処理をしています。

ここで出てきたセッショントークンなんですが、アプリでQRコードを読み取ったあとに一定期間のみ決済可能にする仕組みです。これは何のために実装したかというと、お客様がQRコードを読み取ってそのあと画面を一定期間放置して、そのあとに間違って購入ボタンを押してしまった場合にも決済ができてしまうので、一定期間放置されたセッションは弾くような実装をしています。

決済処理ですが、いろいろなマイクロサービスでやり取りをして、静的MPM決済のドメインに沿った各処理を実行しています。ここの決済処理の設計をミスすると容易に不整合が発生してしまうので難しいです。先月、弊社のエンジニアが「マイクロサービスにおける決済トランザクション管理」というブログを出していたので、読んでいない人は読んでみてください。

静的MPMにおける不整合対策

不整合対策が難しいということがあったので、それについてざっくりと説明したいと思います。不整合対策をする上で、僕らが考えているキーポイントです。

まず冪等性、Write repair、Asynchronous repairが大事だと考えています。冪等性に関しては、クライアントは同じリクエストを繰り返し呼ぶことができ、かつ同じレスポンスが返ってくるという状態です。

メルペイでは、クライアントがIdempotency Keyという、各々が発行したUUIDをリクエストに付与しています。

続いてWrite repairです。通信先のマイクロサービスやネットワークに一時的な障害があった場合、クライアントは同じリクエストをリトライすることで不整合をオンデマンドに修復できます。タイムアウトとリトライを使ってやっています。

そのためにGoのgRPCのInterceptorのライブラリであるgrpc_retryを利用しています。

Asynchronous repairですが、Write repairでリトライしても修復できなかった場合に、非同期でマイクロサービス間の最終的な状態をチェックして、仮に不整合が発生した場合はあるべき状態に修復します。今はバッチによる定期実行をしていますが、今後の予定としてはCloud Pub/Subなどを使ってイベント駆動な設計にして、よりスケールするように修正する予定です。

Write repairとAsynchronous repairの働き

では、ここで新たにでてきたWrite repairとAsynchronous repairについてざっくりと説明します。

まずはWrite repairについてです。StaticMPM Serviceが決済リクエストを投げるときに、Idempotency Key XXXというものを付与してTransaction Serviceにリクエストをします。Transaction Serviceは、そこからYYYというIdempotency Keyを作って一旦データベースにトランザクションとともにログとして書き込みます。このときはステータスをinitialとしています。

そこから決済リクエストをPayment Serviceに投げて、このときにIdempotency KeyのYYYを付与して投げます。

ここでPayment Serviceが高負荷であったり、ネットワークの分断があったりして決済がうまくいかなかったと仮定した場合、クライアントがタイムアウトを検知して、同じIdempotency Keyでリトライして、Transaction Serviceがログとして書き込んだIdempotency Keyを取得し、同じようにリトライをすることで不整合を解消できます。

続いてAsynchronous repairです。

ここは簡単で、ステータスが一定時間経過してもinitialなままだったトランザクションをデータベースが定期的にスキャンして、その対象の状態をPayment Serviceに確認しにいきます。仮にAuthorizedだった場合、決済の取り消しリクエストを実行して、対象のトランザクションをキャンセルします。そしてNot FoundだったりAuthorizedではないステータスだった場合は、そのままトランザクションを破棄します。

現状の課題と今後について

現状の課題です。これらの各機能をマイクロサービスそれぞれのアプリケーションコードに実装しなければならないということと、通信元や通信先のネットワーク遅延や分断状態を再現してテストをするのがとても難しいです。例えばiptablesで疑似的にパケット遅延を発生させたりしてテストをすることはできるんですが、けっこう大変で、etcdという分散キーバリューストアでGoでごりごりテストをしているんですが、なかなか大変です。

これから試してみたいことは、Envoyを使ってサービスメッシュの導入をしていきたいと考えています。これにはタイムアウトやリトライのテストをしやすくするためのFault Injectionという機能があるので、それを使ってもう少し信頼性が高く開発しやすい基盤を使っていきたいと思います。

まとめです。go-emv-codeを使うとEMV規格のQRコードのエンコードやデコードができるので、使っていただけるとありがたいです。サービスメッシュの登場で、マイクロサービス間の不整合対策の実装やテストがやりやすくなっていきそうな気がしているので、導入して試していきたいと思っています。

以上です。ご清聴ありがとうございました。

(会場拍手)