作ったもの
obnizを使って、LINE Beaconで仮想的にお店に入ったら即LINE Payでチョコ購入できるデモ pic.twitter.com/Fv6cUJPPIa
— るしわん/おの (@tkyko13) December 24, 2020
何が起きてる?
obnizで作ったLINE Beaconに反応してLINE Botから通知が来て、そのLINE Botのボタンを押すと即物が買えちゃうデモです。
即といってもLINE Payの確認画面には遷移するんですが…
obnizでLINE Beacon
こちらの(自分の)記事を参考にしました!
IFTTT有料化したので前回作ったなんちゃってポケモンGOをLINE Beaconで作ってみた - Qiita
line-pay-v3が動かない
https://github.com/tmitsuoka0423/line-pay-v3
こちらの方で動かせましたので、こちらを利用させていただいてます
npm i line-pay-v3 https://github.com/tmitsuoka0423/line-pay-v3
いざ開発していく
その他のライブラリのインストール
npm i dotenv obniz @line/bot-sdk express uuid
obniz側のソースコード
// おまじない"use strict";// .envファイルを読み込みますrequire('dotenv').config();constObniz=require('obniz');constobniz=newObniz(process.env.OBNIZ_ID);constLINE_BEACON_HID=process.env.LINE_BEACON_HID;//払い出されたIDconsthardwareId=[];// 16進数のByteArray型に変換for(leti=0;i<LINE_BEACON_HID.length/2;i++){hardwareId.push(parseInt(LINE_BEACON_HID.substr(i*2,2),16));}obniz.onconnect=asyncfunction(){console.log('on connect!');constdeviceMessage=[0x01,0x02,0x10];//サーバーに送るメッセージ 1~13byteconstUUID_FOR_LINECORP=[0x6F,0xFE];// flagletadv=[];adv=adv.concat([0x02,0x01,0x06]);//LE General Discoverable Mode & BR/EDR Not Supported//16bit uuidadv=adv.concat([0x03,0x03]);adv=adv.concat(UUID_FOR_LINECORP);//simple beaconadv=adv.concat([1+9+deviceMessage.length,0x16]);adv=adv.concat(UUID_FOR_LINECORP);adv=adv.concat([0x02]);adv=adv.concat(hardwareId);adv=adv.concat([0x7F]);adv=adv.concat(deviceMessage);awaitobniz.ble.initWait()obniz.ble.advertisement.setAdvDataRaw(adv);obniz.ble.advertisement.start();console.log('start beacon!');}
LINE Bot側のソースコード
'use strict';// .envファイルを読み込みますrequire('dotenv').config();// パッケージを使用しますconst{v4:uuidv4}=require('uuid');constLinePay=require('line-pay-v3');constexpress=require('express');constline=require('@line/bot-sdk');constPORT=process.env.PORT||3000;// 注文のデータを一時的に保管しますconstorderData={};// line-pay-v3を使用する準備constpay=newLinePay({channelId:process.env.LINE_PAY_CHANNEL_ID,channelSecret:process.env.LINE_PAY_CHANNEL_SECRET,uri:process.env.ENV==='production'?'https://api-pay.line.me':'https://sandbox-api-pay.line.me'});// Messaging APIで利用するクレデンシャル(秘匿情報)です。constconfig={channelSecret:process.env.LINE_BOT_CHANNEL_SECRET,channelAccessToken:process.env.LINE_BOT_ACCESS_TOKEN};constapp=express();// publicフォルダのファイルを公開するapp.use(express.static(__dirname+"/public"));constclient=newline.Client(config);asyncfunctionhandleEvent(event){if(event.type==='message'&&event.message.type==='text'){if(event.message.text==='チョコレート'){orderChocolate(event);}else{// テキストがきたらオウムがえしreturnclient.replyMessage(event.replyToken,{type:'text',text:event.message.text});}}elseif(event.type==='beacon'&&event.beacon){// ビーコン用console.log(`beacon device(${event.beacon.hwid})から${event.beacon.dm}が届いたよ`)orderChocolate(event);// return client.replyMessage(event.replyToken, {// type: 'text',// text: '入店ありがとうございます!'// });}// テキストでもビーコンでもなければ無視returnPromise.resolve(null);}// チョコレートを買う予約処理asyncfunctionorderChocolate(event){console.log('決済予約処理を実行します。');// 商品名や値段を設定する場合はここを変更します。constorder={amount:100,// packages[].amountの合計金額を記入するcurrency:'JPY',orderId:uuidv4(),packages:[{id:'Item001',amount:100,// products[].priceの合計金額を記入するname:'買い物かご',products:[{name:'チョコレート',// 商品名imageUrl:'https://2.bp.blogspot.com/-zEtBQS9hTfI/UZRBlbbtP8I/AAAAAAAASqE/vbK1D7YCNyU/s800/valentinesday_itachoco2.png',// 商品画像quantity:1,// 購入数price:100// 商品金額}]}],redirectUrls:{confirmUrl:`${process.env.NGROK_URL}/pay/confirm`,}};console.log('以下のオプションで決済予約を行います。');console.log('order',order);try{// LINE Pay APIを使って、決済予約を行う。constresponse=awaitpay.request(order);console.log('response',response);// 決済確認処理に必要な情報を保存しておく。order.userId=event.source.userId;orderData[order.orderId]=order;constmessage={type:"template",altText:`チョコレートを購入するには下記のボタンで決済に進んでください`,template:{type:"buttons",text:`チョコレートを購入するには下記のボタンで決済に進んでください`,actions:[{type:"uri",label:"LINE Payで決済",uri:response.info.paymentUrl.web},]}}awaitclient.replyMessage(event.replyToken,message);console.log('決済予約が完了しました。');}catch(e){console.log('決済予約でエラーが発生しました。');console.log(e);};}app.get('/',(req,res)=>res.send('Hello LINE BOT!(GET)'));//ブラウザ確認用(無くても問題ない)// 以下replay message用app.post('/webhook',line.middleware(config),(req,res)=>{console.log(req.body.events);if(req.body.events.length==0){res.send('Hello LINE BOT!(POST)');console.log('疎通確認用');return;}Promise.all(req.body.events.map(handleEvent)).then((result)=>res.json(result));});// 決済確認処理app.use('/pay/confirm',async(req,res)=>{console.log('/pay/confirmの処理を実行します。');// 決済予約時に保存した情報を取り出す。constorderId=req.query.orderId;if(!orderId){thrownewError('Order ID is not found');}constorder=orderData[req.query.orderId];if(!order){thrownewError('Order is not found');}// 決済確認処理に必要なオプションを用意する。constoption={amount:order.amount,currency:order.currency}console.log('以下のオプションで決済確認を行います。');console.log(option);try{// LINE Pay APIを使って、決済確認を行う。awaitpay.confirm(option,req.query.transactionId)awaitclient.pushMessage(order.userId,{type:'text',text:'決済が完了しました。'});res.send('決済が完了しました。');console.log('決済が完了しました。');}catch(e){console.log('決済確認処理でエラーが発生しました。');console.log(e);};});app.listen(PORT);console.log(`Server running at ${PORT}`);
.envファイルの作成
LINE_PAY_CHANNEL_ID=
LINE_PAY_CHANNEL_SECRET=
LINE_BOT_CHANNEL_SECRET=
LINE_BOT_ACCESS_TOKEN=
NGROK_URL=
OBNIZ_ID=
LINE_BEACON_HID=
それぞれの値の取得方法は参考サイトからお願いします
まとめ
obnizに挿しているセンサーからのデータや、今日の天気や気温などからおすすめの商品を渡せたりしたらもっと便利ですね!