朝日ネット 技術者ブログ

朝日ネットのエンジニアによるリレーブログ。今、自分が一番気になるテーマで書きます。

コンビニ払いを導入して1年ほど経ちました

開発部の 8luka です。

当社ISPサービスASAHIネットの個人のお客様にコンビニ払込用紙での支払方法(以下、コンビニ払いと記載します)を導入して一年ほど経ちました。 なぜ導入したのか、どう導入したのか、導入してどうなったのか、そんな話をこの件の開発を諸々担当した立場からお伝えします。

目的の話

誰が・いつ・コンビニ払い?

個人のお客様(以降「お客様」と表記します)には、ASAHIネットご利用料金(以降「利用料金」と表記します)をクレジットカードでお支払いいただいています*1が、様々な理由でその支払方法が利用できない・できなくなってしまう場合があります。 そのような際もサービスを引き続き利用いただくための一時的な手段として、コンビニ払いを用意しています。

それでもお支払いいただけなかったら?

一定期間利用料金のお支払いがない場合は、お客様に提供しているサービスを停止させていただいたり、ASAHIネット会員を退会いただくことになります。 それらの際の処理の対象をスムーズに抽出することも、コンビニ払い(を管理するシステム)の重要な役割です。 他の請求手段があるならその方法に振り替えをするまでが役割と割り切れるのですが、コンビニ払いからの振替先は存在しないので、自力でなんとかしなければならないのです。

コンビニ払いの導入前はどうなってたの?

コンビニ払いの導入前は、郵便局(ゆうちょ銀行)での払込によりお支払いいただいていたのですが、いくつかの課題がありました。

まずはお客様の利便性に関わる課題として:

  • お支払いいただける時間帯が限られています。
  • お支払いいただける場所がゆうちょ銀行ATMに限られ、ほとんどの地域で(コンビニに比べると)レアです。

さらに、オペレーションコストに関わる課題として:

  • 入金データの取り出しのためにWebブラウザでゆうちょ銀行のサイトから毎日ダウンロードし、さらにデータを目視確認する必要がありました。*2
  • 毎月の請求で多数のお客様へ払込用紙を一括して送付する際、古いシステムを経由してデータを作成し、メールで業者へ手動連絡して委託する必要がありました。
  • 請求書が届かなかったり紛失してしまったお客様へ再送付する際にも同様のシステムで社内印刷を行っていた都合で、再発行の受付をするカスタマーサポート担当者と、実際に再発行を行う事務担当者との間で引継業務がありました。
  • 滞納されてしまった際のサービス停止や退会対象者を特定する際、滞納情報は経理部門から、サービス利用情報はインフラ部門からそれぞれ取得しとりまとめる必要がありました。しかも、取得タイミングのずれによる不整合などを目視で確認し解決した上でこれらの業務に着手する必要もありました。

それをどうして変えようとしたの?

これらの課題を抱えた状態で長年運用されていましたが、 この支払方法での請求件数が増える*3と業務が破綻するおそれが見えてきました。 そこで、 請求件数の増加に対して運用負荷があまり増えないシステムへ移行 することでの解決の道を探ることにしました。

それで、導入できたの?

コンビニ払いの導入に向けて動き始めることになったのが 2020年10月のこと。実際にコンビニ払いでの請求を初めて実施したのが2021年10月のこと。 その1年間、筆者は要求の分析、仕様の提案、DBやプログラムの設計、コーディング、デプロイに携わりました。 いろいろな困りごとを前向きに解決する時間をもらえて、とても良い経験になりました。

以下は、システム上の位置付けや、技術的な工夫などに焦点をあてて紹介していきます。

実現方法の話

利用料金の計算から債権データの生成まで

当社のサービスには各種インターネット接続から固定IPアドレス等のオプション等バリエーションがありますが、これらの利用料金の計算と、その支払方法・入金管理は独立した設計になっています。

というのも、「このサービスを使ってる人は(orだけが)この支払方法を利用してほしい」「特定の支払方法を利用開始するために提携他社と取り決めた個別の手続きが必要」といった提携他社との約束や、クレジットカードでのお支払いをお勧めしている当社の事情はあれど、いざ請求を行う段では前段がどうであったかを忘れられるほうがシンプルですし、支払方法が変わったときにも一貫して取り扱うことができると便利だからです。

この疎結合性・直交性を得るため、毎月計算した利用料金は一旦「債権DB」へ積んでおき、各種請求手段のシステムはこの「債権DB」を参照・更新することで「請求中」「入金済」「未入金」等の状態を管理しつつ、実際の請求行為(払込用紙の送付依頼や、請求金額を提携業者へ送信する等)を行う設計としています。

今回のコンビニ払いの導入にあたっても他の支払方法同様このフレームワークに載せています。多少の拡張は行いましたが、骨格はそのまま、債権回収の方法を追加した、ということになります。

利用料金・債権DB・各種支払方法の関係

簡単に図示してみました。実物からはデフォルメされていますが、今回の記事でお伝えしたい範囲の概念はカバーしているつもりです。

払込用紙の送付

コンビニ払いで「請求する」というのは、つまるところ払込用紙の送付です。 今回は印刷を業者に頼んでいますし、その業者が郵便局出しまでやってくれます。 データ連携の仕様や書面のレイアウトを決めたり払込用紙の体裁が条件を満たしているか試験する等、 導入にあたってはいくつかの調整が発生しますが、 最終的なシステム動作としては「印刷業者に依頼する(データを送る)」ができればひとまず完了です。 そのための一連の流れを以下に図示します。

印刷・発送とその前後の流れ

コンビニ払いで請求したい債権相当を「コンビニ請求行為管理DB」に登録(レコード作成)するのがスタートで、あとはよしなにながれていきます。

印刷リクエストの冪等性の確保

技術的に少しだけ面白いテーマとして、印刷依頼管理をWeb API提供としている点が挙げられます。 今回は払込用紙の印刷・送付が目的ですが、他の種類の印刷物を連携したくなるかもしれません。そういうとき、APIの機能が印刷連携に閉じていて他のことは一切やらない・内部実装を直接使われてもいないことが傍目に明らか(なので、API互換性に気をつけつつ拡張するのが容易)なのがこの設計の利点といえます。*4

話を戻します。印刷依頼を行うにあたり、DBで完結している箇所はトランザクションで整合性を担保する手段をとれますが、 Web APIに切り出された部分では注意が必要です。 というのも、処理が途中でエラー終了してしまったときのリカバリでは難しい判断なしにリトライしたいのですが、APIサーバがリクエストを受け付けたにも関わらずレスポンスの受け取りや記録に失敗しているケースでもう一度リクエストしてしまうと、重複してしまいます。 払込用紙を重複して送付すると費用が余分にかかりますし、何よりお客様の混乱を招きます。 同一の書面は、他の事情が無い限り、ただ一度だけ送る ための工夫が必要なのです。

そこで、まず重複実行してもシステム外への影響がない「印刷管理番号を払い出しDBへ格納してコミット」まで済ませた上で、この印刷管理番号を渡してシステム外への影響のある「本当に印刷をリクエストする」二段階の構成としました。APIサーバ側で印刷管理番号を確認すれば重複かどうかの区別がつくので、前述の要件を満たせます。*5

以上を踏まえて実装した「未印刷分を印刷依頼(スプール)する、何らかの理由で中断している場合もその詳細を気にせず再実行できる」処理を以下の擬似コードで紹介します。

for レコード in スプール日時が空のレコード一覧(DB)
    if レコード.印刷管理番号 が空
        レコード.印刷管理番号 := データ形式チェックする(API_SERVER, レコード)
        update(DB, レコード)
        commit(DB)
        レコード.スプール日時 := スプールする(API_SERVER, レコード)
        update(DB, レコード)
        commit(DB)
    else
        レコード.スプール日時 := スプール済み?(API_SERVER, レコード)
        if レコード.スプール日時 が空
            レコード.スプール日時 := スプールする(API_SERVER, レコード)
        update(DB, レコード)
        commit(DB)
送付できない・できなかったケースの対応

このようにしてお客様へ送付した払込用紙が残念ながら届かないケースがあり、戻り郵便として当社が受け取ります。このとき、このまま新しい郵便物を同じ住所へ送り続けても意味がないので、次回以降の発送を行わないように顧客DBにフラグを立てます。 次回以降の送付依頼処理ではこのフラグを参照して、払込用紙を送付することができない一覧を業務部門へデータ提供し、お客様へのコンタクトを試みることになっています。

再発行

そのような経緯を経て無事コンタクトできたお客様の新しい住所へ払込用紙を再送付したり、紛失等の場合には再発行を行うことがあり、そのための機能(業務UI)も提供しました。

誤操作を防ぐための工夫・要望の取り入れなどもありましたが、 内部的にはほぼレコードを複製して次回の印刷依頼に回すだけの作りにしています。

入金データと消込

お客様にコンビニでお支払いいただけたら「請求中の金額が入金済みになった」ということです。 これを債権DBへ反映する処理を消込と呼んでいます。*6

入金データの取り扱いについても、前述の印刷管理の話と同様にWeb API提供としています。 入金管理APIサーバに入金状況を定期的に問い合わせて(ポーリング)、消込処理を都度行っていく設計です。*7

入金・消込の流れ

入金・消込についても、なるべく人手なしでたくさん捌く仕組みになっている必要があります。 前述の請求・払込用紙の送付がスムーズになっていることと、どちらが欠けてもダメで 両方そろって初めて運用がスケールします。

入金と請求の紐付けの知識のありか

ところで、前述した再発行や後述する滞納対応(督促)で、同じ請求に対応する払込用紙を複数回送付することがあります。 入金の有無の確認の観点から言えば、入金データが「どの請求のものかどうか」が大事で「どの払込用紙のものか」にはあまり興味はありません。 しかし、業者から届く入金データはあくまで「どの払込用紙のものか」です。 請求と払込用紙の紐付けは払込用紙の印刷依頼を行った側のDBには持っていますが、業者との連携部分にまでその知識を持たせることはしたくありません。*8 そこで、入金管理APIサーバへ問い合わせる際に、同一請求に対応する払込用紙(の印刷管理番号)をグループ化したクエリを発行し、レスポンスもグループ化するという手段をとることにしました。*9

入金情報の取り出しと消込の詳細

あとは、興味のある請求分についてひたすら問い合わせるだけです。 ただし、リクエストにはオーバーヘッドがあるので回数が増えすぎると際限なく時間がかかりますし、1リクエストが大きすぎても上手く処理できません。 問題なく処理できそうな個数に分割して*10、その単位でのリクエストを繰り返すことにしました。

滞納対応

一定期間お支払いのないお客様へはサービスを停止させていただく予告とともにお支払いのご案内をしたいので、その書面を送ります。このことを督促と呼んでいます。 督促の書面送付にあたっても、前述「払込用紙の送付」のデータフローそのまま、 コンビニ請求行為管理DBのレコードを作成します。 書面の種類を指定するパラメータ等にいくつか差異がある程度で、あとは同じです。

督促してもお支払いのないお客様に対しては本当にサービスを停止させていだたいたり、退会いただくことになります。これらを行う業務部門が「誰が対象なのか・何をすればいいのか」が分かるように一覧の表示と最低限の状態管理を提供しています。 もしお支払いいただけたらそれ以上の督促は不要ですし、可能であればサービス再開を手配させていただく必要があり、これらについても同様です。

滞納対応の流れ

ここで重要なのは、いずれもコンビニ入金の有無を確認するのではなく、債権DBの消込状態を参照する必要があるということです。前述「入金データと消込」で「1回のコンビニ請求に複数の払込用紙を発行していたら、どの用紙の入金のものであっても区別しない」という趣旨のことを述べたように、ここでは「コンビニ払いで請求したけれど、もし他の方法での消込があるなら*11それを参照すべきだ」という原則に従っています。要するに、どんな方法であってもお支払いいただけたら、もう滞納ではないのです。

仕様を決めるにあたり業務部門と多くのディスカッションを行い、何とか形になりました。

効果の話

リリースまでに拾いきれなかった要望もありましたが、大きなブーイングもなさそうなので、それなりにうまくいったんだと思っています。

「便利になった!」と明示的に言ってくださる方の声を聞く機会は多くはないのですが*12、請求と収納の関係や回収率などのデータが出しやすくなったこともあり、経理部門からは喜んでもらえているみたいです。

その回収率も(具体的な数字はここでは伏せますが)コンビニ払い導入後に上がっているので、お客様にも価値が届けられていたらいいなあと思います。

採用情報

朝日ネットでは新卒採用・キャリア採用を行っております。

新卒採用 キャリア採用|株式会社朝日ネット

*1:ご利用サービスによってはクレジットカード以外の支払方法が利用できる場合があります。

*2:ゆうちょ銀行の入金データの大半はテキストデータで取得し問題なく取り扱えますが、読み取りエラーで画像として届く場合があり、これを証跡として確保したりテキストに起こす業務がありました。

*3:具体的なシチュエーションとして、クレジットカード決済処理がNGとなった分を全て払込用紙送付した場合、特に初月は例月の数倍~10倍の送付件数に膨れ上がる見込みがありました。関連して、実額オーソリ導入についても別の機会でお伝えできたらうれしいです。

*4:同じ業者へ他の機能の連携を目的にAPIサーバが既に構築・運用されていたので、今回はなるべく自然な形でそこへ相乗りしてコンビニ払いのための機能拡張する選択をしています。このあと述べるようにAPIサービスは整合性に気を遣うので、その形が必要な明確な理由がある場合に採用する設計なのだと思っています。

*5:この設計にたどり着くまで相当悩んだのですが、世の中的には「冪等キー」という概念で理解されていると最近知りました。

*6:消込を行うことで「支払い済みなのでこれ以上の督促行為は不要である」「入金済みとして会計上扱うことができるようになる」という効果があります。請求のゴールの一つで、ここまで来れば一安心です。

*7:入金イベントを拾って即時反映できるとかっこいいのですが、取りこぼしたときのリカバリやイベントが短時間に集中したときの性能の確保など安定運用のために考慮すべきことが多い割に、それで得られるものが見合わない感じがしたので、対応の優先度を下げて、リランによるリカバリが容易なポーリングにしました。

*8:コンビニ払い以外のことにも使う印刷サービスAPIになる可能性を保つために、特定の支払方法に依存した使い方の制約が入ってしまう余地をなるべく排除したいのです。

*9:グループ化しないままリクエストし、クライアント側でレスポンスをグループ化する、としたほうが一見シンプルなのですが、レスポンスの入金データが0件のとき「単にリクエストにいなかったのか」「入金がなかったのか」を取り違えると大変なことになるので、必ず入金確認の意味のある請求行為の単位でまとめてからリクエストしてほしいし、レスンポンスもそのようにするという意思を込めてこの設計にしています。

*10:正確な個数はここでは伏せますが、たとえば1回の問い合わせに含まれる請求が1件でも100件でもAPI応答速度がほぼ変わらないなら、100件ごとにまとめることで全体の速度は100倍近くになります。このチューニングを行いました。

*11:他の請求手段が使えないときのコンビニ払いなのに、他の方法での入金なんかあるの?と疑問に思った方は鋭いです。具体例は控えますが、さまざまな業務都合をシステムへ反映する余地を残しておく必要もあります。

*12:「これで当たり前」「なんで今までこうしてくれなかったんだ」くらいに思ってもらえたら御の字です。さすがに面と向かって言われたら堪えそうですけれど、それでも「やらない方がよかった」って言われてしまうよりはずっとずっとよいです。