LayerX エンジニアブログ

LayerX の エンジニアブログです。

決済基盤の整合性設計を仕様から決める。バクラク請求書発行のカード決済における2つの判断

はじめに

こんにちは。LayerX でソフトウェアエンジニアをしている @ysakura_ です。バクラク請求書発行のカード決済機能の新規開発を担当しました。発行した請求書に対してクレジットカードでの決済を受け付けられるようにする機能です。取引先がクレジットカードで支払うと、バクラク請求書発行の利用者には銀行振込で請求書の代金が入金されます。カード決済の受付から銀行振込での入金まで、一連の処理を担う基盤です。

bakuraku.jp

この決済機能は外部の決済サービスプロバイダ(以下、PSP)や銀行 API と連携して動いています。決済が成功したのに自システムの保存が失敗した、送金 API を呼び出したが結果が返ってこない。立ち上げの中で、こうした整合性の課題に繰り返し向き合いました。本稿では、この 2 つのケースとそれぞれの判断プロセスを紹介します。具体的な実現方法は連携先の仕様に依存しますが、判断のプロセス自体は異なる技術制約のもとでも応用できると考えています。

ケース1: 決済実行時の保存失敗 — 仕様は「ユーザー体験を壊さない」

※ 本節では「利用者」を、カード決済機能を利用する取引先の決済担当者として扱います。

課題と仕様

決済処理は外部の PSP と連携して進みます。次のシナリオを考えてみます。

PSP では決済が成立した。しかし決済サービスへの結果保存が失敗した

DB 接続の一時的なエラーなどで、PSP では決済が成立したのに決済サービスの保存が失敗するケースがあります。決済サービスから見れば「失敗」ですが、PSP 側では成立しています。

本稿のシーケンス図では、実線(→)はリクエスト・レスポンス、点線(⇢)はイベント通知やプッシュ通知を表します。

図1: 決済成立後に内部保存が失敗するケース

カード会社によっては、決済成立時に利用者へ即座に通知が届きます。そのため、画面上の表示と外部からの通知が食い違う可能性があります。ここで失敗を示すと、そうした食い違いが混乱を招きます。「決済を取り消して再実行する」案もありましたが、成立後に決済を取り消す判断はさらに強い混乱を生むため、チームで議論し、PSP で成立した決済は取り消さず、決済サービスの内部状態をPSP側に合わせる方向で仕様を決めました。

仕様: 外部で確定した結果に合わせて、内部状態を更新して回復する。

設計の考え方

実現方法としては、不整合をその場で解消する(決済の取り消しや保存の即時リトライなど)のではなく、利用者にはまず成功として応答し、後続で PSP の決済状態を照会して内部状態を同期する方式を採りました。図式化すると次のようになります。

図2: 成功応答と後続での整合性回復

ケース2: 送金結果不明 — 仕様は「統制・監査上の説明可能性」

決済だけでなく、その先の送金フェーズでも同じ構造が現れました。本機能では決済完了後にバクラク請求書発行の利用者へ代金の送金を行いますが、外部の銀行 API との間で新たな整合性課題に直面します。

課題と仕様

送金処理は外部システムと連携して進みます。次のシナリオを考えてみます。

送金に関する処理を進めたが、ネットワーク断絶等の一時的な障害で結果が返ってこない。処理がどこまで進んだのか分からない

図3: 送金 API のレスポンス不達シナリオ

なお、今回の送金 API は冪等性(同じ操作を何度実行しても結果が変わらない性質)が担保されており、技術的にはリトライしても重複送金は発生しません。ケース 1 と同様に、この場面でも複数の回復手段があり、技術的な妥当性だけを見れば候補はいくつかありました。

しかし、チームで議論する中で 1 つの論点が浮かび上がりました。冪等性によって技術的な安全性は担保されていますが、冪等な API への再リクエストは、元のリクエストが相手に到達していれば「結果の確認」になり、到達していなければ「新たな送金の実行」になります。同じ操作の意味が状況によって変わる構造は、統制・監査の観点では説明が難しくなります。

仕様: 確認系と実行系の責務を厳密に分離する。

設計の考え方

仕様が「確認系は実行を伴わない」と決まったことで、冪等な API への再リクエストで結果を確定させる案は採用しませんでした。結果が不明な状態を無理にその場で解消するのではなく、参照系の API で送金状態を照会し、新たな送金として実行されないかたちで不整合を解消する方針を採りました。

おわりに

ここまで 2 つのケースを見てきました。いずれもトレードオフを引き受けています ── 瞬間的な不整合を許容したり、自動解決できないケースが残ったり。それでも「ユーザーの業務を止めない」「ユーザーに混乱を与えない」という方針は守りたいと考えていました。

振り返ると、2 つのケースはいずれも同じパターンに沿っています。

ケース 境界 優先した観点 対応の方向性
1. 決済保存失敗 決済サービス × 外部 PSP 利用者の混乱回避 外部で確定した事実を基準に回復方針を決める
2. 送金結果不明 決済サービス × 外部銀行 API 説明可能性の確保 確認と実行の意味を混在させない

2 つのケースはそれぞれ異なりますが、いずれも仕様を明確にしたことで技術選択が絞り込めた事例です。技術制約が先に来るケースもありますが、今回は仕様起点で判断できた例として紹介しました。

補償トランザクション、即時リトライ、冪等な API への再リクエスト。いずれも検討しうる手法ですが、重要なのは技術的な優劣ではなく、仕様と技術制約を踏まえてどの手法を選ぶか です。

「競合したときにユーザーにどう見えるべきか」「監査でどう説明するか」── これらは技術の問いではなく、仕様の問いです。PdM(プロダクトマネージャー)や事業側とこの問いを共有し、仕様を明確にすることこそが、整合性設計を正しく進める鍵だと考えています。


最後に、バクラクの決済事業ではソフトウェアエンジニアを募集しています。仕様やその前段となる判断軸から議論し、それを技術設計に落とし込むプロセスに日常的に携われる環境です。興味のある方はぜひカジュアル面談や求人ページをご覧ください。

jobs.layerx.co.jp

open.talentio.com