決済システムの設計ポイント、変更しやすい決済システムの設計論

## はじめに
株式会社ドワンゴ ファンコミュニティ事業室 VP of Engineering の @chiyoppy です。

これは [ドワンゴアドベントカレンダー2024](https://qiita.com/advent-calendar/2024/dwango) 16日目の記事です。諸事情で19日の投稿となってしまい大変申し訳ありません。

昨日は @sup_miso さんの記事 [微分方程式で部屋の二酸化炭素濃度をシミュレーションした話](https://qiita.com/sup_miso/items/74bcc8e21cca6a479f89) でした。微分方程式には微妙にトラウマがあるのですが、二酸化炭素濃度を低く抑えることは集中力を維持するために必要なことのひとつだと思います。

さて、今日は、決済システムの設計ポイント、変更しやすい決済システムの設計論を紹介したいと思います。

なお、こちらの記事は [Qiita×PAY.JP Meetup〜恐れずに挑む決済開発：エンジニアのための実践的アプローチ〜](https://increments.connpass.com/event/337974/) のセッション「事業を成功に導く決済システム設計・開発のポイント」の資料を使用しています。

## 決済システムのドメイン分析
![スライド3.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99980/ad3d3351-72a3-b698-3adf-61ff3c084055.jpeg)

### 決済は合意と履行に分解できる
決済システムのドメイン分析をどのように行うと良いのかを考えます。まず、決済ということばを辞書で調べると、「売買取引を終了すること」と記載されています。

ここで、 __取引__ と __終了__ という2つの単語に着目します。

__取引__ とは、相手と何かについて合意をすることです。次に、この合意したことをお互いに履行することで、取引が __終了__ します。つまり、合意と履行を合わせたものが __決済__ です。

### 合意: 契約のこと
合意をする、というのは社会で利用されている言葉をつかうと、 __契約__ するとも表現できます。

契約は、契約の目的、契約条件（契約期間、価格など）、契約当事者（Webサービスの場合、自社と利用者の情報）などが含まれます。

また、契約はシーケンスの観点では申し込みと承諾というステップに分解することもできます。これは、画面遷移を作る上で必要な視点ですが、モデルがシーケンスに囚われないようにするために、一旦置いておきます。

### 履行: 売上債権とサービス提供義務
今日ここではサービスプロバイダー側の視点にたって整理します。履行は、契約にもとづいて対価を請求する権利と、サービスを提供する義務に分解することができます。

よくある誤りとして、売上債権の履行のみを決済ドメインと考えてしまうことが挙げられます。こうした整理をしてしまうと、何によって売上債権が発生するのかという整理がおろそかになってしまい、全体のモデリングが上手にできません。

契約と債権、債務のすべてが決済ドメインであると整理することが、決済ドメイン全体を捉える上で重要です。

## 各モデルの役割と安定性
![スライド4.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99980/b9ae3877-00c0-ea05-1127-6e22bc632067.jpeg)

### モデルの整理

#### 契約モデル
契約モデルには、ユーザー(ID)、商品名、価格・通貨、支払手段、契約期間、初月無料割引等の役割があります。ちなみに、取引価格を決定するのも契約モデルの役割であることから、消費税の計算も契約モデルに含まれます。

#### 債権モデル
債権モデルには、請求やチャージバック等の役割があります。

#### 債務モデル
債務モデルには、ユーザー情報の変更等の役割があります。

### 安定性分析
契約モデルは、サービスが成長するにつれて、要求が変化しやすい（＝ __不安定なモデル__ ）です。要求の変化というのは、たとえば、商品を追加したいとか、支払手段を追加したいとか、契約期間を月間プランのほかに年間プランを追加したい、などです。

債権モデルと債務モデルについても考えてみます。

債権モデルでは、たとえば請求金額が 1,000 円なのか 5,000 円なのかによってやることが変わるかというと、基本的には変わりません。通常、決済エージェンシーに渡すパラメータを `1000` から `5000` に変えるだけです。

債務モデルも、商品が追加・変更になったとき、変更すべきユーザーのパラメータが少し変わるだけで、この程度の要求の変化はモデルに対する（本質的な＝リモデリングが必要なレベルの）変更であるとはいえません。

したがって、債権モデルや債務モデルは要求が変化しにくい（＝ __安定なモデル__ ）です。

### 作るべき依存関係
ざっくりではあるものの、上記の安定性分析から、契約モデルから債権モデル・債務モデルへの依存関係をつくることはあまり恐れる必要がないと結論づけられます（安定依存の原則）。

### （補足）契約モデルの肥大化への対策
上記から簡単に想像できる通り、契約モデルは肥大化しやすいです。サービスが成長して契約モデルが肥大化してきた場合は、契約モデルの一部（たとえば商品とか、ディスカウントのロジックとか）を別のモデルに分割しても良いでしょう。

この場合も、切り出したモデルと（切り出し元の）契約モデルが共依存にならないように留意します。特にディスカウントのロジックなどは、かなり慎重に設計しないと、つまりドメインエキスパートの説明をそのまま実装してしまうと、相互依存になりやすいと思います。

ビジネスロジックにややこしい依存関係がある場合に、コンポーネント同士が相互依存にならないようにするテクニックには、たとえば Strategy Pattern や Mediator Pattern があります。

## 決済エージェンシーとの関係
![スライド5.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99980/782c848d-f47a-6234-8b75-97ae87061a97.jpeg)

### 決済エージェンシーとは
次に、決済エージェンシーとの関係について整理します。決済エージェンシーとは、ここでは、カード会社や国際ブランドと加盟店である自社の間に入るシステムのことです。

### 決済エージェンシーの安定性: 高い
決済エージェンシーのインターフェースは安定しており、私の経験でいえば、15年以上破壊的な変更が入っていないインターフェースも存在します。このため、決済エージェンシーごとにサービスをつくることを考えるとき、各エージェンシーと強く結合することは、あまり恐れる必要がありません。

### 決済エージェンシー自体の選択の安定性: 低い
一方で、どの決済エージェンシーを採用するかというビジネス要求は不安定です。たとえば、アプリ内決済を導入したいとか、なんとかペイを導入したいとか、こういった要求は後から発生する可能性が高いです。

どんな決済エージェンシーを後から追加することになるかは予測が難しいです。

決済エージェンシーのインターフェースの安定性に惑わされないでください。たしかに、決済エージェンシーは安定しています。依存や結合を恐れる必要は低いです。ただ、ずっと同じ決済エージェンシーだけを使い続ける可能性は低いです。

### 腐敗防止層としての債権モデル
_Disclaimer: これは特定の決済エージェンシーについて記述する意図はありません。あくまで設計上のテクニックとして、腐敗防止層（Anti Corruption Layer）というアイディアが利用できることを示しています。_

債権モデルはドメイン駆動開発における腐敗防止層であると整理することができます。

DDD(Domain-Driven Development: ドメイン駆動開発）にある腐敗防止層の設計テクニックを使うことで、上手に設計できると思います。具体的には、契約モデル側をきれいなモデル、決済エージェンシー側を腐敗側と定義して、決済エージェンシー側のデータモデルと契約モデルに開示するサービスやデータモデルを切り離すというアイディアがあり得ます。

個人的には、腐敗防止層のアイディアを早期に導入すべきとは思いませんが（設計上は綺麗になるが、実用上のベネフィットが生まれづらい）、契約モデルとの相互依存が強く、問題が発生している場合には有用だと思います。

### （参考）結合度について
結合度を下げるときに、どのような状態が下がった状態なのかという定義は [Wikipedia - Coupling (Computer programming)](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) が詳しいです。

契約モデルと債権モデルの間には、本質的な順序依存が存在しておりこれを排除することはできない（依存関係を完全に消すすることはできない）ものの、データ結合やスタンプ結合のレベルまで下げることは可能です。

もちろん開発リソースは限られているので、とくに、債権モデルから契約モデルへの依存関係の方に注目することで、コスパ良く良い設計（将来の変更に対応しやすい設計）ができます。

## シーケンスとのモデルの対応
![スライド6.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99980/eb09288a-0609-d874-ba77-f2602df62bea.jpeg)

### 契約モデルに閉じ込める
具体的なシーケンスはサービスによっていくらか異なると思いますが、各シーケンスは契約モデルからはじめて、契約モデルで終える、言い換えると契約モデルの中に閉じ込めることが重要です。

決済システムを開発してきた方には、このシーケンスを冗長に感じられるかもしれません。しかしながら、サービスの解約は支払を止めることではなく、サービスの利用契約にもとづく解約申し込みであるという整理の方がより現実の権利義務関係をモデルとして正確に表現していると思います。

## おわりに
いかがでしたか？

決済システムの設計・開発は、慎重さが求められるというのは否定できない一面ではあるものの、基本的なアプローチは普通のWebサービスと同じテクニックが活用できます。

「お金・売上に関わることだから心配」「決済エージェンシーがいるから安心」といったイメージだけではなく、課題を論理的に分解して良いソフトウェアを作れることを祈っています。

明日は @misuken さんの [ニコニコ生放送でBCD Designを4年運用した知見(基礎テクニック編)](https://zenn.dev/misuken/articles/6bfadc96f1fecb) です。

## Appendix
### 債権モデルと債務モデルの間の整合性
債権モデルと債務モデルは契約モデルで統合されますが、システム障害やバグなどで、この2つのモデルの間でデータの一貫性が毀損することがあります。

「料金を支払ったのに利用できない」「サービスを利用できるが、料金を支払っていない」といった状況は、ビジネス上の大きな問題として評価されることが多いです。このとき（利用する決済エージェンシーにもよりますが、私の経験では）一貫性があるというのはどういう状態なのかはけっこう緻密に考える必要があります。
というのも、実際に請求処理が行われて、相手の（たとえば）カード会社が承認したタイミングと、自社のシステムが請求を完了したタイミングにずれがあるためです。詳細は各決済エージェンシーの NDA の範囲内の仕様に触れる必要があるのでここには書けませんが、いつかこういった情報も書ける日がくることを期待しています。