Doorkeeper
ニュース

問題の報告:イベント前払いの記録における不具合について

2018-08-27(月)

7月23日、ある参加者が弊社のクレジットカード決済プロセッサであるStripeを通じてDoorkeeperのイベントの支払いをしました。しかし、バグの発生によりDoorkeeperではその支払いが適切に記録されませんでした。Doorkeeper上で支払いが反映されなかったことから、その参加者は再度支払いをし、この支払いについては記録されました。

二重払いが判明したことから、バグの発生の原因となった問題を解決しました。また、この問題が発生したのは今回の一件のみであったことも確認し、今後同じような問題を監視するためのモニタリング機能も追加しました。

今回の問題が発生したことで、このような深刻な問題に関連する処理過程を見直す機会となりました。弊社の方針として、今回のような限られたケースであったとしても、今後はイベント申し込みに関わる問題を公表していくことを決定しました。

問題のタイムライン

2018-07-23

当該参加者がStripe経由で前払いを完了するが、Doorkeeper側でその支払いが記録されず。当該参加者にはエラーが表示されるが、Doorkeeperの既存のモニタリング機能では認識されず。

エラーが表示され、また申し込みが完了したと表示されなかったため、当該参加者はイベント主催者に連絡。エラーについて伝え、支払い状況を確認してもらうよう主催者に依頼。

その後、当該参加者は再度イベントの支払いを試みるも、所有するクレジットカードの会社に支払いを拒否される。30分後、再度支払いを試みたところ、この支払いは問題なく処理される。この時点で、当該参加者のクレジットカードは2回課金されている。

2018-08-06

主催者が当該参加者に対して、二重払いのうち1件の支払いを全額返金。

2018-08-07

当該参加者のクレジットカード会社が、返金されていない支払いに対して調査を開始。

2018-08-08

主催者がDoorkeeperに対して支払いの調査について報告。当該参加者が二重課金されていた問題を知らせる。

2018-08-09

Doorkeeperは二重で前払いが課金された原因となった問題を解決。当該の問題が初めて起こった事実を確認し、Stripe経由の支払いが適切に完了しなかった場合には警告がDoorkeeperに送信される付加的確認機能を追加する。この対応について、Doorkeeperから主催者に返信し、報告。

問題の原因

今回の問題はDoorkeeperがStripeで課金を作成し、前払いを記録した方法が原因となり発生しました。Doorkeeperのコードを簡易化したバージョンは以下の通りです。

charge = Stripe::Charge.create(amount: @registration.amount, source: params['stripeToken'])
if charge.paid
  @registration.prepaid!
end

このコードが実行される限り、期待される通りに手続きは機能し、前払いは記録されます。しかし、今回問題が発生したケースでは、Stripeによる課金は作成されたものの、Doorkeeperが当該の支払いを記録する前にプロセスが予想外に中断されました。

プロセスが予想外に中断されることは、ほとんど起こることはないものの起こりうる事象であり、我々のアプリケーションは今回のケースに対応できるはずでした。これはまた、プロセスが変則的に中断されたことから、このコード内で例外が発生せず、我々が警告を受信しなかった一因となっています。

この問題を解決するため、Stripeで課金を試みる前に「決済処理中」ステータスである申し込みをDoorkeeperで記録することにしました。申し込みがこのステータスである場合、その後に続く支払いはできなくなり、万が一プロセスが予想外に中断された場合においても二重課金を防ぐことができます。

追加のモニタリング機能

今後、同様の問題が発生しないよう、予想外の事態が発生した際にDoorkeeperが警告を受け取れるよう、さらなるモニタリング機能を追加しました。

StripeかPayPalを使って参加者が前払いを試みるすべての場合において、5分後に確認の処理が実行されるよう設定しました。この処理は、支払いが問題なく完了するか、またはクレジットカード会社により拒否されるかなど、有効な支払いステータスで完了していることを確認します。ステータスが未完了のままの支払いがあった場合には、Doorkeeperに警告が送信されます。このような警告があれば、Stripeでは支払いされたもののDoorkeeper側で記録されていなかったという、今回のようなケースも認識できたと考えています。

上記の対策に加えて、Stripeが提供するwebhookを使い追加的な確認機能も採用する予定です。このwebhookは、主催者のStripeアカウントを通じて課金される際に通知が送信されるものです。Doorkeeperによって作成されるすべての課金において、今後はDoorkeeper側でも記録されるようにしたいと思います。この機能も採用することで、今回のような問題も速やかに認識できるようになると考えています。

誤解の回避

今回のバグの影響は、このことが原因となって生じた一連の誤解により拡大されました。これらの誤解は、当該参加者、主催者、当該参加者のクレジットカード会社、Stripe、Doorkeeperという大勢の人が問題に関わることになったことに起因しています。課金を実行したアプリケーションの当事者として、Doorkeeperは今回の問題の全体像をもっともよく把握しています。しかし残念ながら、これらの関係者の中で当該問題を認識したのはDoorkeeperが一番最後でした。我々がもっと本件を事前に予防していれば、多くの誤解が生じるのを回避できたのではないかと思います。

追加的なモニタリング機能により、今回のような問題が今後発生した場合にはより早く認識できると考えています。しかし、我々が把握しきれない他の問題が発生するリスクはなおも存在することから、そのような問題が発生した際に状況を速やかに報告されるよう尽力するつもりです。特に下記について努力する必要があると考えています。

  • 申し込み時に問題が発生した際に、主催者ではなくDoorkeeperに連絡してもらうよう参加者に呼びかける
  • 申し込み時に問題が発生したと参加者から報告があった場合には、Doorkeeperに連絡してもらうよう主催者に呼びかける
  • 主催者がStripeに対して問題を報告した場合には、Doorkeeperにも知らせてもらうようStripeのカスタマーサポートと協力する

上記に対する具体的な解決方法はまだ確定していません。また、どの程度これらの目標を達成できるか確かではありません。しかし、今回のような誤解が生じることを今後は最小限に止められるよう努力していきたいと思っています。

過去に発生した類似問題

今回の問題に加えて、前払いに関連した他の問題が過去にありました。それぞれの問題の影響を受けた各主催者と迅速に協力できたことから、これらの問題については公表していませんでした。この機会に、下記のとおり、それぞれの問題を簡単に公表いたします。

2018-07-18

数名の参加者が試みたPayPal経由での前払いの支払いがDoorkeeper上で「無効」と表示されているとの報告が、ある主催者から寄せられる。しかし、PayPal側では「成功」と表示されていました。

これは「COMPLETED(すべて大文字)」ではなく「Completed(最初だけ大文字)」とのステータスが、PayPalからこの主催者に対して送られたことによって発生していることがDoorkeeperで判明しました。他のすべての主催者に対しても、ステータスは「COMPLETED」として送られ続けていたこと、そして当該主催者に対しても「Completed」は時々しか送られてきていなかったことから、我々はこれをPayPalのバグによるものだと推測しました。また、ステータスの値が「COMPLETED」であると表明しているPayPalのドキュメントにも矛盾することから、PayPal側の問題に起因していると考えました。

Doorkeeperでは既知のステータスのみを認め、それ以外の前払いは無効としていたことから、「Completed」というステータスの支払いを無効と認識していました。大文字小文字を区別せずステータスの確認ができるよう修正することで、このPayPalの問題に対応しました。

1人の主催者に対する合計8件の申し込みが、この問題の影響を受けました。この主催者には影響を受けた各申し込みの詳細をDoorkeeperから提供し、その後の対応について提案しました。

2018-06-05

Doorkeeperの自動モニタリング機能が、PayPalの支払いページに参加者がリダイレクトされる際に問題が発生していることを検知しました。詳しく調べたところ、「メモ」欄に「\b(バックスペース文字コード)」が含まれていた場合にPayPalのAPIがクラッシュし、「Internal Error(内部エラー)」を返すというバグが発生していることが判明しました。

取り急ぎの対応として、「\b」がメモ欄に含まれないよう当該文字をイベント名から削除しました。その後、この問題について主催者に報告をし、影響を受けた参加者1人に対して連絡を取ってもらうようお願いしました。

その後、PayPalのバグ対策を実装し、Doorkeeperから送信するすべてのメモ欄から当該文字を削除するようにしました。

2017-07-21

特定のPayPal取引に関してDoorkeeperが詳細情報を取得できないと、ある主催者から報告を受けました。詳しい調査の結果、PayPal側では「pending(待機中)」となっていた支払いがDoorkeeperでは完了となっていたことが判明しました。

PayPal側で待機中となっている支払いがあった場合、PayPalのAPIからは彼らの規定が示す「PENDING」ではなく「COMPLETED」の全体的ステータスがDoorkeeperに送信されていたことがわかりました。全体的な支払いステータスを確認するのではなく、並列の支払いのいずれかがPENDINGの「transactionStatus(取引ステータス)」となっていないかを確認する必要がありました。

この問題を解決するため、該当する支払いを正しく待機中とするためにコードを変更しました。また、待機中の支払いに関して事前に主催者に連絡ができるよう、eCheck(日本国外の一定のアカウントにおいてPayPalがサポートする支払い方法)以外の理由により待機中とされている支払いの場合に対して警告機能も追加しました。

その支払いが待機中となっていたのは、主催者がPayPalアカウントで使用していたメールアドレスをPayPal側で変更し、Doorkeeper側では変更していなかったことが原因でした。PayPalでは、PayPalアカウントに紐付いているメールアドレスだけではなく、いかなるメールアドレスでも支払いを作成することができます。支払いを作成した際、その支払いは待機中とされ、PayPalは支払いの請求ができることを知らせるメールをそのメールアドレスに送信します。このため、我々は支払いを作成した後、主催者がもはや確認していないメールアドレスにそのメールを送信していました。この一連の問題は、PayPalの実装において認識すべきエッジケースを明らかにしていると考えています。