システムエンジニアシステムエンジニア
自営業自営業
2021年3月 - 2021年9月 · 7ヶ月2021年3月 - 2021年9月 · 7ヶ月
## プロジェクト概要
自社システムの決済、サービスが燃えてたので火消し。
## 担当フェーズ
開発、運用、保守
## 業務内容
- プログラムコーディング
- テスト設計
- table設計
- 単体テスト、結合テストの実施
- 保守メンテナンス
- ヘルプデスク
## 要件
メンバーとしてプロジェクトに参加し、主に決済周りの担当を務めました。
以下はすべて要件定義、コーディングまでほぼ一人称でやっています。
- 外部決済システムのVirtual口座と自社システムとの連携のtable設計およびコーディング
- バッチ処理を速度改善のため、pythonからgolangに書き換え。またsqlの効率化
- ヘルプデスクの仕事を無くし、事務との連携の簡易化のため一部の事務用の資料の自動作成
- OEMの商品のサブスクリプションのポイント管理システムの実装
- ポイントのロジック、table構成の見直し
- ギガ購入のテーブルの見直し
### 課題1
この会社の決済は外部決済を利用しており、クレジットカード決済と口座振替に対応していた。
クレジットカードは、毎月10日が決済日で、ユーザーのクレジットカードに請求がされる。
しかし、口座振替は決済会社の都合により、月により決済日がまちまちで
9日決済だったり、15日決済だったり、ゴールデンウィークは3日だったりで
決済日が違う。
また、口座振替は決済日に直接ユーザーの口座から引き落とし処理がなされるため、
決済日までに銀行口座にお金が無いユーザーは決済に失敗するため、お金が振り込まれない。
決済代行会社のシステムではクレジットカードと口座振替含め、その月初回の決済が失敗した場合は、
同月に2回目の決済処理が走る。
クレジットカードは25日、口座振替は月によって2回目の決済日が違い月の中旬から下旬、場合によっては翌月の2日、3日となる。
このため、口座振替のユーザーかつ初回決済に失敗したユーザーは銀行口座の残高によっては翌月2重に支払われていると錯覚しクレームが入ったり、振り込み期限を間違えてさらに利用料を滞納するユーザーが出てきたり、事務作業が非常に複雑で会社側の事務が対応を間違えてさらにクレームが入ることもあった。
会社としては口座振替の決済フローとシステム上の流れを変更してこの状況を打破したい。
### 取り組み1
#### 課題解決の流れ
外部決済のシステムにvirtual口座という機能がある。
これは、利用期限が決められた支払い用の口座である。
この口座番号をその月の初回の口座振替に決済に失敗したユーザーに
メールで送りつけて、25日までに決済させることにより、口座振替の決済日のブレを無くして、
決済日にルーズなユーザーからも自主的に決済日を意識させることにより、利用料の回収と事務作業の軽減を行う事ができると考える。
#### 業務、システム的な仕様
外部決済のシステムの口座振替決済失敗時に走る2回目の決済処理は、月の初回に口座振替の決済に失敗したときにapiで2回目の決済を無効にすることで、2重に課金されることは防げる。
外部決済の仕様により、全ユーザーにデフォルトで口座振替の月2回目の決済を無効にできないため、
このapiは口座振替の決済が失敗した時点で速やかに実行する必要がある。
また、Virual口座用のTableを作成する必要がある。
以下の仕様となる。
Table設計
そのユーザーIDと利用料の金額、請求書へのID、決済失敗日、Virtual口座の番号、Virual口座への決済期限、Virual口座が決済成功したかどうか?の各カラムが必要。
決済フラグは未決済、決済済み、決済期限が過ぎたの3種類。
決済済みにしたら、Virual口座と紐づいている請求書へのIDをたどり、その請求書を支払い済みにして、
他に滞納しているお金が無ければユーザーの信用情報も支払い済みにする。
こうするとシステム上は支払い済みの状態になる。
口座振替の決済が失敗した時点で、このテーブルにユーザーのデータを挿入。
そして口座番号、支払い期限、支払い金額をユーザーにメールで送信。
期限日までに支払わさせる。
期限日までに支払った場合は外部決済のシステムから支払いのapiが即時に自社に流れるので、
そのapiをもとに毎日17時にpythonバッチによりVirautl口座と請求書、ユーザーを支払い済みにする。
Virtual口座の支払い期限を過ぎても、支払いが無かった場合はVirautl口座の決済フラグを決済期限が過ぎたに変更して、ユーザーの信用情報をブラックにする。
これはVirtual口座支払い期限当日のpythonバッチにより処理する。
Virual口座の支払期日を過ぎても、口座に振り込んだ金額は消失するわけではなく、
一度振り込みが保留になるだけであるため、
外部決済の会社との事務との連携でちゃんと振り込み扱いにでき、また、自社も利用料を回収できる。
この場合は振り込み自体が保留なのが、apiではわからないが、外部決済の会社の管理サイトでは
保留なのがわかるので自社の事務にその作業はやってもらう。
保留から振り込み扱いにした瞬間にapiが自社に来るので、そのapiをもとにVirtual口座とその他関連のテーブルは支払い済みにすることで運用上問題はない。
ユーザーには支払い期限が過ぎた場合はシステム上で処理に時間がかかることを、あらかじめ事務方面の方から通知してもらうことで解決できる。
### 成果1
ユーザーから利用料の回収と、クレームが減り、また事務作業も大幅に軽減された。
特に口座振替決済日前後は事務がみんな残業だったのが、3/4程度の人数で運用できるようになり、
残業もほとんどなくなったのはかなりの成果だと思います。
#### 課題2
この会社はMVNOの会社のため、回線自体はDocomoの回線を
使わせてもらって、その利用料金に自社の利益を載せてユーザーに請求している。
その前月利用料を巨大なcsvで毎月5日9:00から12:00ごろに送られてくる。これを6日9:00に請求書として
ユーザーに見えるようにWebサイト上で表示して欲しい。
しかし、このバッチ処理はかなり重いため、利用料のデータだけをダウンロードして
ローカルで個人の化け物スペックのマシンで3時間ほどバッチ処理を回して、その結果を本番のDBにマージしていた。
個人のマシンに依存するのは運用上よくないし、バッチ処理に3時間かかるならもし不具合があったときに翌日に請求書を作ることが困難になる。
経営サイドの要望から6日からずらすことは無理であることが判明したので、
バッチ処理の負荷軽減、時間短縮を行いたい。
また、これ以外のバッチ処理も今後重くなっていくorすでに重くて割と問題になっている
物もあり、せっかちなユーザーからはクレームも来ている。
### 取り組み2
#### 課題解決の流れ
まず、バッチ処理を見てみる。
これはpythonで書かれており、大量のデータ処理上ではメモリ上不利である。
コンパイル言語にすれば速度が上がりそうである。
Java、Csharp、Rust、Goが候補に挙がった、
JavaはJavaエンジニアが単価が高い割にあたりはずれが大きいので運用上はエンジニア採用で
ギャンブルになる可能性があり、また書くのに時間がかかる割に危険なコードもかける。
CsharpはLinux上の実績が少なく、CsharpでLinuxのシステムをがりがりかけるエンジニアを探すのに苦労しそう。
Rustはまだ若く、エンジニアを探すのに苦労する。また、Golangと比べてコンパイルに時間がかかり、今後決済の処理に問題があったときの修正が発生するときにコンパイル時間で困る可能性がある。またプログラミングスキル差がかなり出る言語である。
Goは単価が高いが、単価以外の上記のほかの言語の問題点をすべて解決しており、CTOに以上の事を報告して、今後バッチ処理は緩やかにすべてGolangにしていくことを伝えた。
メンバーは自分以外はGolangの経験が無かったので、なるべくほかのメンバーでも読めるように、横展開したときにかけるように簡易な書き方をして、複雑にならないように書いた。
#### DBの処理について
既存のエンジニアがSQLが苦手なせいか、
Ormを使ってプログラミング言語からループをぶん回して集計、まるめ処理を行っており、
DB、プログラミング間のIOが馬鹿にならないレベルで大きい。
またプログラミング言語側で集計用の変数でメモリを圧迫しているため、これもDB側に押し付けたい。
集計、データ処理のためのメモリの使い方はDBの方が賢く、プログラミング言語と比べて処理が遅くなっても単純にDBスペックを上げるだけで簡単に解決しやすい。
こうすれば今後処理がさらに10倍ほど重くなっても対応できるだろう。
集計、まるめ処理などの加工はDB側ですればよい。
ちゃんとクエリを書き、計算用の中間テーブルを実装するなどして、
なるべくDB、プログラミング言語間のIOを減らすための工夫をすると負荷が軽減できそうだ。
プログラミング言語↔DB間はプログラミング言語からsqlをキックするだけにして、
どうしても実データがプログラミング言語で欲しい時以外はデータを取ってこないとする。
### 成果2
処理時間が劇的に減った。化け物スペックのもので3時間ほどかかった処理が
ローカルでやるならちょっと良い程度のメモリ16G、Intel第7世代corei7程度のPCで15分ほどに削減され他のメンバーでもかなり触りやすくなった。
また、請求書の作成に不具合などがあっても当日の修正が容易になった。
### 課題3
自社システム上の決済処理と、外部決済のサイトに書かれている決済処理
がちゃんとあっているかどうかの確認処理は事務作業になっており、
これを確認するためにエンジニア側に定期的にヘルプデスク的な作業が
たびたび来る。
エンジニアは5名程度しかおらず、みんながみんな開発と運用とヘルプデスクの
作業を同時並行でやっている状況のため、ヘルプデスク的な作業を減らす必要があった。
また、決済処理や請求書の集計は負荷が大きく、SQLも複雑なのでそもそも書けないエンジニアもいた。
業務を理解している必要があったので、業務がエンジニアに依存するリスクがあった。
決済処理について一番詳しくてDB、SQLに詳しいのが自分だったのだが、
忙し過ぎて手が回らなくなってきたので、PMに言って自作した。
### 取り組み3
クレカ登録の人は毎月の決済時間の3時間ほど後に決済処理をもとにクレカの決済csvを作成し、
口座振替の人も同様に毎月の引き落とし日に決済処理をもとに当日口座振替の決済csvを作成し、
Virtual口座に関しても毎月の支払い期限日に決済csvを作成する。
それを経理担当のslackのチャンネルにプログラミング言語から投稿すれば実装できる。
口座振替、クレカ両方の決済が終わったときは両方の決済処理を含むcsvを作ればよい。
また、毎月の初回決済失敗後に2回目の決済に成功したなどの場合や、
支払い期日前のVirtual口座の支払い状況なども確認できるようにするため、
手動で任意のタイミングで決済状況を確認できるように実装した。
業務としては
1. 手動の場合はプログラマがbashから適当な引数を与えれば、slackにcsvが投稿される。
2. それ以外の場合はcronから適当な引数が与えられるので、自動的にslackにcsvが投稿される。
3. 引数については201903など具体的な月を与えれば、その月の決済処理が投稿される。ユーザーの情報ごとに集計を取ることができ、信用がブラックなユーザー、特定のidのユーザーなど柔軟に指定することができる。
という風にした。
もし、経理や事務側が決済csvについてなんらかの疑問(ユーザーに2重引き落としされた可能性がある、や支払ったつもりだが支払ってないなど)があったら、slackの投稿にそのままエンジニアにメンションつけてレスをすれば、無意識のうちにエンジニア間で決済の不具合や問題について連携が取れるため、情報も散らばることなく運用できる。
プログラミング言語としてはgolangで実装した。
### 成果3
決済の情報が前より散らばらなくなったのと、決済データの集計のために
いちいち手が止まることがなくなったので、大分楽になりました。
事務、経理とも決済の連携がとりやすくなったので作ってよかったと思います。
### 課題4
この会社は通信事業とは別にOEMとして化粧品やウォーターサーバーなども取り扱っており、販売は外部の業者に代行させている。
毎月5に前月分の商品の売上数、代金、買ったユーザーをcsvで自社に送付するので、それを見て目視で事務が各ユーザーにポイントを手打ちで付与していた。
これだとオペミスで2重にポイントを付与してしまったり、ポイントのつけ忘れが出てくるので
これを無くしたい。また、今の形でポイント付与だとユーザーに即時反映されてしまうため、ポイントの取り消しができず、ポイント付与日前に事務作業のチェックという業務フローが作れないという欠点がある。
これらを解消したいというCTOとビジネスサイドからの要望である。
「csvはとても煩雑で機械的に処理できる形になっていないため、事務がcsvを見て処理するしかない。なので、事務の負担を減らすように一部の処理だけにはなるが自動化してほしい」と。
OEMの商品に対してポイントを付与する場合は以下のルールがある。
1. ウォーターサーバーなど、単価が高い一部の商品はポイントを分割して支払う。例えば、2000ポイント付与の場合は500ポイントx4ヶ月という支払いになる。定期購入は無く、買い切りで複数欲しいならその数だけユーザーは購入することになる。
2. 化粧品などは定期購入している限り毎月指定のポイントが支払われる。
3. 定期購入の商品には商品ごとに個数上限があり、それを超えて購入することはできない。
4. 商品によっては初回購入特典が存在しており、その場合は本来の購入時のポイントに加えて特定のポイントが別途付与される
それとはまた別に下のケースにも対応して欲しいとの依頼もあった。
1. オペミスや不具合、お詫びなどなんらかの理由でポイントの支払い
2. 不具合などなんらかの理由のためのポイント調整のためのマイナスのポイント付与
3. 画面からオペミスをした場合は誰がいつ、どのようなオペミスをしたかわかるように履歴も見れるようにして欲しい。
GUIの画面としては以下のように実装してほしい
1. 商品IDと支払う商品のポイント、ポイントが一括か分割で支払われるかを事務から触れる画面
2. 定期購入、ポイント分割含めてなんらかの理由でポイントの付与を止めることができる画面。理由もかける。
3. ポイントを分割で付与できる商品があるので、あと何回付与か管理できる画面。
CTOとビジネスサイドのイメージとしては以下のようになる。
定期購入の場合
1. 毎月届くcsvを参考に定期購入のポイントが今月は付与されるか確認。
2. 新たに定期購入があるなら、その商品の定期購入フラグをつける。
3. 定期購入を続けるならポイントの購入フラグをそのままにする。
4. 定期購入をやめたなら購入フラグを消すという形で処理。
5. 定期購入フラグがついている限り、商品に応じたポイントが支払われる。
ポイントを分割で付与する商品の場合
1. ポイントが分割処理される商品があるか確認。
2. ポイント分割処理される商品の購入を見つけたら、その商品のポイントを分割して付与する
3. 指定回数ポイントを付与されたら、ポイントがこれ以上付与されない状態であることを確認
これらを画面上で処理したい。
以上である。
### 取り組み4
また、OEM商品が定期購入か、ポイント分割か
合計でポイントをいくら付与するか、
何回ポイントを分割するか?を
管理するマスターテーブルが必要になる。
これをOEM商品テーブルとする。
ポイントの付与を管理するTableが必要になる。
これを自動ポイント付与番号とする。
自動ポイント付与番号はOEM商品テーブルを参照し、
ユーザーに対して、以下の情報を紐付ける。
1. 購入した商品が定期購入か、ポイント分割か
2. 一回で付与されるポイントの額
3. 初回購入フラグ
4. 初回購入特典のポイントの額
5. ポイント付与回数
6. 自動ポイント付与番号によりすべて付与された場合のポイントの付与総額
7. 作成日時
8. 自動ポイント付与番号が現在有効かどうか
これらの情報を自動ポイント付与番号を作成ごとにOEM商品テーブルのデータを
Insertしている。
その時点でのInsertにしないと後で経営の方針により、購入時とポイント付与の額が変わったときに不具合が出るからである。ポイント分割付与の場合は特に事務が一度触ってから放置という運用が基本になるので気づきづらい。
定期購入の場合は、その月からOEM商品テーブルと、現在有効な自動付与ポイント番号のポイントの額を一括でupdateしたら正しく動く。
仕様としては以下のようになる。
1. 同一商品で複数個定期購入できるものは複数自動ポイント付与番号を作成する
2. 定期購入が確認できなかったら、自動付与ポイント番号を手動で無効にする
3. ポイントを分割付与する場合は、指定された回数ポイントが付与されたら、自動ポイント付与番号は自動的に無効になる。
4. 初回購入フラグがついている場合は、初回購入ポイントを参照してポイントを付与し、初回購入フラグがついていない場合は通常のポイント付与になる。
事務はユーザーが商品の新規購入を確認するごとに、
この自動付与ポイント番号を管理サイトから登録すれば、
毎月、前月購入分のポイントを支払うことができる。
これをバッチ処理からキックすることにより、実際にポイントが付与される。
一回処理するごとにポイントが付与されるのでシステム上は「一月たった」という判定になる。
bashから特定の自動ポイント番号に対してのみ処理することもできる。
これには2つ理由がある。
一つは不具合があったときのエンジニアによる手動処理のためと、簡単に動作テストを行うためというシステム側の運用のための理由である。
2つ目の理由はお詫びやなんらかの理由でのポイントの払い戻し、支払いなど事務側の運用による理由である。
2つ目の理由については件数が少なく安全のために事務側とシステム側でちゃんと突き合わせてから
処理するべきであり、事務側の混乱を防ぐためGUI側でできるように実装しないほうが好ましいと考えた。CTOにも実装せず、業務を理解しているエンジニアが確認してから処理するほうが好ましいと伝えた。
もし、必要ならばSlackでエンジニアにメンションをつけて事務側が今の状況を説明してから、業務を理解しているエンジニアが処理が必要かどうか考えれば良い。
自動付与ポイント番号で付与したポイントは
ユーザーごとのポイント履歴テーブルに日付付きでポイント番号がわかる形で挿入されるため、
エンジニア側でも自動付与ポイント番号由来かどうかの処理を追うことも可能である。
自動付与ポイント番号、OEM商品テーブルに履歴テーブルが存在しており、
GUIからかバッチから変更するたびに、履歴テーブルに変更前のデータが入るようになっている。
管理サイトのログインセッションから事務のIDは取得できるので、そこから誰が操作したのかわかる。
これを管理サイトで見れるように実装した。
プログラミング言語としては画面はphp, バッチ処理はgolangで実装した。
実装としては既にDBがかなり重くなっていた事と、ビジネスロジックがDBにかなりよっている
ため、今後の仕様の変更での変更処理が煩雑化することも予想されていた。今後商品に対してなんらかのキャンペーンや、キャッシュフローの安定化のためポイントの額を変更したり、ルールが変わる可能性も高い。
よって今後のIOの問題とDBの実装に柔軟性をもたせるためにsqlで実装した。
### 成果4
オペミスが減って、ポイントの修正の仕事が減った。
事務側とOEM商品の状況との連携が簡単になった。
### 課題5
既存のポイント集計のロジックが重過ぎるのと、ポイントのロジックで正しいかどうか怪しい動きがあるので、OEMの商品のポイント付与を実装するにあたって改修。
### 取り組み5
課題2と同様に処理がおかしかったので、sqlを見直してgolangで実装した。
### 成果5
処理が早くなってすぐ終わるようになった。
### 課題6
ギガ購入(ユーザーがデータ容量が足りなかった場合に追加で通信量を買う)処理にバグがあったので
それの改修。
請求書にギガ購入の購入履歴を参照して、請求に乗せる処理の時、ギガ購入側に
請求に載せたとDB、プログラミング言語側で判定するための処理が無かった。
また、請求書がどのギガ購入のId由来の請求なのかの判定もない。
このため、ギガ購入の請求書作成のバッチ処理を回したぶんだけ多重請求がなされる。
ソースコードを読み取って、これらを今までは多重で請求されている分を個人が手動で直していたらしい事を発見したので、CTOに報告して改修することにした。
請求書のバッチ処理とGUIの処理は複雑でスパゲッティなコードになっており、
属人化された上で、書いた人がすでにプロジェクトから抜けている状態であった。
他の処理になるべく影響を与えないように改修する必要がある。
### 取り組み6
月末の請求書の処理に
なるべくプログラミング言語側に影響を与えない処理にしたい。
普通ならギガ購入と請求書の間に中間テーブルを作って実装するのだろうが、
請求書の処理がスパゲッティかつギガ購入、請求書ともにテーブル構造が酷いので、その実装だと
プログラミング言語に頼るしかなく、納期的にも、人員的にも、メンテナンス的にも不可能であると考えた。
このため技術的にはTriggerで実装するのが、一番影響が少ないだろうと考えた。
つまり以下のように実装する。
1. ギガ購入のTableに請求書へのIDを新たに導入
2. 請求書のupdate, insertかつギガ購入の請求を乗せる場合は、
ギガ購入のTableに請求書へのIDを挿入するTriggerを実装。
処理自体は非常に単純なので、このTriggerのせいで請求書の処理自体が重くなることもない。
これならば、変更をDBに閉じ込めることができるし、既存のプログラミング言語側のスパゲッティなコードの改修を少なくすることができる。
ドキュメントを書いた上で、ギガ購入のTableとTriggerにCOMMENTを残した。
### 成果6
ギガ購入のバグがなくなった。
### 課題7
督促状の自動作成。
クレジットカードや口座振替などがなんらかの理由で落ちなかった人で、
連絡がつかない人に督促状を送るためのデータ処理の自動化。
以下の仕様になる
1. 3ヶ月以上、滞納すると督促状送付
2. 督促状には3ヶ月分以上の請求書の内訳が入っている
3. 何故か滞納している分の一部のみを支払うというユーザーが一定数おり、
一部のみ支払った場合は、督促状で請求する場合は支払った分を差っ引いて欲しい。
4. 請求書と同様に各請求に対して細かく書いてほしい。
5. 毎月の決済が終わった後の毎月20〜23日発で郵送
6. 督促状の形式は決まっている。もう既に事務側はビジネスサイドによる指定通りの書式のものを作ってある。
### 取り組み7
システム上把握できないが支払っているお金は、現在管理画面から金額を手打ちして反映させる運用になっているので最終的には事務側でチェックする方が効率が良い。管理画面に打ち込む前にスプレッドシートで金額を誰がどの程度、いつ、何円払ったかのデータをリスト化しているので、それと併せてチェックする事ができるからだ。
いずれにせよ、業務上の手続きでは最終的には事務が手動で
プリントアウトした督促状を封筒に入れて送付になるので、手作業がどうしても
発生する。
システムのデータをもとにcsvからwordの文書を吐き出すこともできるはずだが、
それなりに工数が発生するため、あまり現実出来でない。
業務上の仕様と、システムで要件を突き合わせると下のように実装したらうまく行きそうだ。
1. 毎月20日に督促状郵送対象のcsvを経理、事務向けにslackのチャンネルに投げる。
2. 事務がcsvをexcelで開く。エクセルで開くためにshiftjisでcsvを作っておく。
3. 事務側でwordの差し込み文書という機能を使って、そのエクセルからwordの督促状の文書に対してデータを差し込む
4. それを印刷して送付
### 結果7
督促状の送付が簡単になった。
## 開発環境
### 仮想環境
- vagrant
- docker
### インフラ
- aws(ec2, rds)
- gcp(cloud sql, gae)
- github
### 言語
- php
- nodejs
- python
- golang
- bash
### OS
- ubuntu
- centos
- 各のクライアントPCのOS
### DB
- MariaDb
### framework
- codeigniter
- bootstrap
- express
- urfave/cli
## 規模
エンジニア10名
PM兼CTO1名