Node-REDでLINE Payを使うノードを公開してみた

このブログはNode-RED Advent Calendar 2021 9日目の記事です

LINE Pay、最近話題ですよね(悪い意味でw)

japanese.engadget.com

Githubに個人情報をアップロードした上にそれが閲覧できる状態だったというなかなかの事故です。

ただ、システム上で管理するためのIDやら識別子みたいなので住所やクレジットカードの番号がないのが不幸中の幸いかもしれませんね。(フォローしているつもりはないw)

これ以上深く言及するつもりはありませんが自分もやりかねないような事故なので反面教師として受け止めることにします。

前フリはこのぐらいにして本題です。

久々にNode-REDで使うオリジナルのノードを作ってみたので紹介します。

そう、渦中のLINE PayをNode-RED使えるようにしました!

LINE Payは自前のアプリケーションに決済システムを組み込めるようにするためのAPIが揃っています。

SDKは公式で提供していないですが、いろんな方がLINE Pay用のSDKを公開しています。

engineering.linecorp.com

github.com

これをNode-REDでもできたらかなり楽だろうなと思い作ってみました。

作ったノード

node-red-contrib-line-payという名前でノードを公開しています。

flows.nodered.org

以前作ったnode-red-contrib-telloではREADMEが英語ですが、今回は日本で使う人が多いだろうということで日本語版READMEも用意しました。

具体的な使い方やサンプルも掲載しているのでぜひいろいろ試してみてください!

github.com

こだわりポイント

では実装者のこだわりポイントを紹介します。

実装はv1.0らしく

Node-REDのオリジナルノードはv0系とv1.0以降では実装スタイルが違うようだということをドキュメント見てて気づきました。

nodered.jp

今まで作ったノードは何も考えずにv0系の実装をしたのですが、非同期なv1.0に合わせるのであればsend()関数やdone()関数を使った実装をしたほうが非同期処理でもノードの処理の終わりが明示的になるのでいいらしいです(ノードを作った事ある人じゃないと何言っているかわからんかもしれません、ごめんなさい…)

多分v0系使っている人はいないので多分大丈夫だと思うのですが、下位互換があるようにしたほうがいいのかな…と今更悩んでますw

初めてConfigノードを導入した

何かしらのAPIを叩くノードを実装するときにはConfigノードを使う方法がよくあります。

ここに接続情報を書くとNode-RED側に保存されるので、同じノードを別のフローで使いたいときにプルダウンメニューから接続情報を選択するだけで設定できるので便利です。

やり方は公式ドキュメントで解説されています。

nodered.jp

あとは、inputタグのtypeをpasswordにしてますが、インプットタイプがpasswordだと自動入力に登録するか聞かれて鬱陶しいので、自動入力を無効にさせるようにしました。

    <div class="form-row">
        <label for="node-config-input-channelId"><i class="fa fa-id-card-o"></i>Channel ID</label>
        <input type="password" id="node-config-input-channelId" autocomplete="off">
    </div>
    <div class="form-row">
        <label for="node-config-input-channelSecret"><i class="fa fa-key"></i>Channel Secret</label>
        <input type="password" id="node-config-input-channelSecret" autocomplete="off">
    </div>

async/awaitで実装

今まで非同期が苦手だったんですけど、やっぱりNode.js使うなら非同期ぐらい書けないとと思い、関数はasync/awaitで実装しました。

        node.on('input', async (msg, send, done) => {
            let api = '/v3/payments/request';
            let body = msg.payload;
            RED.log.info(`call ${api}`);

            try {
                let setting = {
                    headers: MakeHeaders('POST', api, node, body),
                    transformResponse: [
                        data => {
                            return jsonBigint.parse(data)
                        }
                    ],
                };
                res = await axios.post(node.config.uri + api, body, setting);
                msg.payload = res.data;
                send(msg);
                done()
            } catch (err) {
                done(err);
            }
       });

思ってたよりも難しくなく、関数書きまくって非同期コワクナイぐらいにはなりました。

辛い話

ここから実装するときに大変だったポイントを紹介します。

トランザクションIDを正しく取得できない

LINE PayのAPIでは取引情報をトランザクションIDを使ってやり取りをしています。

ところが、このトランザクションIDをConfirm APIで取得して他のAPIでリクエストを投げると、Transaction record not found.と表示されます。

APIのバグだとは考えにくいのでなんでこうなったのかよくわからなくて悩んでいたら、どうやらJavaScript特有のMAX_SAFE_INTEGER制約に引っかかっているぽいです。

dev.classmethod.jp

axiosでリクエストした結果は自動で型変換されてしまうので、そのときに19桁のトランザクションIDの下2桁は勝手に丸められるそうです。

↓こんな感じで丸められます(トランザクションIDはダミーなのでご安心をw)。

2318945757360476258 → 2318945757360476200←下2桁が必ず0になる

そこで、npmライブラリのjson-bigintを使って以下のようにレスポンスをパースするようにaxiosのコールバック関数を用意します。

これで正しくトランザクションを正しく取れるようにしました。(これをRequestノードで1から実装するってこと考えると辛い…)

transformResponse: [
   data => {
       return jsonBigint.parse(data)
   }
],

なぜか出るencodeObject Error

APIの実行後のレスポンスをDebugノードに渡すとなぜか全部のノードではなく、captureノードとcheckPaymentStatusノードの出力をDebugノードで表示させようとするとencodeObject Errorと表示されます。

上記で紹介したjson-bigintのパース処理を入れないとエラーが出ないので、その変換処理が原因っぽいです。

Node-REDのフォーラムでもそれらしきエラーは出てるけど結局解決には至らなかったようです…

discourse.nodered.org

ただ、Debugノード以外のノードでパラメータの取得はできるっぽいので一旦目をつぶることにしました(なにか修正案がありましたらコメントorレポジトリのissueへ)。

captureノードとcheckPaymentStatusノードのうち、checkPaymentStatusノードに関してはレスポンスにトランザクションIDが含まれていないのでとりあえずこっちは先程のjson-bigintの処理を外して対処しました。

気づくのに時間がかかった凡ミス

リクエストヘッダーは共通の処理で作成しているのになぜかレスポンスでHeader information error. channelId is required headerと出力されていたんです。

APIのリクエストボディーを疑いましたが何も変なところはありませんでした。

他のノードの関数と見比べてわかったのが、axios.post()のリクエストボディとsettingオブジェクトの引数が逆でしたw。

そんなのすぐ気づくだろ?って思いますよね?それが、リクエストボディもsettingオブジェクトもjson形式なのでモジュール側ではエラーにならなくてそのままリクエスト実行されちゃうんですよw。

だからAPI側のエラーと勘違いしてしまうんですよね。

ちなみにこれに気づくのに3時間ぐらいかかりましたw。次コード書くときは気をつけねば…

今後の展望

今は英語でノードを記述していますが、日本語のローカライズ化をやってみようと思います。

ドキュメント見たらやり方が紹介されていたので気が向いたらアップデートします。

nodered.jp

あとはさっき紹介したdebugノードをつないだときのバグも対処したいですね。

まとめ

今回は新作のオリジナルノードのnode-red-contrib-line-payのお話をしました。

辛いこともありましたが、なんとかLINE PayのAPIを簡単に使えるいい感じのやつができてよかったです。

で、LINE PayをWebアプリで実装する方法はレポジトリのサンプルで紹介しましたが、同じLINEならLINE botにも決済機能入れたいですよね?

というわけで決済機能が入ったLINE botをNode-REDで実装する方法はLINE DCのアドベントカレンダーで紹介しようと思います(多分Zennで書きます)。

宣伝

このブログでは使い方の紹介はしませんでしたが、LINE DCのLT大会でデモを交えてこのノードの使い方を紹介するので良かったら参加登録お願いします(リアタイで参加するとビンゴで景品もらえるよ!)

linedevelopercommunity.connpass.com