PAY.JPで決済する場合単発の決済と、定期課金(サブスクリプション)ができます。
決済自体はどちらも簡単に導入できるのが、PAY.JPの良いところですが、定期課金についてその後のシステム上での管理が必要になります。
弊所のサービスで、サブスクリプションの決済サービスを制作を行いましたが、その過程で分からなかったことで特にWebhookの挙動について、PAY.JPの技術担当者とメールのやり取りをしながら開発したこともあり、ドキュメントとしてまとめたいと思いこの記事を書きました。
もしPAY.JPのWebhookの挙動について、お役に立てれば幸いです。
定期課金(サブスクリプション)の決済成功時のWebhook
そもそも定期課金においてWebhookが必要なのは、PAY.JP上のサブスクリプションの決済ステータスとシステム上のステータスの同期を取るためですね。
Webhookを使わないという場合は、決まった時間にバッチ処理で各サブスクリプションIDにアクセスして、ステータスを取りに行くという方法もありますが、この記事ではあくまでWebhookで出来る限りやる方向で話しを進めます。
まず、定期課金を設定したあとの、2回目以降の決済成功時の動作についての流れを見ていきたいと思います。
この記事ではプランに課金日の指定がない場合を想定します。
プラン作成時に例えば20日に課金日と固定することができるのですが、これが課金日の指定がある場合です。
それに対して、定期課金を申し込んだ日が課金日というように、申し込む人によって課金日が異なるのが、課金日の指定がないということになります。
定期課金が作成されたサブスクリプションに対して、2回目の決済が成功した場合は、続けて2つのWebhookが送信されます。
charge.succeeded
定期課金 | PAY.JP:2回目以降の課金
subscription.renewed
charge.succeededは、決済成功で課金されたという通知です。
subscription.renewedは、次の課金サイクルがスタートした、という通知です。
なので、この通知が来たタイミングで、データベース上では決済成功というレコードを作成し、subscription.renewedに関しては、正常な決済が続いている場合には特にステータスの変更処理は不要だと思っています。
正常な決済だけで済めば何も難しい事は無いのですが、決済が一度でも失敗した場合に決済再開までの処理がいくつか必要になります。
定期課金(サブスクリプション)の決済失敗時のWebhook
もし2回目以降の決済で失敗した場合のWebhookはこちらです。
charge.failed
定期課金 | PAY.JP:2回目以降の課金
subscription.paused
charge.failedが決済失敗のWebhook通知ですので、この通知でシステム上決済エラーというレコードを作成するイメージです。
subscription.pausedは、サブスクリプション停止のWebhookですので、システム上ではサブスクリプションを有効から無効などステータスの変更が必要なイメージです。
定期課金の再開とWebhook
では、定期課金を再開についてですが、当初私の勘違いをしていましたがPAY.JPでは一度失敗した定期課金については、決済のリトライは行いません。
顧客のカードの有効期限が切れていたり、カードが不正な場合、定期課金は失敗します。このとき定期課金は停止され、再開リクエストがされるまで今後の課金は行われません。定期課金を再開するには、顧客のカードを有効なものに更新してから、再開リクエストを送信します。
定期課金 | PAY.JP:定期課金の再開
多くの場合、決済が失敗する理由はクレジットカードの期限切れや限度額などの問題のため、少なくともその時点で該当のクレジットカードでは決済は難しいので、クレジットカードを変更してもらう必要があります。
そして、そのクレジットカードを変更したタイミングでResume APIを送信します。
Resume APIを送信すると、PAY.JP側では顧客情報のクレジットカードの情報を更新するとともに、滞納分があると判断した場合はそのまま即時決済されます。
そうして決済が成功すると、以下Webhookが届きます。
subscription.resumed
定期課金 | PAY.JP:定期課金の再開
charge.succeeded(失敗課金の再開リクエスト時)
subscription.resumedの通知を受け取り、サブスクリプションのステータスを停止から通常のステータスに変更し、charge.succeededを受け取ることで、失敗した決済レコードを決済済みに更新するイメージでしょうか。
しかし、ここで注意が必要です。
もし、プランに課金日の指定がない場合、決済サイクルがこの決済した日が起点となってしまう点です。
直近の決済日:5月1日(成功)
定期課金の金額:1,000円
停止日:6月1日(失敗)
再開日:6月25日■課金日指定がない場合
再開日が基準となりますので、以降は下記の通り課金されます。
6月25日1,000円→ 7月25日1,000円→ 8月25日1,000円、、、、、課金日を1日としたい場合は、再開時にtrial_endを指定して1日に課金されるようにしてください。
PAY.JP定期課金実装ガイド
そのため、定期課金の申し込んだ人によって、課金日が異なる場合は、その人の次の課金日の日付から残り期間を算出して、その日数をtrial_endとしてパラメーターに埋め込んで送信する必要があります。
それに対して、課金日が固定の場合は、再開時の決済がどの日であれ、次のサイクルの指定した日に決済されます。
そういった意味では、課金日固定のプランで運用する方が、システム上の処理は少しは楽なのかもしれませんね。
プラン変更のWebhookとサイクルについて
プラン変更の場合、即時のプラン変更と次のサイクルから変更するというやり方があります。
即時プラン変更をした場合は、課金サイクルの起点がその日になってしまうため、恐らくこのパターンでのプラン変更というのは、やらないのではないかと思います。
サイクル途中でプランを更新する場合
API「定期課金を更新」を使用し、引数
planを指定することでプランを更新できます。curl "https://api.pay.jp/v1/subscriptions/定期課金ID" \ -u "sk_test_c62fade9d045b54cd76d7036": \ -d "plan=更新したいプランID"この際、以下の点にご注意ください。
- 更新時点で新しいプランでの課金が実行され、サイクルも更新されます。
- プランに設定されているトライアル期間は無視されます。
課金を行わないためには、引数
プランを更新する | PAY.JP:サイクル途中でプランを更新する場合trial_endが利用できます。
課金したい時刻のタイムスタンプを指定することで、その時刻までがトライアル期間となり、その時刻を過ぎてから課金が行われます。
次のサイクルからのプラン変更
次サイクルからプランを更新する場合
trial_endを利用する方法
trial_endに現在のサイクル終了日current_period_endを指定することで、プランを更新しつつ、課金を次サイクルから行うようにできます。curl "https://api.pay.jp/v1/subscriptions/定期課金ID" \ -u "sk_test_c62fade9d045b54cd76d7036": \ -d "plan=次サイクルから適用したいプランID" \ -d "trial_end=現在の定期課金のcurrent_period_end"next_cycle_planを利用する方法
next_cycle_planを利用することでも、次サイクルからのプラン適用を切り替えることが可能です。curl "https://api.pay.jp/v1/subscriptions/定期課金ID" \ -u "sk_test_c62fade9d045b54cd76d7036": \ -d "next_cycle_plan=次サイクルから適用したいプランID"プラン適用を解除したい場合は、空文字列でリクエストしてください。
curl "https://api.pay.jp/v1/subscriptions/定期課金ID" \ -u "sk_test_c62fade9d045b54cd76d7036": \ -d "next_cycle_plan="
next_cycle_planを利用する方が良いかなと思います。
プランが変更された時のWebhook
next_cycle_planによってプランが変更され新しい課金が始まるタイミングに、subscription.renewedのWebhookが送信されます。
なので、プラン変更をシステム上で同期させる場合はこのsubscription.renewedのWebhookを利用すれば良いかと思います。
ちなみに、subscription.updatedというWebhookもありますが、これは、プランが変更されたタイミング(プラン更新のAPIを送信したタイミング)で送信されるので、subscription.renewedと間違えないようにしましょう。
プランの解約後のWebhookについて
プランを解約した場合、私の勝手な想像では解約満了日にWebhookが届くのかと思っていたのですが、それはありません。
プラン解約の場合の運用フローを考えると2パターンあると思います。
1つ目は、解約申請時に即解約のパターン。
2つ目は、解約申請日から契約更新日が満了日(正規の解約)のパターン。
1つ目の場合は、なにも難しくなくAPIでキャンセルしたと同時にシステム側でもキャンセル済みというステータスにして、ログイン機能などがあればそれをできなくすれば良さそうです。
問題は2つ目の解約申請日が解約日ではないパターンです。
この場合は、満了日まではシステム上ではまだアクティブですが、次の更新のタイミング(満了日)にWebhookが送信されるわけではないので、Cronでバッチ処理として、解約申請中のステータスを持っているSubscription IDを取得して、キャンセルをするという流れにするのが良さそうですね。
UTC時間基準の決済について
PAY.JPは内部的にはUTC時間基準で決済されます。
そのため、もし課金日の指定をしない場合に、月初や月末に定期課金が始まる場合は、日付がずれる場合があります。
月初および月末の9時以前に開始する場合
課金日なしで月初の9時以前、または月末の9時以前に課金する場合、以下のようなサイクルとなります。
例: 6月1日0時頃に定期課金を作成した場合
- 日本時間(JST): 6月1日0時頃に課金 → 7月1日0時頃に課金 → 7月31日0時頃に課金
- 弊社処理(UTC): 5月31日15時頃に課金 → 6月30日15時頃に課金 → 7月30日15時頃に課金
例: 3月31日0時頃に定期課金を作成した場合
- 日本時間(JST): 3月31日0時頃に課金 → 5月1日0時頃に課金 → 5月31日0時頃に課金
- 弊社処理(UTC): 3月30日15時頃に課金 → 4月30日15時頃に課金 → 5月30日15時頃に課金
このような挙動を避けたい場合、課金日の設定をしていただくか、定期課金のサイクル開始時刻を trial_end パラメーターにより9時以降にずらすなどの実装をご検討ください。
定期課金 | PAY.JP:課金日の指定をしない場合の注意
これは、同様の決済サービスのStripeにおいても同じようにUTC時間での決済になるため、問題になることがありますが、PAY.JPの場合、課金日固定の場合はJST(日本時間)で決済をするため、プランを日付固定で作成して、初回の決済時には日割計算でスタートさせるのが良いかもしれませんね。
まとめ
今回はPAY.JPで定期課金をスタートした場合のWebhookの挙動について、一通り書いてみました。
なかなかドキュメントだけでは不安な部分もあり、PAY.JPの技術スタッフさんとメールで教えてもらいながら開発している状況ですが、いつも丁寧に教えていただきありがたいなと思っています。
PAY.JPの定期課金については、事例としてまた何か出てきたら随時更新する予定です。
参考になれば幸いです。
以上、PAY.JPの定期課金(サブスクリプション)におけるWebhookの挙動と決済再開について、でした。
TEDASKはPAY.JPの決済導入を承っております。なるべく短期間でリリースできるようにご支援しておりますので、お気軽にお問い合わせください。




コメント