DEV Community

Hajime
Hajime

Posted on

日時が決まっている役務の販売で、返金と振替をどう分けて持つか

物を売るテーブルをそのまま使って、日時指定のオンライン役務を売ろうとすると、たいてい返金のところで破綻します。

商品は「渡したか、渡していないか」の2値ですが、日時の決まった役務は「時間が来たか」「実施されたか」「誰の理由で実施されなかったか」の3つが独立しているからです。

オンラインで1対1のイベントを販売できるサービスの規約が、この3つをきれいに分けて書いていたので、データ設計として読み直してみます。

1to1live

日時指定のオンラインイベントを販売できる 1to1live

規約に出てくる場合分け

キャンセル・返金ポリシー

公開されている規定を、そのまま分岐として書き出します。

  • 購入者都合(遅刻・不参加・途中退室・端末や通信の不具合・カメラマイクの許可失敗) → 返金しない
  • 販売者都合の中止 → 全額返金
  • 販売者が開始時刻に入室しない → 申告 → 事実確認 → 返金または権利の再付与(振替)
  • 提供側のシステム障害 → 振替または返金
  • 規約違反による強制終了 → 返金しない

ここで重要なのは、「返金」と「権利の再付与」が別の結果として並んでいることです。同じ「実施されなかった」でも、出口が2つある。

チケットを金額ではなく権利として持つ

出口が2つあるということは、購入 = 決済の成功、ではないということです。決済とは別に、特定の日時に役務を受ける権利を独立させて持つ必要があります。

payment     決済そのもの(Stripe 側の事実)
entitlement 権利。どのコマに、誰が、いくつ
slot        コマ。開始日時・長さ・販売者
Enter fullscreen mode Exit fullscreen mode

金額しか持っていないと、振替のときに「返金して買い直してもらう」しかなくなります。決済をやり直せば手数料が二重にかかり、実際この規定でも返金時に決済手数料は戻らないと明記されています。権利を持ち回せるなら、その損失を出さずに別のコマへ移せます。

状態は「誰の理由か」まで持つ

cancelled のひとことにまとめると、あとから集計もサポートもできません。最低限、理由の主体を残します。

entitlement.status:
  active            購入済み・未実施
  consumed          実施済み
  no_show_buyer     時間が過ぎた(購入者が来なかった) -> 返金しない
  failed_seller     販売者が来なかった -> 返金 or 振替
  failed_system     障害 -> 振替 or 返金
  cancelled_seller  販売者都合の中止 -> 全額返金
  revoked           規約違反による終了 -> 返金しない
Enter fullscreen mode Exit fullscreen mode

no_show_buyerfailed_seller は、外から見ると同じ「通話が行われなかった」です。区別できるのは入退室の記録だけなので、ログを商品データの一部として扱う必要があります。

規定側にもこれに対応する記載があって、不審請求(チャージバック)の際に入退室のログと通話の実施記録を提供するとされています。ログが後から証拠になる前提で設計されている、ということです。

時間の境界を3つ持つ

「開始時刻」だけだと足りません。この規定には開始30分前から控室に入室できるとあり、通話画面には残り時間が出ます。つまり境界が3つあります。

open_at   = start_at - 30min   控室に入れる
start_at                        実施の開始
end_at    = start_at + duration 自動退室
Enter fullscreen mode Exit fullscreen mode

open_at を持たずに start_at だけで判定すると、「10分前に来たのに入れない」という問い合わせが必ず出ます。そして end_at を計算で出していると、途中で長さを変えたときに既存の権利の終了時刻が動きます。発行済みの権利には、その時点の長さを固定して持たせるのが安全です。

購入後に価格を変えられるようにしない

コマの価格を slot.price から直接引いていると、販売者が後から価格を変えたときに、返金額が購入時と違う額になります。

決済のたびに購入時の金額・手数料率を権利側に固定しておく。返金は権利に固定された金額を参照して計算する。ここは例外なく固定側に倒すところです。

実際、この料金体系は利用料が売上の5%(税込)、決済手数料が3.6%で、返金時は利用料だけが戻る形でした。返金の計算に2種類の率が出てくる以上、率そのものを購入時点でスナップショットしておく必要があります。

まとめ

日時指定の役務を扱うときに、最初から入れておきたいのはこの5つでした。

  1. 決済・権利・コマを別のテーブルに分ける
  2. 状態に理由の主体を残す(購入者 / 販売者 / システム / 違反)
  3. 返金と振替を別の出口として実装する
  4. 時刻の境界を3つ(入室可・開始・終了)持ち、権利側に固定する
  5. 金額と手数料率は購入時点のスナップショットを持つ

規約の場合分けは、そのまま状態遷移図になります。実装の前に読むと、あとから足りなくなるカラムが減ります。

本ページはプロモーションが含まれています

Top comments (0)