💭

クレジットカードによるサブスク実装をするときの全体感【PAYJP】

2022/09/22に公開

CTOの名人です。
マナリンクでのPAY.JPの活用を題材に、クレジットカードによるサブスク実装をするときの全体感について解説します。

これまでサブスク課金を実装したことがなかったけど、これから実装することがあるかもといった方や、サブスク課金特有の罠について知りたい方に読んで頂きたいです。

主に以下のテーマについて説明していきます。

  1. カード情報をトークン化して決済処理
  2. 2回目以降の課金成功をWebhookで検知して通知処理
  3. 2回目以降の課金での失敗をWebhookで検知して通知処理
  4. ユーザーがいつでもサブスクを停止、再開できる機能

カード情報をトークン化して決済処理

大前提として、クレジットカードによる決済処理を実装する際は、自社のサーバーをユーザーのクレジットカード番号が通過しないように実装する必要があります。

https://payjp-announce.hatenablog.com/entry/2017/11/10/182738

上記記事から、以下のように、カード番号を非通過にすることが求められていることがわかります。

一般社団法人日本クレジット協会および経済産業省により「クレジットカード取引におけるセキュリティ対策の強化に向けた実行計画」*2 (以下「実行計画」)が策定され、 改正割賦販売法の施行時期 2018年6月頃 を目安に、後述するクレジットカード番号の非通過対応が加盟店さま側で求められております。

それでは、クレジットカード番号をクライアントから受け取らずにどうやって決済処理を行うかと言うと、クレジットカード番号を元に生成した”トークン”を利用して決済処理を行います。

ざっくりいうと、フロントエンドにpayjp.jsというライブラリを用いて決済フォームを構築し、入力されたクレジットカード番号をまずはPAYJPの専用のエンドポイントへPOSTします。正しいカード番号であれば数十文字の文字列でトークンが返ってきます。ここまでの手順が以下画像の3までに相当します。


※引用元:https://payjp-announce.hatenablog.com/entry/2017/11/10/182738

最後に得たトークンをLaravel等のバックエンドにPOSTします。バックエンドではPAYJPのシークレットを環境変数経由で取得しつつ、トークンをPAY.JPのAPI宛にシークレットとともにPOSTすることで決済処理を実行できます。これが画像中の4に相当します。

このときバックエンドで任意の処理を実行できるので、売上テーブルにデータをインサートしたり、関連するユーザーにメール通知を飛ばしたりもできます。

また、注意点として、後述するサブスクの2回目以降の支払いに備えて、サブスクのIDをDBに保存しておく必要があります。サブスクのIDは、サブスクの決済処理をPAYJPに対して実行したときに返り値として入手できます。

2回目以降の課金成功をWebhookで検知して通知処理

さて、サブスクの支払いに一度成功すると、毎月同日に決済が行われます。決済が行われるたびに、売上テーブルにデータをインサートしたり、関連するユーザーに通知を飛ばす必要がありますが、先程の決済処理と違って、ユーザーのアクションを契機にこれらの処理を実行することはできません。

サブスクの2回目以降の支払いに対して処理をするには、PAYJPのWebhookを活用します。

Webhookとは、カンタンに言うとあるWebサービスで起こったイベントをフック(hook)に別のWebサービスへ通知する仕組みのことです。
ここでは、PAYJP内でサブスクの2回目以降の決済が正常終了したことをフックに弊社のサーバーへPOSTリクエストで通知する仕組みがWebhookとして用意されています。

PAYJPの管理画面から、自社のAPIサーバーの特定のURLを入力します。(例:https://example.com/webhook/payjp)
こうすると、サブスクの課金成功のみならず支払いの失敗時などいろいろなタイミングで入力したエンドポイントにPOSTリクエストが飛んでくるようになります。

ですので、当該エンドポイントに対してControllerを実装して、受け取ったペイロードを分析してサブスクの課金成功処理だった場合は、売上テーブルにデータをインサートしたり、関連するユーザーに通知を飛ばす実装を書くことができます。

ここで予め保存しておいたサブスクのIDと、イベントのペイロードに含まれているサブスクのIDを照合することで、サーバー上でもどのサブスクに対して支払いが成功したのかを特定し、関連するユーザーや契約情報にアクセス可能になります。逆に言うと、契約情報を辿れるようにサブスクIDを保存するようにDB設計しておく必要があります。

なお、このControllerはあらゆるPAYJP上のイベントを受け取ることになるので、最初にどのタイプのイベントかを分岐する処理を書いたら、その先はユースケースなどに処理を切り出すことでFat Controllerを防ぐことができます。

2回目以降の課金での失敗をWebhookで検知して通知処理

同様に、Webhookを通して課金の失敗に関する通知も受け取れるので、その場合にユーザーにクレジットカードの再入力を求めるといった対応も実装可能です。

サブスクの罠として、初回の支払いを行ったときはクレジットカードが有効だったのにも関わらず2回目以降の支払いで無効になることがあり得ることが挙げられます。
たとえば有効期限切れや、デビットカードを使っていて残高が切れた場合、単純に今月の与信枠を使い切った場合など様々あります。しかし、いかなる理由であっても、PAYJPのAPIからはCard Declinedといった単純なエラーメッセージしか返ってこない点には注意です。詳細な原因はわからないので、支払いに失敗して原因がわからないという問い合わせをエンドユーザーからもらっても、こちらにとっても原因がわからないことに注意が必要です。

ユーザーがいつでもサブスクを停止、再開できる機能

サブスクの停止、再開機能も必要です。これらを実現する方針はいくつかありますが、弊社の場合は契約情報とサブスクIDを紐付けているので、フロントエンドには契約情報IDが返されるようになっており、そこから停止や再開したい契約情報を指定してPOSTすることで、サーバーサイドでサブスクIDを引いてきてPAYJPに対してキャンセルまたは再開のリクエストを飛ばします。

再開について注意すべきことは、PAYJPの言うところの「再開」は停止してから本来の次回支払日までの間にしかできないということです。

定期課金をキャンセルする場合は、下記のようなリクエストを送ることで可能です。statusは canceled になり、現在のサイクルの終了日 current_period_end 以降に自動で定期課金が削除されます。

https://pay.jp/docs/subscription

定期課金が削除されてしまうと再開機能を使うことはできません。それでも再開させたい場合は、同じ金額のサブスクを新たに開始することで、エンドユーザーから見たらあたかも再開したように見せることが可能です。


以上でサブスク実装の全体感の解説を終わります。

マナリンク Tech Blog

Discussion