| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- const API = require('../../lib/API')
- const db = require('../../plugin/DataBase/db')
- const Redis = require('../../plugin/DataBase/Redis')
- const { BaseStdResponse } = require('../../BaseStdResponse')
- const AccessControl = require('../../lib/AccessControl')
- const { validateCoupon, recordUsage, roundMoney } = require('../../lib/CouponService')
- const { buildPaymentSession } = require('../../lib/OrderPayment')
- const { createPaymentAttempt, generateGatewayOrderNo } = require('../../lib/OrderPaymentAttempt')
- const { enqueueOrderPaymentCheck } = require('../../plugin/mq/orderPaymentWorker')
- function generateOrderId() {
- const now = new Date()
- const pad = (n, w = 2) => n.toString().padStart(w, '0')
- return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}` +
- `${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}` +
- `${pad(now.getMilliseconds(), 3)}`
- }
- async function acquireCouponUsageLock(couponId) {
- const lockKey = `coupon:usage:${couponId}`
- const rows = await db.query('SELECT GET_LOCK(?, 5) AS ok', [lockKey])
- return { lockKey, ok: Number(rows?.[0]?.ok || 0) === 1 }
- }
- async function releaseCouponUsageLock(lockKey) {
- if (!lockKey) return
- try { await db.query('SELECT RELEASE_LOCK(?)', [lockKey]) } catch (_) { }
- }
- class CreateOrder extends API {
- constructor() {
- super()
- this.setPath('/Order/CreateOrder')
- this.setMethod('POST')
- }
- async onRequest(req, res) {
- const { uuid, session, goods_id, pay_type, coupon_code } = req.body
- if ([uuid, session, goods_id, pay_type].some(v => v === '' || v === null || v === undefined)) {
- return res.json({ ...BaseStdResponse.MISSING_PARAMETER })
- }
- if (!await AccessControl.checkSession(uuid, session)) {
- return res.status(401).json({ ...BaseStdResponse.ACCESS_DENIED })
- }
- let couponLockKey = null
- try {
- const goodsRows = await db.query(
- 'SELECT name, price, num, state FROM goods WHERE id = ? LIMIT 1',
- [goods_id]
- )
- if (!goodsRows || goodsRows.length !== 1) {
- return res.json({ ...BaseStdResponse.ERR, msg: '商品不存在' })
- }
- const goods = goodsRows[0]
- if (Number(goods.num) < 1 || Number(goods.state) !== 1) {
- return res.json({ ...BaseStdResponse.ERR, msg: '商品已下架或库存不足' })
- }
- const createTime = Date.now()
- const orderId = generateOrderId()
- const normalizedPayType = String(pay_type).trim()
- const originalPrice = roundMoney(goods.price)
- let finalPrice = originalPrice
- let discountAmount = 0
- let couponId = null
- let appliedCouponCode = null
- if (coupon_code && String(coupon_code).trim()) {
- let couponResult = await validateCoupon({
- code: coupon_code,
- userUuid: uuid,
- goodsId: goods_id,
- goodsPrice: goods.price
- })
- if (!couponResult.ok) return res.json({ ...BaseStdResponse.ERR, msg: couponResult.msg })
- const lockRet = await acquireCouponUsageLock(couponResult.couponId)
- if (!lockRet.ok) {
- return res.json({ ...BaseStdResponse.ERR, msg: '优惠码校验繁忙,请稍后重试' })
- }
- couponLockKey = lockRet.lockKey
- couponResult = await validateCoupon({
- code: coupon_code,
- userUuid: uuid,
- goodsId: goods_id,
- goodsPrice: goods.price
- })
- if (!couponResult.ok) return res.json({ ...BaseStdResponse.ERR, msg: couponResult.msg })
- finalPrice = couponResult.finalPrice
- discountAmount = couponResult.discountAmount
- couponId = couponResult.couponId
- appliedCouponCode = couponResult.code
- }
- const gatewayOrderNo = generateGatewayOrderNo(orderId)
- const payment = await buildPaymentSession({
- orderId,
- gatewayOrderNo,
- payType: normalizedPayType,
- goodsName: goods.name,
- price: finalPrice,
- deviceType: req.headers['device-type']
- })
- const conn = await db.connect()
- let insertResult
- try {
- await conn.beginTransaction()
- const [stockRes] = await conn.execute(
- 'UPDATE goods SET num = num - 1 WHERE id = ? AND num > 0 AND state = 1',
- [goods_id]
- )
- if (!stockRes || stockRes.affectedRows !== 1) {
- await conn.rollback()
- return res.json({ ...BaseStdResponse.ERR, msg: '商品库存不足或已下架' })
- }
- const [orderRes] = await conn.execute(
- `INSERT INTO orders (
- orderId, create_user, create_time, goods_id, price, pay_type,
- original_price, discount_amount, coupon_id, coupon_code
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
- [
- orderId,
- uuid,
- createTime,
- goods_id,
- finalPrice,
- normalizedPayType,
- originalPrice,
- discountAmount,
- couponId,
- appliedCouponCode
- ]
- )
- insertResult = orderRes
- await createPaymentAttempt({
- orderId,
- payType: normalizedPayType,
- gatewayOrderNo,
- executor: conn
- })
- if (couponId) {
- await recordUsage(couponId, orderId, uuid, discountAmount, conn)
- }
- await conn.commit()
- } catch (writeError) {
- try { await conn.rollback() } catch (_) { }
- throw writeError
- }
- if (!insertResult || insertResult.affectedRows < 1) {
- return res.json({ ...BaseStdResponse.ERR, msg: '创建订单失败' })
- }
- await Redis.set(`payData:${orderId}`, JSON.stringify(payment.payData), { EX: 300 })
- try {
- await enqueueOrderPaymentCheck(orderId)
- } catch (error) {
- this.logger.error(`推送订单支付检查消息失败,订单号:${orderId},错误:${error.stack || error}`)
- }
- return res.json({
- ...BaseStdResponse.OK,
- id: orderId,
- pay: payment
- })
- } catch (err) {
- this.logger.error(`创建订单失败:${err.stack || err}`)
- return res.json({ ...BaseStdResponse.ERR, msg: err.message || '创建订单异常,请联系管理员' })
- } finally {
- await releaseCouponUsageLock(couponLockKey)
- }
- }
- }
- module.exports.CreateOrder = CreateOrder
|