決済系の仕込みという、自身にとっての鬼門案件と自分のあぽーによる負のスパイラルについて書いたLINEPay決済の導入体験。

LINEPayのオンライン決済を組み込んでみて

この記事に関連して、実装中に気になったことを引き続き、残しておこうと思います。
なお、以降にでてくるソースコードの一部は、今回の実装時で利用させていただいた、https://github.com/nkjm/line-pay をベースに記述されています。

外部サービスを利用する時は

  1. URLへリクエストを投げる
  2. リクエストが正常に完了(200)したら、正常処理をコールバックで実行する
  3. リクエストが異常終了(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行にも満たないけれど、色々考えさせられました。
次はもう少し、改良したい。