事業者請求払い: 多様な決済を支える決済基盤の仕組み

この記事は Merpay & Mercoin Advent Calendar 2024 の記事です。

メルペイの Payment Core チームでバックエンドエンジニアをしている komatsu です。
普段はメルカリグループのさまざまなプロダクトに共通した決済機能を提供するための決済基盤の開発・運用をしています。
この記事では、私たちが直近開発した新しい決済手段であり、今年リリースされたスキマバイトサービス「メルカリ ハロ」や現在試験運用中のサービス (以降サービス A とします) に利用されている、“事業者請求払い” について紹介します。

事業者請求払いとは

メルカリグループが提供するプロダクトは個人のお客さまに支えられていますが、同時にメルカリShops やメルペイなど、多くのパートナー企業 (加盟店、事業者) によっても支えられています。
そのため、個人のお客さまに提供する残高やあと払いといった決済スキーム以外にも、加盟店との資金の流れをシステムで管理するさまざまなユースケースが存在します。
メルカリとパートナー企業間における資金の流れは大きく分けて 2 種類あります。

  • メルカリ -> パートナー企業
    • メルペイ加盟店の売上から手数料などを差し引いた金額を、その加盟店に振り込むための資金の動きです。内部的には、締め日に応じて売上金の精算をし、メルカリが各加盟店に銀行振込をすることで実現されています。
  • パートナー企業 -> メルカリ
    • 加盟店の売上をメルカリに移動することで、toB のサービス提供する場合の資金の動きです。例えばメルカリ ハロでは、お客さまが求人に応募をし、お仕事をすることでパートナー企業から給与が支払われます。ただし、パートナー企業から直接お客さまに支払われるわけではなく、メルカリ ハロを通してお客さまに給与の支払いが行われます。これはアルバイト完了後にすぐ給料が支払われる体験を提供するためや、パートナー企業から手数料を受け取ることを実現するためのものです。他にも試験運用中のサービス A では、加盟店がメルカリに代金を支払うことで、メルカリが加盟店に対してサービスを提供します。

このような、パートナー企業からメルカリに資金が移動する場合に利用されるのが事業者請求払いです。
事業者請求払いでは、次のような手順でサービスの提供が行われます。

メルカリはメルカリ ハロのようなプロダクトを、Payment Platform はメルカリグループにおけるあらゆる資金の移動に利用されている決済基盤を、加盟店は各プロダクトがサービスを提供しているパートナー企業を指します。

  1. 加盟店の与信審査依頼
    事業者請求払いはサービスを提供してからその金額に応じた支払いを加盟店に請求するため、クレジットカードのようなあと払いの決済スキームです。
    そのため、実際にサービスの提供を行う前に、貸し倒れリスクなどを考慮して各加盟店が支払い可能な金額分のサービスを提供する必要があります [1](現在加盟店の与信審査が必要な場合、私たちのチームから外部の会社に API 経由で依頼して実現しています)。

  2. 審査結果に基づくサービスの提供
    与信審査が完了したら、サービスを提供できる状態になります。
    メルカリ ハロであれば与信枠内で求人募集ができるようになります。
    決済基盤の観点では、このタイミングで利用分を与信総額から都度減らし、利用分は未回収金の債権として管理します。

  3. 請求
    月末などプロダクトが定める締め日をもって請求金額が計算され、加盟店にメルカリに返済するための請求書が送付されます。
    請求書には支払う金額のほか、支払先の銀行口座やインボイス制度に基づく明細などが記載されます。

  4. 請求金額の支払い
    加盟店は請求書に記載された金額を、支払期限までに支払います。
    決済基盤は入金の通知を受け、債権の消し込みを行います。

ではなぜこのようなスキームが必要なのでしょうか?
事業者請求払いを利用しない最も簡単な方法は、サービス利用時に必要な金額をメルカリに入金することです。
ですが、これには事業者請求払いで解決できる、いくつかの問題点があります。

  • 支払いが同月中に何度も発生するため、加盟店もメルカリも振込に関する管理が複雑化する。
    • 振込入金に関するオペレーションは手動で行われることが多く、煩雑になります。
    • 事業者請求払いでは月に 1 回程度の作業になるため、加盟店としてもメルカリとしても作業が簡易化されます。
  • 加盟店のキャッシュフローが悪化する
    • サービス利用時に入金する場合、加盟店の売上が立つ前に支払いを行うことになり、手持ちのキャッシュを利用する必要があります。
    • 事業者請求払いではサービス提供の翌月以降に請求をすることで、売上やサービス利用による利益を返済に利用することができます。
  • 適切な金額の請求ができないことがある
    • 例えばメルカリ ハロの場合、残業が発生する場合など、サービス提供時 (= アルバイト募集の掲載時) と実際に加盟店が支払う金額には差分が生じることがあるため、前払いの形式では正しい金額を受け取ることが不可能です。
    • 事業者請求払いではサービス提供後に請求が行われるため、実際のサービス利用料を算出したうえで正しい金額を請求することができます。

決済基盤の API 設計

事業者請求払いの概要を説明したところで、私たち Payment Core チームが既存の決済基盤にこの機能を追加するときの設計について紹介します。

パートナー間の決済を表現する PartnerTransfer

決済基盤マイクロサービスである Payment Service はさまざまな資金の動きや決済手段をサポートする API を持っています。
例えば、メルカリで商品を売買する場合、Escrow と呼ばれる API を利用して、買い手の残高やあと払いの枠、クレジットカードといった決済手段を消費し、その分のメルペイ残高を売り手に付与したり、手数料をメルカリ自身の売上として計上します。
他にもコード決済等で購入者から加盟店に資金を移動するための Charge や、キャンペーンのポイント付与等でメルカリからお客さまにポイントを扶養するための Transfer といった API があります。
Payment Service を利用するマイクロサービス、つまりプロダクト側のマイクロサービスはこれらの API を必要に応じて組み合わせながら、さまざまな決済体験をお客さまに提供します。

そして、さまざまある API の中で、パートナー間での資金の流れを表現する PartnerTransfer API があります。
ここでいうパートナーはコード決済を導入している加盟店や、メルカリShops に出店している加盟店、メルカリ ハロに求人を掲載している事業者などが含まれます。
それと同時に、メルカリグループの売上を管理するために、メルカリやメルペイ自身も含まれます。

既存の PartnerTransfer API のユースケースには以下のようなものがあります。

  • メルペイコード決済における加盟店の売上を加算する
    • 決済には必ず原資があるため、PartnerTransfer 内部では、メルペイ自身の売上金を減らし、加盟店の売上金に追加する、といった処理が行われます。ここで、売上金に加算ということは、決済基盤が持っている加盟店残高管理用のマイクロサービスである Balance Service 上で加盟店の売上金 2 が増加することであり、実際に加盟店に振り込まれているわけではない状態です。プロダクトによって定められている精算のタイミングで売上金を加盟店の銀行口座に振り込むことで、現実世界で金銭が移動します。
  • 加盟店の売上からメルカリShops の手数料を差し引く
    • メルカリShops では売り手である加盟店から手数料を徴収するビジネスモデルです。そのため、手数料分を加盟店の売上からメルカリに移動する処理が必要となり、PartnerTransfer によって実行されます。

事業者請求払いは原資が加盟店の売上ではなく与信枠という違いはありますが、資金の流れは加盟店からメルカリ自身という点で、PartnerTransfer が想定するユースケースに当てはまります。
そのため、PartnerTransfer がサポートする 1 つの決済手段として事業者請求払いを組み込むことにしました。

与信管理や精算を柔軟にする設計

メルカリグループにはさまざまなプロダクトがあり、それぞれのプロダクトで千差万別の要件があります。
決済基盤チームはプロダクトの要望を受け入れつつも、なるべく一般化し、ロバストな設計を保つ必要があります。

グループ内のプロダクトごとに、それぞれが抱える加盟店の特徴や、決済基盤として求められるものが異なるケースがあります。例えば、メルカリ ハロではより多くの事業者がアルバイトを募集できるようにする一方で、サービス A のように与信情報がすでに分かっており、社会的な信用のある特定の事業者に対してのみ機能を提供する場合もあります。
言い換えれば、前者では大量の事業者に対して適切な与信審査をする必要があり、後者は審査をせずに大きな金額を与信として与えることができます。

そのため、私たち決済基盤では 2 通りの事業者請求払いのフローを構築しました。
1 つ目が外部の与信審査や請求を行う企業をバックエンドとして利用するパターンです。
メルカリではメルペイのあと払いなどで利用されている個人向けの与信管理の仕組みはありますが、パートナー向けのものはありませんでした。
そのため、企業与信の管理を行っている企業の API に別のマイクロサービス [3](Payment Service の責務は決済のトランザクション管理や価値交換のためのインターフェースの提供であり、外部サービスとの接続は、Payment Provider という別のマイクロサービスを開発して責務の分割をしています。Payment Service が Payment Provider を gRPC で呼び、Payment Provider が外部サービスを HTTP などのプロトコルで呼ぶようなフローになります。) を介して繋ぎ込みを行いました。
2 つ目は外部サービスを利用せず、メルペイが持つ既存の精算管理の仕組みを利用したパターンです。
こちらは企業与信を審査することはできないかわりに、精算や請求、入金確認までをすべてメルペイ内のシステムで完結して提供します。

これらの決済基盤の裏側の仕組みはユースケースに応じて選択できるものなので、API のインターフェースはなるべく一般化し、パラメータ 1 つで切り替えられる仕組みが必要でした。
そのため、Payment Service の RPC は以下のようになります。

// payment.proto
service PaymentService {
  rpc CreatePartnerTransfer(CreatePartnerTransferRequest) returns (CreatePartnerTransferResponse) {}
  rpc CapturePartnerTransfer(CapturePartnerTransferRequest) returns (CapturePartnerTransferResponse) {}
  rpc CancelPartnerTransfer(CancelPartnerTransferRequest) returns (CancelPartnerTransferResponse) {}
}

message CreatePartnerTransferRequest {
  // 資金の移動元のパートナー
  uint64 from_partner_id = 1;
  // 資金の移動先のパートナー
  uint64 to_partner_id = 2;
  // from_partner_id が利用する決済手段
  PaymentMethod payment_method = 3;
}

message CapturePartnerTransferRequest {
  string partner_trasnfer_id = 1;
}

message CancelPartnerTransferRequest {
  string partner_trasnfer_id = 1;
}

message PaymentMethod {
  enum Type {
    // Balance Service が管理するパートナーの売上金を利用する決済
    PARTNER_SALES = 1;
    // 事業者請求払いの与信を利用する決済
    PARTNER_INVOICE = 2;
  }

  PaymentMethod.Type type = 1;
  oneof details {
    PaymentMethodPartnerSales partner_sales = 1;
    PaymentMethodPartnerInvoice partner_invoice = 2;
  }
}

message PaymentMethodPartnerSales {
  uint64 amount = 1;
}

message PaymentMethodPartnerInvoice {
  // 請求明細の項目を表現する message
  message Detail {
    enum TaxType {
      UNKNOWN       = 0;
      EIGHT_PERCENT = 1;
      TEN_PERCENT   = 2;
      ANY           = 3;
    }

    // 項目の名称
    string name = 1;
    // 単価
    int64 price = 2;
    // 量
    int64 quantity = 3;
    // 税区分
    TaxType tax_type = 4;
  }

  enum InvoicePaymentProvider {
    INVOICE_PAYMENT_PROVIDER_UNKNOWN = 0;
    INVOICE_PAYMENT_PROVIDER_XXX = 1; // 外部サービスを利用した事業者請求払い (XXX は仮の名前)
    INVOICE_PAYMENT_PROVIDER_INHOUSE = 2; // 内製の事業者請求払い
  }

  InvoicePaymentProvider invoice_payment_provider = 1;
  repeated Detail details = 2;
}

ここで、CreatePartnerTransfer, CapturePartnerTransfer, CancelPartnerTransfer はそれぞれ PartnerTransfer における決済のオーソリ、キャプチャ、キャンセルを表現します。
CreatePartnerTransfer は PaymentMethod を引数に取り、パートナーの残高を消費する決済手段か、事業者請求払いによる与信枠を消費する決済手段化かを選択できます。
事業者請求払いの場合、PaymentMethodPartnerInvoice によって各明細の単価や量、インボイス制度に対応する税区分などを入力できます。
InvoicePaymentProvider によって、バックエンドで利用する事業者請求払いのプロバイダ (外部サービスなのか、メルペイ内製のものなのか) を切り替えることができます。
これによって、Payment Service を利用するプロダクト側はバックエンドのシステムをあまり知らない状態で、ユースケースに応じてパラメータを切り替えるだけで事業者請求払いの機能を一貫して利用することができます [4](実際には各バックエンドに依存する API なども存在しますが、決済のタイミングではこのフィールド以外を意識する必要がありません)。

決済の整合性担保

さまざまなマイクロサービスや外部サービスを跨いだ決済スキームである以上、整合性の担保が重要になります。
特に外部サービスとの突合は重要であり、プロダクトローンチ時から厳密な仕組みづくりが必要でした。
決済ごとの与信審査があるため、決済ステータスのライフサイクルは以下のようになります。

外部サービスとは毎日一度、前日のすべての取引の状態が CSV ファイルとして SFTP サーバ経由で送られてきます。
外部サービスに接続しているマイクロサービスである Payment Provider では、その時刻になったら CSV ファイルを取得し、決済基盤が持っている決済ステータスと差分がないかを突合します。
一見簡単にみえるこの処理において難しい点は、決済基盤が持っているデータは最新のものであるのに対し、CSV ファイルに含まれるのはあくまで前日終了時点でのステータスであるという点です。
例えば、

  1. 12/19 23:55 CreatePartnerTransfer によって外部サービスを利用した決済が発生し、オーソリが完了
  2. 12/20 00:05 CapturePartnerTransfer によって決済のキャプチャが完了
  3. 12/20 06:00 12/19 分の取引データが連携 (status: authorized)
  4. 12/20 07:00 12/19 分の突合処理を実行

このような時系列の場合、CSV に含まれるデータでは最後のステータスは authorized であるのに対し、私たちの決済基盤では captured になります。
これらを考慮するために、上記の状態遷移をコードで表現し、ステータスに差分が会ったとしても移り得るものなのかを判断し、柔軟に突合する仕組みを作りました。

一方で、1 日以上経ってもステータスが同じにならない場合、それは不整合として検知する必要があります。
例えば、

  1. 12/19 23:55 CreatePartnerTransfer によって外部サービスを利用した決済が発生し、オーソリが完了
  2. 12/20 00:05 CapturePartnerTransfer によって決済のキャプチャが完了
  3. 12/20 06:00 12/19 分の取引データが連携 (status: authorized)
  4. 12/20 07:00 12/19 分の突合処理を実行
  5. 12/21 06:00 12/20 分の取引データが連携 (status: authorized)
  6. 12/21 07:00 12/20 分の突合処理を実行

この例の場合、5 では取引データは captured になっていることを期待していますが authorized のままになっています。
状態遷移のみを考慮した場合では authorized から captured への遷移は想定されるため不整合と判別できません。
そのため、前回突合された際の決済ステータスを考慮に入れることで、より正確な整合状態を判別するようにしています。

このような突合の仕組みを利用して、より安定した決済基盤としての機能をプロダクトチームに提供しています。

おわりに

この記事では、メルカリ ハロをはじめとするメルカリグループが近年注力しているプロダクトにおけるパートナーとの決済手段である、事業者請求払いについて解説しました。
実際にはもっと泥臭い処理が多く存在しており、さまざまなチームやマイクロサービスが関わって全体のフローが構成されていますが、今回は主に与信を利用した決済の部分にフォーカスをしました。
事業者請求払いによる決済スキームは、”あらゆる価値を循環させ、あらゆる人の可能性を広げる“ というメルカリグループのミッションを実現するうえで重要な役割を担っており、Payment Core チームは今後もこのような決済基盤の開発を通じて多くのプロダクトに貢献していきます。

次の記事は abcdefuji さんです。引き続きお楽しみください。

  • X
  • Facebook
  • linkedin
  • このエントリーをはてなブックマークに追加