React+Redux+Ant Design+TypeScript e-commerce practice - server application 06 Order Management api and other APIs

Posted by Lee-Bartlett on Sat, 18 Dec 2021 19:51:06 +0100

Create order Schema

// models\order.js
const mongoose = require('mongoose')
const Product = require('./product')
const Category = require('./category')

// Create snapshot type
// Clone product Schema
const snapshotProductSchema = Product.schema.clone()
// Clone classification Schema
const snapshopCategorySchema = Category.schema.clone()
// Delete the unique setting of category name, otherwise save the same products [n] snapshot. category. Name will fail
snapshopCategorySchema.path('name', String)
// Change the type property from ObjectId to category Schema is used to store type snapshots
snapshotProductSchema.path('category', snapshopCategorySchema)

const orderSchema = new mongoose.Schema(
  {
    // Product information
    products: [
      {
        // Product association id
        product: {
          type: mongoose.Schema.Types.ObjectId,
          ref: 'Product'
        },
        // Product snapshot information (avoid being unable to view product information from the order after the product is deleted)
        snapshot: snapshotProductSchema,
        // Product purchase quantity
        count: Number
      }
    ],
    // Merchant order ID (alipay asynchronous notification return)
    out_trade_no: String,
    // alipay transaction ID (alipay generated ID returned by alipay asynchronous notification)
    trade_no: String,
    // Total order amount
    amount: Number,
    // Receiving address
    address: String,
    // Order status
    // Unpaid = > unpaid
    // Paid = > paid
    // Shipped = > in transit
    // Completed = > completed
    // Cancelled = > cancelled
    status: {
      type: String,
      default: 'Unpaid',
      // enum uses exceptions and uses validate instead
      validate: {
        validator(v) {
          return ['Unpaid', 'Paid', 'Shipped', 'Delivered', 'Cancelled'].includes(v)
        },
        message: '{VALUE} Is not a valid order status'
      }
    },
    // Order user
    user: {
      type: mongoose.Schema.Types.ObjectId,
      ref: 'User'
    }
  },
  {
    timestamps: true
  }
)

module.exports = mongoose.model('Order', orderSchema)

api - order management - create order

Create order

Define an api to create an order, which is used to skip the payment direct test, and then convert it into a method, which is only called in the api/verifySignature interface.

// controllers\order.js
const Order = require('../models/order')
const Product = require('../models/product')

// Create order
const create = async (req, res) => {
  // order => {
  //   trade_no,
  //   out_trade_no,
  //   amount,
  //   address,
  //   products,
  //   user,
  //   result
  // }

  const order = req.body

  // Add order status
  order.status = order.result ? 'Paid' : 'Unpaid'
  // Delete signature verification result
  delete order.result

  // Get product snapshot information
  const snapshotObject = {}

  await Product.find({
    _id: {
      $in: order.products.map(({ product }) => product)
    }
  })
    .populate({
      path: 'category',
      select: '_id name'
    })
    .exec()
    .then(products => {
      products.forEach(product => {
        snapshotObject[product._id] = product
      })
    })

  // Populate product snapshot information
  order.products.forEach(item => {
    item.snapshot = snapshotObject[item.product]
  })

  // Save order
  // MyModel.create() and new mymodel() Same as save()
  // The difference is mymodel Create () can save multiple document s
  const data = await Order.create(order)

  // Change product inventory and sales
  
  res.json({ message: 'Order created successfully' })
}

module.exports = {
  create
}

// routes\order.js
const express = require('express')
const { create } = require('../controllers/order')

const router = express.Router()

// Create order (skip payment)
router.post('/order/create', create)

module.exports = router

Change product inventory and sales

// controllers\product.js
const Order = require('../models/order')

...

// Update the inventory and sales volume of products under the order
const updateProductQuantityAndSold = orderId => {
  Order.findById(orderId, (error, order) => {
    if (error || !order) {
      console.error('Failed to obtain order information when updating production inventory and sales volume', error)
      return
    }

    // Generate updateOne command
    const bulkOps = order.products.map(item => ({
      updateOne: {
        filter: {
          _id: item.product
        },
        update: {
          // $inc increases the specified value by the specified amount
          $inc: {
            quantity: -item.count,
            sold: +item.count
          }
        }
      }
    }))

    // Execute multiple commands at once
    Product.bulkWrite(bulkOps, {}, error => {
      if (error) {
        console.error('Product inventory and sales volume update failed', error)
      }
    })
  })
}

module.exports = {
  create,
  getProductById,
  read,
  update,
  remove,
  list,
  listRelated,
  listCategories,
  photo,
  updateProductQuantityAndSold
}

// controllers\order.js
const { updateProductQuantityAndSold } = require('./product')

// Change product inventory and sales
updateProductQuantityAndSold(data._id)

Convert the api into a method and call

// controllers\order.js
const Order = require('../models/order')
const Product = require('../models/product')
const { updateProductQuantityAndSold } = require('./product')

// Create order (to skip payment test)
const create = async (req, res) => {
  const order = req.body
  try {
    await createOrder(order)
    res.json({ message: 'Order created successfully' })
  } catch (e) {
    res.status(400).json({ message: 'Order creation failed', errors: e })
  }
}

// Create order (used to be called after successful payment)
const createOrder = async order => {
  // order => {
  //   trade_no,
  //   out_trade_no,
  //   amount,
  //   address,
  //   products,
  //   user,
  //   result
  // }

  // Add order status
  order.status = order.result ? 'Paid' : 'Unpaid'
  // Delete signature verification result
  delete order.result

  // Get product snapshot information
  const snapshotObject = {}

  await Product.find({
    _id: {
      $in: order.products.map(({ product }) => product)
    }
  })
    .populate({
      path: 'category',
      select: '_id name'
    })
    .exec()
    .then(products => {
      products.forEach(product => {
        snapshotObject[product._id] = product
      })
    })

  // Populate product snapshot information
  order.products.forEach(item => {
    item.snapshot = snapshotObject[item.product]
  })

  // Save order
  // MyModel.create() and new mymodel() Same as save()
  // The difference is mymodel Create () can save multiple document s
  const data = await Order.create(order)

  // Change product inventory and sales
  updateProductQuantityAndSold(data._id)
}

module.exports = {
  create,
  createOrder
}

Create order after asynchronous notification

// controllers\alipay.js
...
const { createOrder } = require('../controllers/order')

...

// Payment successful callback
const alipayNotifyUrl = async (req, res) => {
  // 1. Signature verification
  // Create SDK instance
  const alipaySdk = new AlipaySdk(alipaySdkConfig)
  try {
    // result is a Boolean value, indicating whether the signature is passed
    const result = await alipaySdk.checkNotifySign(req.body)

    // Get return parameters
    const passback = JSON.parse(req.body.passback_params)

    // Create order
    createOrder({
      out_trade_no: req.body.out_trade_no,
      trade_no: req.body.trade_no,
      amount: req.body.total_amount,
      address: passback.address,
      products: passback.products,
      user: passback.userId,
      result
    })
  } catch (e) {
    console.error('Signature verification exception:', e)
    res.status(400).json(e)
  }
}

module.exports = {
  getPayUrl,
  alipayNotifyUrl
}

api - user management - get user historical orders according to id

api design

essential information

Path: /user/orders/:userId

Method: GET

Request parameters

Headers

Parameter nameIs it necessaryremarks
AuthorizationyesAuthentication token, format bearer < JSON web token >

Return data

Field nameexplain
ResponseReturned collection object
├─ statusOrder status
├─ out_trade_noAlipay trading ID
├─ trade_noMerchant order ID
├─ amountTotal order amount
├─ addressReceiving address
├─ productsProduct collection in order
├─── _idmongoose default assigned id field
├─── countProduct purchase quantity
├─── productAssociated product information (the same as the return object of product details)
├─── snapshotProduct snapshot information (the same as the return object of product details and filled with product classification information)
├─ userUser information of the order
├─── _idmongoose default assigned id field
├─── nameUser nickname
├─ createdAtSpecifies the automatically managed createdAt field assigned when creating a Schema
├─ updatedAtSpecifies the automatically managed updatedAt field assigned when creating a Schema
├─ _idmongoose default assigned id field
├─ _vmongoose default assigned versionKey field

Define api

// controllers\user.js
...
const Order = require('../models/order')

...

// Obtain the user's historical order according to the id
const getOrderHistory = (req, res) => {
  Order.find({ user: req.profile._id })
    .select('-products.snapshot.photo')
    .populate([
      {
        path: 'user',
        select: '_id name'
      },
      {
        path: 'products.product',
        select: '-photo'
      }
    ])
    .sort('-createdAt')
    .exec((err, orders) => {
      if (err) {
        return res.status(400).json(errorHandler(err))
      }

      res.json(orders)
    })
}

module.exports = {
  signup,
  signin,
  getUserById,
  read,
  update,
  getOrderHistory
}

// routes\user.js
const { signup, signin, getUserById, read, update, getOrderHistory } = require('../controllers/user')

...

// Obtain the user's historical order according to the id
router.get('/user/orders/:userId', [tokenParser, authValidator], getOrderHistory)

...

api - order management - get the cover picture of the product snapshot in the order according to the id

api design

essential information

Path: /order/snapshotPhotp/:orderId/:productId

Method: GET

Return data

Returns the file stream of the product snapshot picture

Define api

// controllers\order.js
const { errorHandler } = require('../helpers/dbErrorHandler')

...

// Obtain the cover picture of the product snapshot in the order according to the id
const getSnapshopPhoto = (req, res) => {
  const { orderId, productId } = req.params
  Order.findById(orderId)
    .select('products.snapshot')
    .exec((err, data) => {
      if (err || !data) {
        return res.status(400).json(errorHandler(err))
      }

      const productItem = data.products.find(item => item.snapshot._id.toString() === productId)

      if (!productItem) {
        return res.status(400).json(errorHandler('The snapshot information corresponding to the product was not found'))
      }

      res.set('Content-Type', productItem.snapshot.photo.contentType)
      res.send(productItem.snapshot.photo.data)
    })
}

module.exports = {
  create,
  createOrder,
  getSnapshopPhoto
}

// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto } = require('../controllers/order')

const router = express.Router()

// Create order (skip payment)
router.post('/order/create', create)
// Obtain the cover picture of the product snapshot in the order according to the id
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)

module.exports = router

api - order management - administrator get order list

api design

essential information

Path: /orders/:userId

Method: GET

Request parameters

Headers

Parameter nameIs it necessaryremarks
AuthorizationyesAuthentication token, format bearer < JSON web token >

Return data

Returns an array of order information.

Define api

// controllers\order.js

...

// Get a list of all orders
const list = (req, res) => {
  Order.find()
    .select('-products.snapshot.photo')
    .populate([
      {
        path: 'user',
        select: '_id name'
      },
      {
        path: 'products.product',
        select: '-photo'
      }
    ])
    .sort('-createdAt')
    .exec((err, data) => {
      if (err || !data) {
        return res.status(400).json(errorHandler(err))
      }
      res.json(data)
    })
}

module.exports = {
  create,
  createOrder,
  getSnapshopPhoto,
  list
}

Configuration api

// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto, list } = require('../controllers/order')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { getUserById } = require('../controllers/user')

const router = express.Router()

// Create order (skip payment)
router.post('/order/create', create)
// Obtain the cover picture of the product snapshot in the order according to the id
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)
// The administrator gets a list of all orders
router.get('/orders/:userId', [tokenParser, authValidator, adminValidator], list)

// Monitor routing parameters
// Obtain user information according to id
router.param('userId', getUserById)

module.exports = router

api - order management - administrator modifies order status

api design

essential information

Path: /order/updateStatus/:userId

Method: PUT

Request parameters

Headers

Parameter nameIs it necessaryremarks
AuthorizationyesAuthentication token, format bearer < JSON web token >

Body

Parameter nameIs it necessary to passtypeexplain
orderIdyesStringOrder ID
statusyesStringOrder status

Return data

Returns a success prompt

Define api

// Modify order status
const updateStatus = (req, res) => {
  const { orderId, status } = req.body
  if (!orderId) {
    return res.status(400).json(errorHandler('Please pass in the order ID'))
  }

  // Only the save method triggers the verifier
  // To verify whether the status belongs to the specified enumeration range, save is used instead of updateOne
  Order.findById(orderId, (err, order) => {
    if (err || !order) {
      return res.status(400).json(errorHandler(err))
    }

    order.status = status
    order.save((error, updateOrder) => {
      if (error) {
        return res.status(400).json(errorHandler(error))
      }

      res.json(data)
    })
  })
}

module.exports = {
  create,
  createOrder,
  getSnapshopPhoto,
  list,
  updateStatus
}

// routes\order.js
const express = require('express')
const { create, getSnapshopPhoto, list, updateStatus } = require('../controllers/order')
const { tokenParser, authValidator, adminValidator } = require('../validator/user')
const { getUserById } = require('../controllers/user')

const router = express.Router()

// Create order (skip payment)
router.post('/order/create', create)
// Obtain the cover picture of the product snapshot in the order according to the id
router.get('/order/snapshotPhotp/:orderId/:productId', getSnapshopPhoto)
// The administrator gets a list of all orders
router.get('/orders/:userId', [tokenParser, authValidator, adminValidator], list)
// The administrator modifies the order status
router.put('/order/updateStatus/:userId', [tokenParser, authValidator, adminValidator], updateStatus)

// Monitor routing parameters
// Obtain user information according to id
router.param('userId', getUserById)

module.exports = router

end

Then you can develop client applications using these interfaces.

Topics: node.js React TypeScript