Chapter 05

Refunds and Disputes

Toru Furukawa
Toru Furukawa
2021.12.13に更新

返金(Refunds) と、不審請求の申立(Disputes) は、すでに完了している決済による資金を、もとのカードホルダーに返す処理だ。順番に見ていく。

返金 / Refunds

完了した支払い金額の全額または一部を、カードホルダーである購入者に返すことを「返金」「Refund」と呼ぶ。

EC サイトで購入者が「注文をキャンセル」ボタンをクリックしたので、販売者が refund する。サービスを提供できない事情(疫病が流行ったのでツアーが中止とか)が発生したので、販売者が refund する。いろんな状況があるけど、refund を実行するのは販売者である。

Account に保有していた資金を動かす、またもや Balance Transaction が出てくる。

Buyer                                                  Seller
Card                                                   Bank
  :
Payment Method -> [Charge] -> [Balance Transaction] -> Account
  ^                  :                |                   |
  |            [Payment Intent]       v fee               |
  |                  :              Stripe                |
  |                  :                                    |
  +------------- [Refund] <- [Balance Transaction] <------+
                     👆               👆

API 呼び出しは、以下のようにする。

request
curl https://api.stripe.com/v1/refunds \
  -u sk_xxxx: \
  -d payment_intent=pi_xxxx \
  -d amount=2222 \
  -d "expand[]"=balance_transaction
response
{
  "id": "re_xxxx",
  "amount": 2222,
  "balance_transaction": {
    "id": "txn_xxxx",
    "amount": -2222,
    "fee": 0,
    "net": -2222,
    ...
  },
  ...
}

返金自体に手数料はかからないけれど、Stripe の手数料は返ってこない。どういうこと?って思われそうだが、そこで俺たちの Balance Transaction である。

id source amount fee net
txn_1 payment +1,000 -36 +964
txn_2 refund -1,000 0 -1,000
total 0 -36 -36

txn_1 は Payment で発生した Balance Transaction だ。1,000 円の支払いから、3.6% = 36 円手数料が引かれて、946 円残高が増加した。

そこで 1,000 円全額返金をしたのが txn_2 だ。手数料がかからないので fee は 0。残高から 1,000 円減って、カードホルダーに戻っていった。さよなら。

payment の時点で手数料が発生し、refund では手数料は発生しないが還元もない。だから、トータルで見たときに手数料ぶんの 36 円は Account から減る。

返金には応じない、というストロングスタイルは理論的には可能だけれど、次に書くチャージバックによって似たような資金フローが発生する。

不審請求 / Disputes

クレジットカードの利用明細を見て「え、なんだこれ? スキミングされたのか?!」って思って、カード発行会社に問い合わせて、「デジタル動画コンテンツの会員登録のようですね。何かご質問は?」「いえ」って会話をしたことがあるだろうか。私はある。

いえ、ではなく、自信をもって不当な決済だと主張できることはある。そして、その主張が真っ当であると判断されれば、カード発行会社はアクションを起こしてくれる。これが dispute だ。

dispute のひとつの形態が「照会」だ。「カードホルダーが何だこれって言ってるすけど、どういうことっすか?」的なコミュニケーションを取ってくるので、「いやーこれちゃんと同意して買ってもらったんすよ」と証拠を出せば解決する。だめなら、チャージバックになる。

照会を経てチャージバックになることもあるし、いきなりチャージバックになることもある。これは、カード発行会社が「その決済はナシ。決済が妥当なら、反証資料を出して」ってことである。このときは、強制的に、資金が移動される。Balance Transaction もある。さらに手数料がかかる。

Buyer                                                  Seller
Card                                                   Bank
  :
Payment Method -> [Charge] -> [Balance Transaction] -> Account
  ^                  :                |                   |
  |            [Payment Intent]       v fee               |
  |                  :              Stripe                |
  |                  :                ^ fee               |
  |                  :                |                   |
  +------------ [Dispute] <- [Balance Transaction] <------+
                     👆               👆

refund とは違って、dispute はカード発行会社がトリガーすることに注意されたい。オンラインサービスの外側で、強制的に発生する。だから webhook で検出するなどの対策が必要になる。ともあれ、ここでは資金の動きに集中しよう。

Stripe の test mode にある、チャージバックが起こるテストカード番号を使って決済する。

request
curl https://api.stripe.com/v1/payment_intents \
  -u sk_xxxx: \
  -d amount=1000 \
  -d currency=jpy \
  -d payment_method=pm_card_createDispute \
  -d confirm=true \
  -d "expand[]"="charges.data.balance_transaction"
response
{
  "id": "pi_xxxx",
  "charges": {
    "data": [
      {
        "id": "ch_xxxx",
        "balance_transaction": {
          "id": "txn_1",
          "amount": 1000,
          "fee": 36,
          "net": 964,
          ...

呼び出し直後に dispute が発生しているはずなので、取得してみよう。

request
curl https://api.stripe.com/v1/disputes \
  -u sk_xxxx: \
  -d payment_intent=pi_xxxx \
  -G
response
{
  "data": [
    {
      "id": "dp_xxxx",
      "payment_intent": "pi_xxxx",
      "balance_transactions": [
        {
          "id": "txn_2",
          "amount": -1000,  # 決済金額
          "fee": 1500,      # チャージバック手数料
          "net": -2500,     # 合計
        }
      ],
      ...

チャージバックが発生したときは、決済で発生した資金に加えて、チャージバック手数料がかかる。Balance Transaction を見てみよう。

id source amount fee net
txn_1 payment +1,000 -36 +964
txn_2 dispute -1,000 -1,500 -2,500
total 0 -1,536 -1,536

決済手数料とチャージバック手数料をあわせて、1,536 円のマイナスになった。

このまま放置するか、あるいは反証資料を提出しても認められなければ、Balance Transaction はこのままだ。けれど、反証資料を提出して認められれば、チャージバックがらみの資金は戻ってくる。

反証資料が認められた後の、Balance Transaction を見てみよう。

request
curl https://api.stripe.com/v1/disputes
  -u sk_xxxx: \
  -d payment_intent=pi_xxxx \
  -G
response
{
  "data": [
    {
      "id": "dp_xxxx",
      "balance_transactions": [
        {
          "id": "txn_2",
          "amount": -1000,
          "fee": 1500,
          "net": -2500,
          ...
        },
        {
          "id": "txn_3",
          "amount": 1000,
          "fee": -1500,
          "net": 2500,
          ...
        }
      ],
      ...

Dispute に関連した Balance Transaction が増えている。

id source amount fee net
txn_1 payment +1,000 -36 +964
txn_2 dispute -1,000 -1,500 -2,500
txn_3 adjust +1,000 +1,500 +2,500
total +1,000 -36 +964

チャージバック関連のマイナスが相殺されて、当初の決済の txn_1 ぶんだけが残る。

最後に資金フローのアスキーアートを残しておく。

Buyer                                                  Seller
Card                                                   Bank
  :
Payment Method -> [Charge] -> [Balance Transaction] -> Account
  |  ^               :                |                 |  ^
  |  |         [Payment Intent]       v fee             |  |
  |  |               :              Stripe              |  |
  |  |               :                ^ fee             |  |
  |  |               :                |                 |  |
  |  +--------- [Dispute] <- [Balance Transaction] <----+  |
  |                                                       |
  +------------> [Adjust] -> [Balance Transaction] --------+
                                      ^ fee
                                      |
                                    Stripe

まとめ

Refund と Dispute が分かったので、基本的な資金フローは分かった。明日は、消込 / Reconsiliation を見ていく。