PAY.JPで発生したイベントをWebhookで受け取る

Webサービスというのは基本的にプル型です。クライアント側からアクセスしてはじめてデータを受け取ったり、逆に登録したりします。しかし購入が発生した時や、何からのイベントが発生したときにそれをすぐに知りたいというニーズは強くあります。

それを可能にするのがWebhookです。通常はWeb API利用側がサーバにアクセスするところを、逆にサーバからWeb API利用者側の指定するURLにアクセスしてもらう仕組みです。簡単なシーケンス図は次のようになります。

f:id:goofmint:20171121161313p:plain

有名なところではGitHubのWebhookがあって、リポジトリへのコードプッシュがあったり、マージした時に通知を受け取れる仕組みがあります。またSlackもチャットメッセージが来た時にWebhookで通知してもらえます。同様にPAY.JPでもWebhookが用意されていますので、取引が発生したり、定期課金が作成されたタイミングなどで通知を受け取れます。

Webhook | PAY.JP

注意点として、Webhookを完全に信用するのは危険です。100%確実に届くとは言い切れません。Webhookを受け取るサーバが落ちているケースもあるでしょう。PAY.JPのWebhookでは400または500系エラーを受け取った際には3分間隔で3回までリトライしますが、それを超えると通知を行いませんので注意してください。これはWebhookをサポートするサービス全般に言えるもので、データをすべて受け取るのはWebhookではなく通常のWeb APIを用いるのが良いでしょう。

この記事ではそんなWebhookを実装するための方法を紹介します。

サーバの準備

WebhookはPAY.JPからあらかじめ指定したサーバを呼び出す仕組みです。つまりサーバはインターネット上に公開されている必要があります。クラウドなどを使ってサーバを立ち上げるのが一番良いのですが、開発時には ngrok を使うのが便利です。これは ngrok のサービスとローカルのHTTPサーバがつながり、ローカルコンピュータをインターネット上に公開できる仕組みです(厳密には違いますが、ここでは簡略化しています)。昔はダイナミックDNSで同様の仕組みが提供されていましたが、もっと手軽に使える技術になります。

今回のサンプルは Node.js + Express で作成してあります。Node.js はJavaScriptをサーバ上で実行する技術で、ExpressはNode.jsでよく使われるWebアプ リケーションフレームワークです。そして、PAY.JPで取引が発生すると /hooks が呼ばれるようになっています。コードは後で解説します。

router.post('/hooks', (req, res, next) => {
  // Webhook処理
  if (req.headers['x-payjp-webhook-token'] !== config.webhook_token) {
    // 不正なアクセス
    return res.status(401).send({});
  }
  if (req.body.type === 'charge.succeeded') {
    const charge = req.body.data;
    console.log('決済が成功しました');
    console.log(`id => ${charge.id}`);
    console.log(`card.last4e => ${charge.card.last4}`);
    console.log(`card.exp => ${charge.card.exp_year}/${charge.card.exp_month}`);
    console.log(`price => ${charge.amount}(${charge.currency})`);
  }
  res.send({});
});

設定を変更する

PAY.JPの管理画面で取得できる各種設定情報を config.json の中に記述します。config.example.json というファイルがあるので、 config.json に名前を変更してください。内容は最初、次のようになっているはずです。

{
  "public_key": "YOUR_PUBLIC_KEY",
  "secret_key": "YOUR_SECRET_KEY",
  "webhook_token": "YOUR_WEBHOOK_TOKEN"
}

これのテスト公開鍵、テスト秘密鍵をそれぞれ書き換えてください。

{
  "public_key": "pk_...e5",
  "secret_key": "sk_...24",
  "webhook_token": "whook_4a...f8"
}

次にローカルコンピュータでサーバを立ち上げます。

$ node ./bin/www

そして ngrok をインストールします。

brew install cask ngrok

インストールしたら、以下のコマンドでローカルコンピュータにインターネット上からアクセスできるようになります。

$ ngrok http 3000
ngrok by @inconshreveable                                       (Ctrl+C to quit)
                                                                                
Session Status                online                                            
Account                       Atsushi Mint Nakatsugawa (Plan: Free)             
Version                       2.2.8                                             
Region                        United States (us)                                
Web Interface                 http://127.0.0.1:4040                             
Forwarding                    http://001ca638.ngrok.io -> localhost:3000        
Forwarding                    https://001ca638.ngrok.io -> localhost:3000       
                                                                                
Connections                   ttl     opn     rt1     rt5     p50     p90       

一時的に使えるURL(今回の場合は https://001ca638.ngrok.io)ができましたので、このURL + /hooks をPAY.JPの管理画面にてWebhookするURLとして登録します。

f:id:goofmint:20171121161330p:plain

これで準備完了です。

取引を行う

ではテストで決済を行ってみます。決済は http://localhost:3000/ でできるようになっています。アクセスすると 100円支払うという表示とボタンが'確認できるはずです。

f:id:goofmint:20171121161348p:plain

ボタンを押すとPAY.JPのクレジットカード入力フォームが表示されます。テストのカード番号はこちらを参考にしてください。

f:id:goofmint:20171121161359p:plain

クレジットカード番号を入力すると、 http://localhost:3000/pay が呼ばれて1000円の決済処理が行われます。決済処理が無事完了すると、次のような場面が表示されるでしょう。

f:id:goofmint:20171121161410p:plain

Webhookが呼ばれる

そして少し待つとPAY.JPからWebhookが呼ばれます。ターミナルなどの画面に次のように表示されたら成功です。

決済が成功しました
id => ch_84970e95eb7f66e21a41a4dac60ae
card.last4e => 4242
card.exp => 2020/10
price => 1000(jpy)

Webhookの検証

WebhookのURLには特にセキュリティがありません。外部から誰でも呼べてしまうアドレスになります。そこで、呼ばれた内容が正しいことを検証する方法としてリクエストの X-Payjp-Webhook-Token ヘッダーを確認してください。この内容はPAY.JPの管理画面で確認できますので、リクエストに踏まれるヘッダーと同じものであればPAY.JPからの正当なWebhookだと確認できます。

イベントの種類

なお、PAY.JPでは以下のイベントの際にWebhookを呼び出します。詳細はこちらで確認できます。それぞれにイベント種別(type)が設定されていますので、それを見て何のイベントであるかを知ることができます。

  • 支払い関係
    • 成功
    • 失敗
    • 更新
    • 返金
    • 確定
  • トークン関係
    • 作成
  • 顧客関係
    • 作成
    • 更新
    • 削除
    • カード作成
    • カード更新
    • カード削除
  • プラン関係
    • 作成
    • 更新
    • 削除
  • 定期課金
    • 作成
    • 更新
    • 削除
    • 停止
    • 再開
    • キャンセル
    • 期間更新
  • 入金関係
    • 内容確定

PAY.JPから送られてきたデータを使って、チャットにメッセージを流したり、メールを送信するといった処理につなげるのも良いでしょう。前述の通り、100%の信頼性を前提にするのはお勧めしませんが、通知を使うことで業務の生産性をさらに高めることができるはずです。

Webhook | PAY.JP