ECサイトを作るときに意識したいお金周りの設計

# この記事に関して
ECサイトを作ろうとしたときに一番ネックになるのがお金周りの設計。
「ここは気をつけておかないとヤバイ」
というところを備忘録的にメモしておく。
ただしあくまで「危ないところ」を上げただけであって
それに対するベストプラクティスを書いたものではない。そこは探求中。

# 「価格」を単一カラムで持たない
一番はこれ。
税抜と税込は必ずそれぞれのカラムを用意しておくこと。
「計算すれば出せるじゃん」
と思いがちだが、2019年10月に2019年9月の売上データを表示しようとすると
時間を見て計算に使用する税率を変更する処理が必要になる。

入力の補助として税抜価格を入力したら
その時の税率を適用した税込価格がフィールドに設定される、
という仕組みなら構わない。
（ただし必ず変更できるようにしたほうがいい）
表示する箇所で計算前提にするのは避けるべき。

ちなみに2019年10月以降は

* 税抜価格
* 税込価格

に加えて

* 消費税率

も持たせておいたほうが安全だろう。
軽減税率が適用された価格かどうか判断できるようにするべき。
「軽減税率フラグ」という手も無くはないが
消費税率が3段階になった時に死にそうなので推奨しない。
（昭和フラグという魔のフラグも平成→令和になるときに聞いたし）

## 消費税は切り捨てか四捨五入か切り上げか
消費税の最大のポイントは
小数点以下の扱いが定まっていないということだと思ってる。
自社ECサイトであれば統一したルールによって計算できるが
例えばモール型ECサイトで、複数の販売主が価格を入力する場合
販売主によってその扱いが変わってくる。
これも「表示価格を計算で表示しないほうがいい」理由の1つ。
自社ECサイトに関しても、いつそこのルールが変わるかわからないので
計算はあくまで補助に留めておきたい

## 消費税を計算するタイミング
切り捨てか四捨五入か切り上げか。
このルールによっては「いつ消費税を計算するか」が重要になる。

税抜価格を全て合計した上で消費税を計算するのと
1つずつ計算した上でその結果を合計するのとでは
実は結構違いが出てくる。

例えば税抜30円のお菓子は消費税8%だと32.4円だ。
これが切り捨てか四捨五入だと32円、切り上げなら33円。
ここではたった1円の違いでしかないが、これを100個購入する場合はこうなる。

* 税抜の合計から税額計算
    * 300 * 1.08 = 3240
* 1つずつ計算して切り捨て後に集計
    * 32 * 100 = 3200
* 1つずつ計算して切り上げ後に集計
    * 33 * 100 = 3300

このように3パターンの価格が存在してしまう。
実際にはここは法務的には「どれでもいい」でしかないので
きちんと決めてその通りに動くようにすればいい。


# 価格を元に計算するものは注意
最近QR決済サービスなどで「購入金額の20%還元！」みたいなのをよく見るが
これはなんの価格を元に「20%」なのかを明確にしておく。

簡単なところで言えば税抜価格なのか、税込価格なのか。
配送が発生する場合、送料はそこに含まれるのか否か。
（送料にも消費税が発生するのでそこも注意）

ポイントを現金の代わりに使える様なケースでは
（まさに20%還元された場合とか）
ポイント分を差し引いた金額を元にするのか、など。

実際自分の身の回りで発生する「○%ポイント還元」系のサービスで
何を元にどう計算されているのか？を観察して見ると
これがサービスによってバラバラだったりするのだ。

○%還元、さらに本日5倍キャンペーン！みたいなのはさらに厄介で
「何を5倍するのか？」という観点も必要になる。
還元の割合を5倍にするのか、購入金額を5倍扱いにして還元率は同一なのか。
後者の場合は税抜or税込どちらの金額をベースにするのか。
複数の計算が発生するものは、計算の順番やベースの数値が重要になるので
ここも疎かにしないことがとても重要。

計算に使用する数値が全て出揃って、ルールが明確であれば
実装的な難易度が高いわけではないので
とにかく最初に書いたように「価格は単一カラムにしない」が重要。
最終結果だけ分かればOK、というのは消費者目線でしかない。


# 価格は他の情報と同列に扱わない
これも結構重要。
ついつい商品データを作るときに
「商品名」や「説明」と同列で「価格」を持たせてしまいがちだが
価格は明らかに他の要素と重要度が違う。

例えば商品情報ページで表示されていた情報が
購入完了するまでの間に変更されてしまっていた場合、
価格が変わっているのにそのまま購入完了させてしまうのはとてもよろしくない。
一方で商品名が変わった、とかであれば
軽く警告出しておけば済むかもしれない。
（この辺りはサービスのポリシーによるはず）

なので「価格がいつ変わったか？」は結構重要になるケースがある。
商品テーブルに商品名、説明文、価格、最終更新日あたりを持っていた場合、
商品名が変わったのか、価格が変わったのか、これだけでは切り分けられない。
価格変更日を持つか、いっそ価格テーブルを切り出すか
もしくは価格が変わった＝別商品として扱うか。
別商品として扱う場合、新旧商品の紐付けも必要になるだろう。

## 価格一括変更に耐えうる設計にする
「他の情報と同列に扱わない」にも通じるものがあるが
価格は一括で変わるものである。
消費税アップもそうだし、そうでなくても「価格改定」をしたくなるタイミングは
原価高騰とか理由は様々だが必ずある。
そしてこれがまた厄介なことに、ほぼ確実に「時間指定で切り替わり」を伴う。

やり方はいくつかあるとは思う。

* 価格更新予約テーブルを用意して、時刻が来たらバッチが回って反映する
    * 若干のタイムラグが発生するかもしれない
* 価格テーブルに適用期限を持たせて、改定前の価格と改定後の価格を時間で自動で切り替わらせる
    * 適切なインデックス設計ができていればいける？
* 別商品として扱って販売期間で調整する
    * 改定前の商品は販売終了させて、改定後の商品販売をスタートさせる

実際もっといいやり方があるのかもしれないが…

# キャンセルを気軽に考えない
ステータスカラムをキャンセルにupdateして終わり、みたいなそんな単純な話ではない。
データベースの上ではステータスの問題だけに感じるかもしれないが
実際にはお金が動いている話になる。

月をまたいだキャンセルの場合、前月分はすでに売上として計上していたりするので
単になかったことにするわけにはいかない。
購入は存在した前提で、新たに「キャンセルがあった」という新たな取引を追加する。
そしてそれを今月分の売上としてマイナス計上しなければならない。

商品に対するお金のIN/OUTではなくて
締め単位におけるお金のIN/OUTが表現できなければならない。

やってしまいがちなのは「原則キャンセルは受け付けない」というサービスにおいて
本当にキャンセルのことを考慮しない仕組みを作ってしまうこと。
それは対利用者向けの話であって
システムとしては売上が取り消しになることは考慮しておくべき。

# 最後に
長々と書いたが、要点をまとめれば

* 後から計算で算出しようとしない、その瞬間の価格をなるべく細かく保持する
* 価格改定に耐えられる作りにしておく
* キャンセルを甘く見るな

の3点だったのかもしれない。

多分もっと考慮すべきポイントはあるはずで
また新たな気づきがあれば更新していきたいと思うが
物の販売を行うという経済活動において、お金まわりの仕組みは最も重要なので
もっとノウハウ的なものがちゃんと溜まっていくといいと思う。