決済系の仕込みという、自身にとっての鬼門案件と自分のあぽーによる負のスパイラルについて書いたLINEPay決済の導入体験。
この記事に関連して、実装中に気になったことを引き続き、残しておこうと思います。
なお、以降にでてくるソースコードの一部は、今回の実装時で利用させていただいた、https://github.com/nkjm/line-pay をベースに記述されています。
外部サービスを利用する時は
- URLへリクエストを投げる
- リクエストが正常に完了(200)したら、正常処理をコールバックで実行する
- リクエストが異常終了(500など)したら、異常時の処理をコールバックで実行する
というのが基本構造になります。LINEPayのAPIをコールした場合もこの通り。
ただ、単純なデータ参照系の処理(例:RSSフィードをとってきて表示する)と違い、どのような商品を誰が、いつ、どの値段で決済したのかを自システムに記録する必要があります。
決済、つまり商品売買ですから、売上データとして必要な情報は確実に確保する必要があります。
// オプション作成 const options = { productName: data.name, // 商品名 amount: data.total, // 決済額 currency: "JPY", // 通貨 orderId: uuid // 注文番号 /* 後は各システムに合わせて設定するParameter */ } // 決済予約 pay.reserve(options).then((response) => { /* 予約成功後の処理 */ //LINEPAYの決済URLへリダイレクト res.redirect(response.info.paymentUrl.web) }).catch(function (e) { throw e; })
上記のコードは予約決済のAPIコール部分の抜粋です。コールバック形式なので、応答が戻ってきた時の関数内で
DB関連処理を行った後、決済URLのリダイレクトを実行してユーザー(端末)をLINEPayの決済画面へ誘導します。
他のAPIのコールもほぼ同じようなもので、リダイレクト部分が処理完了的な”メッセージをユーザーに出す”に変わるぐらいの差です。
さて、このDB関連処理。
自分がよく見かけたサンプルコードでは、キャッシュにデータセットする1行で簡易実装されています。
入れる先がDBに変わるだけ、と言えれば楽ですが、さすがにそんな簡単ではございません。
Azureのmysqlでウッカリなタイムアウトからのエラーを回避するべく、
今回は敢えて、接続・切断をこまめに行う方法を取ったうえ、await/asyncのコーディングをしなかったために、
入れるテーブルが2件以上になると(かつ、リレーションしているテーブルが多いと)、DBの例外キャッチを含めてネストと例外throwが複雑になりました。
特に悩んだのは例外発生時に、どのようにユーザーの状況を見せるか?という点。
単純なWebアプリであれば、例外ページを表示して終わりですが、LINEのトークルームとLIFFの両方を使っていたため、どう返そうかと。
最終的にLINEPayとのやり取りはトークルーム上のメッセージから開始するようにしたため、エラー発生時は空のjsonを返すことにして一旦fixです。
やり取り開始とともに、内部にブラウジングのためviewがひらくので、白い画面もどうかなーということで。
結果、だいたいこのような構成となりました。
// オプション作成 const options = { productName: data.name, // 商品名 amount: data.total, // 決済額 currency: "JPY", // 通貨 orderId: uuid // 注文番号 /* 後は各システムに合わせて設定するParameter */ } // コネクション const db = dbconf.connection() // DB処理用のモジュールを別に作り、コネクション生成 // 決済予約 pay.reserve(options).then((response) => { let reservation = options; reservation.transactionId = response.info.transactionId; // Save order information // FIRSET INSERT db.query([sql文], (qerr, qres) => { if (qerr) { logger.trace('[mysql error]', qerr) return res.json({}) } logger.trace('Order Last insert ID: ' + qres.insertId) // SECOND INSERT db.query([sql文], (qerr2, qres2) => { if (qerr2) { logger.trace('[mysql error]', qerr2) return res.json({}) } logger.trace('Condition Last insert ID: ' + qres2.insertId) db.end() //LINEPAYへ res.redirect(response.info.paymentUrl.web) }) }) }).catch(function (e) { throw e; })
※loggerはログ出力処理のインスタンス
リダイレクトは最後の最後、途中でエラーがあったら不発という王道です。
DB接続のクローズはその直前に。
結局、開いてしまった別viewを閉じずに、トークルームにメッセージを出すってできるの?という疑問から、
王道に落ち着きました。
ちなみに、ネストの弊害は思わぬ落とし穴も。
引数の命名で途中素敵なエラーを自分で作りだしました。
これらのロジックを起動する、大外のリクエスト受付部分の引数で“res”(ponse)があったのですが、途中のdbのコールバックの引数で“res”(ult)を使ってしまい、
「リダイレクトできんがな!」とアプリケーションエラーが。
長すぎるネストでjsの引数範囲を見誤りました。
そこで急遽、”qres”(query result)と簡易変更で対応、内側は連番で、というむかしっぽい記述が中に含まれた次第です。
コード量は500行にも満たないけれど、色々考えさせられました。
次はもう少し、改良したい。