iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 25
0
Modern Web

Node JS-Back end見聞錄系列 第 25

Node.js-Backend見聞錄(24):實作-商品系統(七)-確認訂單部分

Node.js-Backend見聞錄(24):實作-商品系統(七)-確認訂單部分

前言

接續實作-商品系統(六)-修改訂單部分(二),我們來完成商品系統的最後一個功能確認訂單

確認訂單部分需求

  • 取得訂單資料(GET)
  • 確認訂單(PUT)

套件

$ npm install nodemailer

資料結構

新增的部分:

  • models
    • order
      • complete_model.js
    • connection_mail.js
.
├── app.js
├── bin
│   └── www
├── config
│   └── development_config.js
├── controllers
│   ├── member
│   │    └── modify_controller.js
│   ├── order
│   │    ├── get_controller.js
│   │    └── modify_controller.js
│   ├── product
│   │    └── get_controller.js
├── models
│   ├── member
│   │    ├── encryption_model.js
│   │    ├── login_model.js
│   │    ├── register_model.js
│   │    ├── update_model.js
│   │    └── verifyication_model.js
│   ├── order
│   │    ├── all_order_model.js
│   │    ├── complete_model.js
│   │    ├── delete_model.js
│   │    ├── one_order_model.js
│   │    ├── order_all_product_model.js
│   │    ├── order_one_product_model.js
│   │    └── update_model.js
│   ├── product
│   │    └── all_product.js
│   ├── connection_mail.js
│   └── connection_db.js
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── member.js
│   ├── order.js
│   └── product.js
├── sevice
│   └── member_check.js
├── views
    ├── error.ejs
    └── index.ejs
├── .env
└── .gitignore

開始實作

由於取得訂單資料(GET)部分已經完成,所以我們直接實作最後一個API確認訂單(PUT)

整個交易流程到這步驟就代表會員已經按下類似確定購物的按鈕,等同於我們可以確定這筆交易已經完成。所以該做的事情有:

  1. 確認是否有該訂單。
  2. 確認訂單是否已完成。
  3. 提取訂單的商品ID。
  4. 進行商品的庫存確認。
  5. 若都還有剩餘商品,則進行庫存的扣除。
  6. 將訂單狀態鎖定。
  7. 寄送email。

註記:這部分我們還尚未實作金流功能。

確認是否有該訂單

這部分我們一樣提取之前做好的判斷function來使用:

const checkOrderData = (orderID, memberID) => {
    return new Promise((resolve, reject) => {
        db.query('SELECT * FROM order_list WHERE order_id = ? AND member_id = ? ', [orderID, memberID], function (err, rows) {
            if (rows[0] === undefined) {
                resolve(false);
            } else {
                resolve(true);
            }
        })
    })
}

確認訂單是否已完成

用來確認訂單的欄位是order_listtable的is_complete。為0就是還未完成,為1就是已經完成。

const checkOrderComplete = (orderID) => {
    return new Promise((resolve, reject) => {
        db.query('SELECT is_complete FROM order_list WHERE order_id = ?', orderID, function (err, rows) {
            if (rows[0].is_complete === 1) {
                resolve(false);
            } else {
                resolve(true);
            }
        })
    })
}

提取訂單的商品ID

由於在進行進行商品的庫存確認前,我們要先提取訂單中的product_id才能再去product的table進行庫存確認。

const getOrderData = (orderID, memberID) => {
    return new Promise((resolve, reject) => {
        db.query('SELECT * FROM order_list WHERE order_id = ? AND member_id = ? ', [orderID, memberID], function (err, rows) {
            resolve(rows);
        })
    })
}

進行商品的庫存確認

在此步驟,我們可以做個判斷,若是有庫存則回傳true,但若是沒有庫存就回傳哪個商品庫存不足,好已告知會員目前有哪個庫存是沒有的。

const checkOrderStock = (orderProductID, orderQuantity) => {
    return new Promise((resolve, rejct) => {
        db.query('SELECT * FROM product WHERE id = ?', orderProductID, function (err, rows) {
            if (rows[0].quantity >= orderQuantity && rows[0].quantity !== 0) {
                resolve(true)
            } else {
                resolve(rows[0].name + "庫存不足")
            }
        })
    })
}

確認訂單部分

上述所提到的5. 若都還有剩餘商品,則進行庫存的扣除6. 將訂單狀態鎖定7. 寄送email都將在這步驟直接進行處理。但在實作前,我們先設定好nodemailer的設置環境。

設置nodemailer環境
  • google

為了能夠使用寄送Eamil的功能。因我們這部分的寄送email是使用google的gmail服務,所以我們得先去google的我的帳戶中將允許安全性較低的應用程式的功能啟用,在設置成啟用後才能使用寄送Email的功能。

  • Node.js

還記得在實作-會員系統(一)-會員註冊(一)的文章中,我們所使用到的dotenv的套件嗎?先得在.env的檔案中輸入讀者自己的gmail帳號及密碼。接續到config資料夾的development_config.js檔案中寫入:

require('dotenv').config()

module.exports = {
    mysql: {
      host: process.env.HOST,
      user: process.env.DATABASE_USER,
      password: process.env.DATABASE_PASSWORD,
      database: process.env.DATABASE
    },
    secret: process.env.MY_SECRET,
    senderMail: {
      user: process.env.SEND_MAIL_USER,
      password: process.env.SEND_MAIL_PASSWORD
    }
}

接著在models資料夾的connection_mail.js中寫入:

const config = require('../config/development_config');
const nodemailer = require('nodemailer');

module.exports = nodemailer.createTransport({
    service: 'gmail',
    auth: {
        user: config.senderMail.user, //gmail account
        pass: config.senderMail.password //gmail password
    }
});

註記:關於寄件的部分功能會一並寫在確認訂單部分的實作中。

這樣我們設置nodemailer環境的部分就完成了。接續回到確認訂單部分的實作,到models > order資料夾的complete_model.js檔案中來將上述的功能組合起來。

const config = require('../../config/development_config');
const db = require('../connection_db');
const transporter = require('../connection_mail');

module.exports = function orderComplete(orderID, memberID) {
    let result = {};
    return new Promise(async (resolve, reject) => {

        const hasData = await checkOrderData(orderID, memberID);

        const hasComplete = await checkOrderComplete(orderID);

        if (hasData === false) {
            result.status = "訂單完成失敗。"
            result.err = "沒有該訂單資料!"
            reject(result)
        } else if (hasComplete === false) {
            result.status = "訂單完成失敗。"
            result.err = "該訂單已經完成。"
            reject(result)
        } else if (hasData === true && hasComplete === true) {
            // 取得order_list的table資料
            const orderData = await getOrderData(orderID, memberID);

            // 提取商品id
            const productID = orderData[0].product_id;

            // 依序確認訂單中的商品是否有庫存
            for (let key in orderData) {
                const hasStock = await checkOrderStock(orderData[key].product_id, orderData[key].order_quantity);
                if (hasStock !== true) {
                    result.status = "訂單完成失敗。"
                    result.err = hasStock
                    reject(result);
                    return;
                }
            }

            // 將商品庫存扣除
            await db.query('UPDATE product, order_list SET product.quantity = product.quantity - order_list.order_quantity WHERE order_list.product_id = product.id and order_list.order_id = ?;', orderID, function (err, rows) {
                if (err) {
                    console.log(err);
                    result.status = "訂單完成失敗。"
                    result.err = "伺服器錯誤,請稍後在試!"
                    reject(result);
                    return;
                }
            })

            // 將is_complete的訂單狀態改為1
            await db.query('UPDATE order_list SET is_complete = 1 WHERE order_id = ?', orderID, function (err, rows) {
                if (err) {
                    console.log(err);
                    result.status = "訂單完成失敗。"
                    result.err = "伺服器錯誤,請稍後在試!"
                    reject(result);
                    return;
                }
            })

            // 寄送Email通知

            const memberData = await getMemberData(memberID);

            const mailOptions = {
                from: `"企鵝購物網" <${config.senderMail.user}>`, // 寄信
                to: memberData.email, // 收信
                subject: memberData.name + '您好,您所購買的訂單已經完成。',  // 主旨
                html: `<p>Hi, ${memberData.name} </p>` + `<br>` + `<br>` + `<span>感謝您訂購<b>企鵝購物網</b>的商品,歡迎下次再來!</span>` // 內文
            }

            transporter.sendMail(mailOptions, (err, info) => {
                if (err) {
                    return console.log(err);
                }
                console.log('Message %s sent: %s', info.messageId, info.response);
            })

            result.status = "訂單編號:" + orderID + " 付款已完成,謝謝您使用該服務!詳細的訂單資訊已寄送至 " + memberData.email;
            resolve(result);
        }
    })
}

註記:撰寫email的內文部分是用HTML語法來進行編譯,讀者可以在這部分花點巧思來設計內文的內容。

接續再將該model匯入到controllers > order資料夾的modify_controller.js檔案中:

putProductComplete(req, res, next) {
    const token = req.headers['token'];
    //確定token是否有輸入
    if (check.checkNull(token) === true) {
        res.json({
            err: "請輸入token!"
        })
    } else if (check.checkNull(token) === false) {
        verify(token).then(tokenResult => {
            if (tokenResult === false) {
                res.json({
                    result: {
                        status: "token錯誤。",
                        err: "請重新登入。"
                    }
                })
            } else {
                const memberID = tokenResult;
                const orderID = req.body.orderID;
                orderComplete(orderID, memberID).then(result => {
                    res.json({
                        result: result
                    })
                }, (err) => {
                    res.json({
                        result: err
                    })
                })
            }
        })
    }
}

最後,給予該API一個URL。這部份到routes資料夾的order.js檔案中寫入:

var express = require('express');
var router = express.Router();

const OrderGetMethod = require('../controllers/order/get_controller');
const OrderModifyMethod = require('../controllers/order/modify_controller');

orderGetMethod = new OrderGetMethod();
orderModifyMethod = new OrderModifyMethod();

// 取得全部訂單資料
router.get('/order', orderGetMethod.getAllOrder);

// 取得單一顧客的訂單資料
router.get('/order/member', orderGetMethod.getOneOrder);

// 訂整筆訂單
router.post('/order', orderModifyMethod.postOrderAllProduct);

// 更改單筆訂單資料
router.put('/order', orderModifyMethod.updateOrderProduct);

// 刪除訂單資料
router.delete('/order', orderModifyMethod.deleteOrderProduct);

// 訂單筆訂單
router.post('/order/addoneproduct', orderModifyMethod.postOrderOneProduct);

// 訂單已完成
router.put('/order/complete', orderModifyMethod.putProductComplete);

module.exports = router;

測試

在進行測試前,我們先說明這次的測試目標。

目前的product的table中,剩下的商品有這些。我們先用之前寫好的訂購功能來分別將商品2跟商品3的商品分別買2個跟1個。

之後到MySQL資料庫裡面去看order_list的結果。

發現到該筆訂單的編號是1,接續我們就使用Postman來進行測試,

  • HTTP method: PUT

  • HTTP url : localhost:3000/order/complete

  • Body中選擇x-www-form-urlencoded

  • headers

    • Content-Type: application/x-www-form-urlencoded
    • token: 會員經登入後所取得的token值
  • body

    • orderID: 1

其結果:

之後,我們再到資料庫那邊做進一步的確認:

發現order_list的table欄位is_complete有成功變成1,也就是已經完成訂單的動作。以及,product的table欄位quanaity其商品2及商品3都有各扣2個及1個。

接續到email的部分,發現也有成功接收到該email。

註記:email部分的測試,讀者記得把收跟發的email都用成有效的email。順帶一提,最快的測試方式就是自己寄給自己。

補充

商品系統-完整的code

shopping_cart_system

小結

商品系統的實作就此結束,不知道讀者的收穫如何?其實,不管是先前的會員系統或是目前的商品系統。在實務的商業邏輯部分,並沒有像筆者寫得這麼單純,若要真的像那些正在上線運作的系統,目前的功能是不健全的。但若是讀者要拿來練習基本的Node.js,那筆者想應該是綽綽有餘且滿有趣的。

筆者在該部分還有匯出Excel功能Unit Test的部分還未講解,但若寫下去會超過30篇的篇幅。但別擔心,筆者之後會再額外補上。接續,我們即將來到下個章節關於Server建置的架設部分。


上一篇
Node.js-Backend見聞錄(23):實作-商品系統(六)-修改訂單部分(二)
下一篇
Node.js-Backend見聞錄(25):關於Server建置
系列文
Node JS-Back end見聞錄31

尚未有邦友留言

立即登入留言