Node.jsでPayPalの商品購入の通知(Webhook)を受け取る


PayPalライトユーザーなので簡単な通知受け取りやってみます。

PayPalのAPIは正直分かりにくいので自分用にまとめます。APIのドキュメントページは以前よりは良くなったけど、昔から存在する分、調べていると過去の情報と混同しがちです。なので2018年2月バージョンです。適宜読み替え必須。

別途ちゃんと記事にまとめたい

かんたん決済ボタンで会社の商品(Nefry BT)販売しています

PayPalにはかんたん決済ボタンというツールがあります。たぶん通称がPayPalボタンで、製品名がウェブペイメントスタンダード。呼び方いっぱいありますね苦笑

https://www.paypal.com/jp/webapps/mpp/merchant/solutions/paypal-button

HTMLソースを貼り付けるだけで商品購入機能を追加でき、すごく簡単に作れて助かっています。

例えばNefry BTの購入ページからたどると、こんな画面になります。

かんたん決済ボタン(PayPalボタン)の購入通知をNode.jsで受け取りたい

この、商品が購入されたことを知りたいときに使う仕組みがInstant Payment Notification (IPN)と呼ぶそうです。認識間違ってるかもしれないけど僕はそう理解しておきます。

ちなみに、毎回購入してテストするのも大変なので、購入があったときに通知をシミュレーション出来るツールもあります。
Instant Payment Notification (IPN) Simulatorらしいです。

https://developer.paypal.com/developer/ipnSimulator/

今回は手元のMacでNode.jsのサーバーを立ててngrokを経由してPayPalからのWebhookを受け取ります。

Expressを使ってPOSTリクエストを受け取るので、expressとbody-parserをインストールします。
ちなみにNode.jsはv9.5.0です。

$ npm init -y
$ npm i --save express body-parser
$ touch app.js
app.js
'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.urlencoded({extended: false}));
app.use(express.static(__dirname+'/'));

const PORT = process.env.PORT || 3000;

app.post('/', async (req, res) => {
    console.log(req.body); //PayPalから送られて来る情報の確認    

    const response = {
        message: 'Success!'
    };
    res.end(JSON.stringify(response));
});

app.listen(PORT);
console.log(`listening on *:${PORT}`);

起動

$ node app.js
listening on *:3000

これで3000番ポートでサーバーが起動します。

このままだと、PayPal側からのWebhookを手元のPCで立てただけのサーバーでは受け取れないのでngrokを使ってトンネリングをします。

まずインストールです。npm経由でインストールできます。

$ npm i -g ngrok

先ほどNode.jsで3000番ポートでサーバーを立てたので、3000番ポートにリダイレクトするトンネリングサーバーを起動します。ターミナルの別タブなどで起動しましょう。

$ ngrok http 3000

起動するとこんな感じでURLが発行されます。

ngrok by @inconshreveable                                      (Ctrl+C to quit)

Session Status                online
Account                       n0bisuke (Plan: Free)
Version                       2.2.8
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://2c0f263e.ngrok.io -> localhost:3000
Forwarding                    https://2c0f263e.ngrok.io -> localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              8       0       0.00    0.00    0.21    5.61

この場合だとhttps://2c0f263e.ngrok.ioがトンネリングサーバーのURLです。
このURLはグローバル公開されているので、このURLに対してPayPalはPOSTリクエストを送ることが出来ます。

このトンネリングサーバーにPOSTリクエストがあると、手元のNode.jsで立てたサーバーにもPOSTリクエストがそのまま送られます。

ちなみにngrokのURLはngrokを再起動するたびに再発行されるので注意しましょう。

Instant Payment Notification (IPN) Simulatorで試してみる

こちらのURLにアクセスし、IPN handler URLの箇所に、ngrokで作成したトンネリングサーバーのURL(今回だとhttps://2c0f263e.ngrok.io)を設定します。Transaction typeはあんまりよく分かってないけどExpress Checkoutにしました。(教えて下さい)

この状態でページ下部のSend IPNを押しましょう。

これで購入があったときのようなPOSTリクエストがPayPalから送られてきて、手元で確認できます。

$ node app.js

listening on *:3000
{ payment_type: 'instant',
  payment_date: 'Mon Feb 12 2018 19:27:32 GMT+0900 (JST)',
  payment_status: 'Pending',
  address_status: 'confirmed',
  payer_status: 'verified',
  first_name: 'John',
  last_name: 'Smith',
  payer_email: '[email protected]',
  payer_id: 'TESTBUYERID01',
  address_name: 'John Smith',
  address_country: 'United States',
  address_country_code: 'US',
  address_zip: '95131',
  address_state: 'CA',
  address_city: 'San Jose',
  address_street: '123 any street',
  business: '[email protected]',
  receiver_email: '[email protected]',
  receiver_id: '[email protected]',
  residence_country: 'US',
  item_name: 'something',
  item_number: 'AK-1234',
  quantity: '1',
  shipping: '3.04',
  tax: '2.02',
  mc_currency: 'USD',
  mc_fee: '0.44',
  mc_gross: '12.34',
  mc_gross_1: '9.34',
  txn_type: 'web_accept',
  txn_id: '507675401',
  notify_version: '2.1',
  custom: 'xyz123',
  invoice: 'abc1234',
  test_ipn: '1',
  verify_sign: 'AV5g.RRvzcNMaeOO73JJ-F3fudM2ApaAE0NwEVMxteL8ElyK913tPoLn' }

ターミナルにこんな感じのログが表示されればOKです。

実際のかんたん決済ボタン(PayPalボタン)で試してみる

ボタンの設定画面でステップ 3: 高度な機能をカスタマイズする(オプション)を選択し、その中の高度な変数を追加するの項目にnotify_url=(Webhookを受け取るサーバーのURL)という形式で記述します。

今回の場合はnotify_url=https://2c0f263e.ngrok.ioを記述しました。

この状態で実際に購入してみると手元のNode.jsのサーバーに情報が送られてきます。

{ mc_gross: '1',
  protection_eligibility: 'Eligible',
  address_status: 'confirmed',
  payer_id: 'YKQ5X3VAHDSR8',
  address_street: '����2-17-11\r\n�N���G�C�^�[�Y�V�F�A�n�E�X �g���u��',
  payment_date: '12:19:52 Feb 17, 2018 PST',
  payment_status: 'Completed',
  charset: 'Shift_JIS',
  address_zip: '111-0025',
  first_name: '�ɉ�',
  mc_fee: '1',
  address_country_code: 'JP',
  address_name: '���� �ɉ�',
  notify_version: '3.9',
  custom: '',
  payer_status: 'verified',
  business: '[email protected]',
  address_country: 'Japan',
  address_city: '�䓌��',
  quantity: '1',
  verify_sign: 'AXK39wE-yxGXy8w.5f-Q92oLUMrXAgw1pM3bgPNM5AJYP8TnyyiCUzHc',
  payer_email: '[email protected]',
  txn_id: '8TW04018NT010421T',
  payment_type: 'instant',
  last_name: '����',
  address_state: '�����s',
  receiver_email: '[email protected]',
  payment_fee: '',
  receiver_id: 'R9B4DK8ZQGJ7U',
  txn_type: 'web_accept',
  item_name: '[�w��]�T���v���{�^��',
  mc_currency: 'JPY',
  item_number: '',
  residence_country: 'JP',
  transaction_subject: '',
  payment_gross: '',
  ipn_track_id: '4a2e2f679688c' }

送られて来たけど文字化けしてますね...

文字化け対応

送られてきた情報をのcharset: 'Shift_JIS'が怪しいです。調べるとUTF8に設定できるらしい。

RailsでPaypalのIPNを処理

設定画面からPayPalボタンの言語コード化を選択します。

言語のエンコード画面になります。

詳細オプションを選択します。エンコード方式が確認出来ますが、確かにShif_JISになってますねぇ。。。

UTF-8に変更しましょう。

これでやっととれました。PayPalに登録している住所やメールアドレス、どの商品を買ったかなどの情報がNode.jsで文字化けせずに受け取れました。プログラム側でエンコードしてもいいですけど、UTF-8にしておいた方が何かと便利です。

{ mc_gross: '2',
  protection_eligibility: 'Eligible',
  address_status: 'confirmed',
  payer_id: 'YKQ5X3VAHDSR8',
  address_street: 'xxxx2-17-11\r\nXXXXXXシェアハウス XXXXX',
  payment_date: '14:31:07 Feb 17, 2018 PST',
  payment_status: 'Completed',
  charset: 'UTF-8',
  address_zip: '111-xxxx',
  first_name: '遼介',
  mc_fee: '2',
  address_country_code: 'JP',
  address_name: '菅原 遼介',
  notify_version: '3.9',
  custom: '',
  payer_status: 'verified',
  business: '[email protected]',
  address_country: 'Japan',
  address_city: '台東区',
  quantity: '1',
  verify_sign: 'XXXXXXXXXX59viv6mMh95wQVtgejAmdhHhoJEWKLm9oesHOJkWqMK-8G',
  payer_email: '[email protected]',
  txn_id: '9XX216775X972960X',
  payment_type: 'instant',
  last_name: '菅原',
  address_state: '東京都',
  receiver_email: '[email protected]',
  payment_fee: '',
  receiver_id: 'XXXXDK8ZQGJ7U',
  txn_type: 'web_accept',
  item_name: '[購入]サンプルボタン',
  mc_currency: 'JPY',
  item_number: '44',
  residence_country: 'JP',
  transaction_subject: '',
  payment_gross: '',
  ipn_track_id: 'xxxxf023bdc2a' }

所感

昔よりは分かりやすいけどまだまだ分かりにくい印象なので今回のメモもいつまで使えるのかなぁという感じです苦笑

PayPalボタンと同様のIPNシミュレータの使い方を知りたいですけど、よくわからなかったので実際試すときは1円とか2円のPayPalボタンを作って購入テストしてみました。笑

もっとスマートなデバッグしたい気持ち。