wechat_miniprogram_pay.js 12.6 KB
const Router = require('koa-router');
const Promise = require('bluebird');
const tenpay = require('tenpay');
const config = require('config');
const mathjs = require('mathjs');
const route = new Router();
const payApi = new tenpay(config.get('wechatPay'), true);

const utils = require('../../utils/utils');
const { getVipOrder, updateVipOrder } = require('../../service/pay_vip');
const { logger } = utils;

async function updateProductStock(connection, cartInfos, tradeNo) {
  for (let i = 0; i < cartInfos.length; i++) {
    const cartInfo = JSON.parse(cartInfos[i].cart_info);

    if (cartInfo.seckill_id) {
      // 更新秒杀库存和销售量
      const updateSeckill = await connection.queryAsync(
        'UPDATE ct_store_seckill SET stock=stock-1,sales=sales+1 WHERE id=?',
        [cartInfo.seckill_id]
      );

      if (!updateSeckill.affectedRows) {
        throw new Error(
          `更新秒杀库存失败,【秒杀编号】:${
            cartInfo.seckill_id
          },【updateSeckill】:${JSON.stringify(updateSeckill)}`
        );
      }

      logger.info(
        `【支付通知】【订单编号】:${tradeNo},更新秒杀库存成功,【秒杀编号】:${cartInfo.seckill_id}`
      );
    } else {
      if (
        cartInfo &&
        cartInfo.productInfo &&
        cartInfo.productInfo.attrInfo &&
        cartInfo.productInfo.attrInfo.unique
      ) {
        const updateAttrValue = await connection.queryAsync(
          'UPDATE ct_store_product_attr_value SET stock=stock-?,sales=sales+? WHERE product_id=? AND `unique`=?',
          [
            cartInfo.cart_num,
            cartInfo.cart_num,
            cartInfo.productInfo.id,
            cartInfo.productInfo.attrInfo.unique
          ]
        );

        if (!updateAttrValue.affectedRows) {
          throw new Error(
            `更新产品属性表失败,【updateAttrValue】:${JSON.stringify(updateAttrValue)}`
          );
        }

          const updateProduct = await connection.queryAsync(
              'UPDATE ct_store_product SET stock=stock-?,sales=sales+? WHERE id=?',
              [cartInfo.cart_num, cartInfo.cart_num, cartInfo.productInfo.id]
          );

          if (!updateProduct.affectedRows) {
              throw new Error(
                  `更新产品表失败,【updateProduct】:${JSON.stringify(updateProduct)},【产品编号】:${
                      cartInfo.productInfo.id
                      },【cart_num】:${cartInfo.cart_num}`
              );
          }


          logger.info(
          `【支付通知】【订单编号】:${tradeNo},更新产品属性表成功,【产品编号】:${cartInfo.productInfo.id},【unique】:${cartInfo.productInfo.attrInfo.unique},【cart_num】:${cartInfo.cart_num}`
        );
      } else {
        const updateProduct = await connection.queryAsync(
          'UPDATE ct_store_product SET stock=stock-?,sales=sales+? WHERE id=?',
          [cartInfo.cart_num, cartInfo.cart_num, cartInfo.productInfo.id]
        );

        if (!updateProduct.affectedRows) {
          throw new Error(
            `更新产品表失败,【updateProduct】:${JSON.stringify(updateProduct)},【产品编号】:${
              cartInfo.productInfo.id
            },【cart_num】:${cartInfo.cart_num}`
          );
        }

        logger.info(
          `【支付通知】【订单编号】:${tradeNo},更新产品表成功,【产品编号】:${cartInfo.productInfo.id},【cart_num】:${cartInfo.cart_num}`
        );
      }
    }
  }
}

async function paySuccess(connection, info) {
  const orders = await connection.queryAsync('SELECT * FROM ct_store_order WHERE order_id=?', [
    info.out_trade_no
  ]);

  if (orders.length) {
    const order = orders[0];

    // 事务开始
    await connection.beginTransactionAsync();

    try {
      // demo环境实付金额设置为1分钱
      const payPrice = config.get('demo.payPrice') || order.pay_price;
      // 更改订单信息
      const updateOrderRes = await connection.queryAsync(
        'UPDATE ct_store_order SET paid=1,pay_type=?,pay_time=?,pay_price=?,trade_no=? WHERE order_id=?',
        ['wxpaymini', +new Date() / 1000, payPrice, info.transaction_id, info.out_trade_no]
      );
      if (updateOrderRes.affectedRows) {
        // 查询购物车信息
        const cartInfos = await connection.queryAsync(
          'SELECT cart_info,`unique` FROM ct_store_order_cart_info WHERE oid=?',
          [order.id]
        );

        // 更新产品库存
        await updateProductStock(connection, cartInfos, info.out_trade_no);
        // 更新用户购买次数
        await connection.queryAsync('UPDATE ct_user SET pay_count=pay_count+1 WHERE uid=?', [
          order.uid
        ]);
        // 添加用户付款状态
        await connection.queryAsync(
          'INSERT INTO ct_store_order_status(oid,change_type,change_message,change_time) VALUES(?,?,?,?)',
          [order.id, 'pay_success', '用户付款成功', +new Date() / 1000]
        );

        // 提交事务
        await connection.commitAsync();
        logger.info(`【支付通知】【订单编号】:${info.out_trade_no},事务处理成功`);

        return true;
      } else {
        throw new Error(`更改订单信息出错,【updateOrderRes】:${JSON.stringify(updateOrderRes)}`);
      }
    } catch (e) {
      logger.error(
        `【支付通知】【订单编号】:${info.out_trade_no},【事务处理出错】:${e.message}`
      );
      await connection.rollbackAsync();
    }
  } else {
    throw new Error('未找到订单');
  }
}

route.post('/wxpay/js/notify/pay', payApi.middleware('pay'), async ctx => {
  const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
  const info = ctx.request.weixin;

  // 更新回调记录表
  const strInfo = JSON.stringify(info);
  await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
    strInfo,
    utils.formatTime(new Date())
  ]);

  const logTradeNo = `【订单编号】:${info.out_trade_no}`;
  if (
    info.result_code.toUpperCase() === 'SUCCESS' &&
    info.return_code.toUpperCase() === 'SUCCESS'
  ) {
    try {
      // 获取订单
      const resCount = await connection.queryAsync(
        'SELECT COUNT(1) AS n FROM ct_store_order WHERE order_id=? AND paid=1',
        [info.out_trade_no]
      );

      if (resCount && resCount[0]['n']) {
        logger.warn(
          `【支付通知】${logTradeNo},该订单已支付,resCount${JSON.stringify(resCount)}`
        );
        ctx.reply('');
      } else {
        const success = await paySuccess(connection, info);
        if (success) {
          ctx.reply('');
        } else {
          ctx.reply('FAIL');
        }
      }
    } catch (e) {
      logger.error(`【支付通知】${logTradeNo} 【出错】:${e.message}`);
      await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
        `wxmp_${e.message}`,
        utils.formatTime(new Date())
      ]);

      ctx.reply('FAIL');
    } finally {
      // 释放数据库链接
      connection.release();
    }
  } else {
    // 支付未成功
    logger.error(`【支付通知】${logTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`);
    ctx.reply('');
  }
});

route.post('/wxpay/js/notify/refund', payApi.middleware('refund'), async ctx => {
  const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
  const info = { return_code: ctx.request.weixin.return_code, ...ctx.request.weixin.req_info };
  const tradeNo = info.out_trade_no;
  const refundTime = +new Date(info.success_time) / 1000;
  const logTradeNo = `【订单编号】:${tradeNo}`;

  if (info.return_code.toUpperCase() === 'SUCCESS') {
    try {
      // 更新回调记录表
      const strInfo = JSON.stringify(info);
      await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
        strInfo,
        utils.formatTime(new Date())
      ]);

      // 查询订单是否存在
      const result = await connection.queryAsync(
        'SELECT id,pay_price,refund_price,refund_status FROM ct_store_order WHERE order_id=?',
        [tradeNo]
      );
      if (result && result.length) {
        const order = result[0];
        const refundPrice = order.refund_price;
        // 在途金额
        const transitFee = (await ctx.redis.get(`wechat:refund:transit:${tradeNo}`)) || '0.00';
        // 在途金额金额+已退款金额
        const refundTotalFee = mathjs.evaluate(`${refundPrice}+${transitFee}/100`);

        //退款金额正常
        if (mathjs.evaluate(`${refundTotalFee}<=${order.pay_price}`)) {
          let updateRefundRes,
            refundStatus = order.refund_status;

          if (mathjs.evaluate(`${refundTotalFee}==${transitFee}/100`)) {
            refundStatus = 2;
          }

          logger.info(
            `【退款通知】${logTradeNo},【更新退款参数】:${JSON.stringify([
              refundStatus,
              refundTotalFee,
              refundTime,
              tradeNo
            ])}`
          );
          updateRefundRes = await connection.queryAsync(
            'UPDATE ct_store_order SET refund_status=?,refund_price=?,refund_reason_time=? WHERE order_id=?',
            [refundStatus, refundTotalFee, refundTime, tradeNo]
          );

          if (updateRefundRes.affectedRows) {
            // 更新在途金额
            await ctx.redis.decrby(`wechat:refund:transit:${tradeNo}`, info.settlement_refund_fee);
            // 清理缓存数据
            if (refundStatus == 2) {
              await ctx.redis.del(`wechat:refund:transit:${tradeNo}`);
              await ctx.redis.del(`wechat:refund:suffix:${tradeNo}`);
            }

            // 退款成功
            ctx.reply('');
            logger.info(`【退款通知】${logTradeNo},退款成功`);
          } else {
            // 更新退款信息失败
            throw new Error('更新退款信息失败');
          }
        } else {
          // 退款金额超出付款总额
          throw new Error('退款金额超出付款总额');
        }
      } else {
        throw new Error('订单不存在');
      }
    } catch (e) {
      // 退款记录入库错误
      logger.error(
        `【退款通知】${logTradeNo},【错误】:${e.message} 【业务处理错误】:${JSON.stringify(
          info
        )}`
      );
      await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
        `wxmp_${e.message}`,
        utils.formatTime(new Date())
      ]);

      ctx.reply('FAIL');
    } finally {
      // 释放数据库链接
      connection.release();
    }
  } else {
    // 退款未成功
    logger.error(`【退款通知】${logTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`);
    ctx.reply('');
  }
});

route.post('/wxpay/js/notify/pay_vip', payApi.middleware('pay'), async ctx => {
  const connection = Promise.promisifyAll(await ctx.pool.getConnectionAsync());
  const info = ctx.request.weixin;

  // 更新回调记录表
  const strInfo = JSON.stringify(info);
  await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
    strInfo,
    utils.formatTime(new Date())
  ]);

  const logUidAndTradeNo = `【用户编号】:${info.attach},【订单编号】:${info.out_trade_no}`;
  logger.info(`【支付食卡会员通知】${logUidAndTradeNo}`);
  if (
    info.result_code.toUpperCase() === 'SUCCESS' &&
    info.return_code.toUpperCase() === 'SUCCESS'
  ) {
    try {
      // 用户id
      const uId = parseInt(info.attach);
      // 获取订单
      const resCount = await getVipOrder(connection, uId, '支付食卡会员通知');
      if (!resCount) {
        ctx.reply('');
      } else {
        // 更新结果
        const success = updateVipOrder(connection, uId, resCount[0]);
        // 提交事务
        await connection.commitAsync();

        if (success) {
          logger.info(`【支付食卡会员通知】${logUidAndTradeNo} 【成功】`);
          ctx.reply('');
        } else {
          ctx.reply('FAIL');
        }
      }
    } catch (e) {
      // 回滚事务
      await connection.rollbackAsync();

      logger.error(`【支付食卡会员通知】${logUidAndTradeNo} 【出错】:${e.message}`);
      await connection.queryAsync('INSERT INTO ct_cs(text, times) VALUES(?,?)', [
        `wxmp_${e.message}`,
        utils.formatTime(new Date())
      ]);

      ctx.reply('FAIL');
    } finally {
      // 释放数据库链接
      connection.release();
    }
  } else {
    // 支付未成功
    logger.error(
      `【支付食卡会员通知】${logUidAndTradeNo} 【微信端业务处理错误】:${JSON.stringify(info)}`
    );
    ctx.reply('');
  }
});

module.exports = route;