123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- 'use strict';
- const { CrudService } = require('naf-framework-mongoose-free/lib/service');
- const { BusinessError, ErrorCode } = require('naf-core').Error;
- const _ = require('lodash');
- const assert = require('assert');
- const Transaction = require('mongoose-transactions');
- const moment = require('moment');
- //
- class PayService extends CrudService {
- constructor(ctx) {
- super(ctx, 'pay');
- this.httpUtil = this.ctx.service.util.httpUtil;
- this.appConfig = this.app.config.wxPayConfig;
- this.goodsModel = this.ctx.model.Shop.Goods;
- this.orderModel = this.ctx.model.Trade.Order;
- this.dictDataModel = this.ctx.model.Dev.DictData;
- this.payOrderReturnUrl = this.app.config.payReturn.order;
- this.wxDomain = _.get(this.app, 'config.httpPrefix.wechat');
- this.tran = new Transaction();
- }
- /**
- * 去支付订单
- * 1.有支付方式之分; 微信/支付宝
- * 2.根据不同方式去请求
- * @param {Object} body 请求体
- * @param body.order_id 订单id
- * @param body.type 支付方式
- */
- async toPayOrder({ order_id, type = '0' }) {
- const payWay = await this.dictDataModel.findOne({ value: type, status: '0' });
- if (!payWay) throw new BusinessError(ErrorCode.DATA_INVALID, '该支付方式暂时无法使用');
- const order = await this.orderModel.findById(order_id);
- if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单数据');
- const { no, pay, stauts } = order;
- let rePayTimes = 0;
- if (stauts === '1') throw new BusinessError(ErrorCode.DATA_EXISTED, '订单已支付,无需重复支付');
- // 检查是否有支付信息如果有的话,支付方式是否一致
- if (_.isObject(pay) && Object.keys(pay).length > 0) {
- const pay_no = _.get(pay, 'pay_no');
- if (pay_no) {
- // 有支付订单.则先查支付订单是否可以继续支付
- // 1.如果有支付信息及支付单号,直接关闭.重开
- const arr = pay_no.split('-');
- // 获取重支付的次数,重支付次数+1
- const last = _.last(arr);
- rePayTimes = this.ctx.plus(last, 1);
- }
- await this.closeOrder(pay);
- }
- // 没有支付信息(要是有支付信息,上面直接return了.漏下来的都是没有的处理方案)
- const str = this.ctx.service.util.trade.createNonceStr();
- const arr = no.split('-');
- // 订单中pay的信息
- const payObject = { pay_type: type, pay_no: `${_.last(arr)}-${str}-${rePayTimes}` };
- const totalMoney = this.ctx.service.util.order.payOrder_RealPay(order);
- payObject.pay_money = totalMoney;
- let payData;
- let res;
- // 找到当前用户的openid:这里涉及问题是: 如果自己下单,自己付款.那没有问题;
- // 如果是自己下单.如果使用账号密码登录再付款.还用下单的人找到的openid就不能在这个微信号上进行支付了;
- // 所以此处是需要用当前用户的openid进行支付,如果之前生成单子.还需要检查当前用户的openid和之前的openid是否一致
- // 如果不一致.则需要将之前的订单关闭,重新生成
- // 请求微信支付接口的数据
- if (type === '0') {
- payData = this.getWxPayData(order_id, totalMoney, payObject.pay_no);
- payObject.openid = payData.openid;
- }
- try {
- this.tran.update('Order', order_id, { pay: payObject });
- await this.tran.run();
- } catch (error) {
- await this.tran.rollback();
- console.error(error);
- }
- if (totalMoney <= 0) {
- // 小于等于0的支付金额,不需要付款
- return { needPay: false };
- }
- if (type === '0') {
- res = await this.create(payData);
- res = this.preparToUniAppWxPay(res);
- }
- return res;
- }
- async withoutPay({ order_id }) {
- try {
- const orderData = await this.orderModel.findById(order_id);
- this.tran.update('Order', order_id, { status: '1' });
- await this.tran.run();
- // 拆订单
- await this.ctx.service.trade.orderDetail.create({ order_id }, this.tran);
- // 加销量
- await this.addSell(orderData, this.tran);
- await this.tran.run();
- } catch (error) {
- console.error(error);
- await this.tran.rollback();
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '支付回调:修改失败');
- } finally {
- // 清空事务
- this.tran.clean();
- }
- }
- /**
- * 支付订单回调函数
- * @param {Object} body 请求地址参数
- * @param body.result 支付回调结果
- */
- async callBackPayOrder({ result }) {
- const { out_trade_no, payer } = result;
- // 没有需要报警,没有订单号就出问题了
- if (!out_trade_no) {
- console.error('没有支付订单号');
- return;
- }
- const openid = _.get(payer, 'openid');
- const query = { 'pay.pay_no': new RegExp(`${out_trade_no}`), 'pay.openid': openid };
- let orderData = await this.orderModel.findOne(query);
- // 没找到订单也是有问题的,需要报警
- if (!orderData) {
- console.error('没有找到订单');
- return;
- }
- orderData = JSON.parse(JSON.stringify(orderData));
- const payData = _.get(orderData, 'pay', {});
- let odIds = [];
- // 支付结果全都存起来
- payData.result = result;
- // 支付时间
- payData.pay_time = moment().format('YYYY-MM-DD HH:mm:ss');
- // 修改状态
- try {
- this.tran.update('Order', orderData._id, { pay: payData, status: '1' });
- await this.tran.run();
- // 拆订单
- odIds = await this.ctx.service.trade.orderDetail.create({ order_id: orderData._id }, this.tran);
- // 加销量
- await this.addSell(orderData, this.tran);
- await this.tran.run();
- // TODO: 将该订单的数据在mq队列中释放掉,不要让信息进入死信队列
- } catch (error) {
- console.error(error);
- await this.tran.rollback();
- const reason = '服务处理发生错误,原路退款!';
- // 先退款,所有回调发生错误的单子都需要退掉
- const str = this.ctx.service.util.trade.createNonceStr();
- const obj = {
- order_no: _.get(orderData, 'pay.pay_no'),
- out_refund_no: `${_.get(orderData, 'pay.pay_no')}-service_error-${str}`,
- money: _.get(orderData, 'pay.pay_money'),
- reason,
- };
- await this.refund(obj);
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '支付回调:修改失败');
- } finally {
- // 清空事务
- this.tran.clean();
- }
- // 发送系统消息,让对应的店铺接收消息
- const msgData = { source_id: odIds, type: '0' };
- await this.ctx.service.shop.shopNotice.remindToSend(msgData);
- }
- /**
- * 加销量
- * @param {Object} order 支付订单数据
- * @param {Transaction} tran 数据库事务
- */
- async addSell(order, tran) {
- const goods = _.get(order, 'goods', []);
- for (const sg of goods) {
- const { is_set = '1' } = sg;
- if (is_set === '1') {
- const sgList = _.get(sg, 'goods', []);
- for (const g of sgList) {
- const buy_num = _.get(g, 'buy_num', 0);
- const goods_id = _.get(goods, '_id');
- if (!goods_id) return;
- const goodsInfo = await this.goodsModel.findById(goods_id, { sell_num: 1 });
- const newSell_num = this.ctx.plus(buy_num, _.get(goodsInfo, 'sell_num'));
- tran.update('Goods', goods_id, { sell_num: newSell_num });
- }
- } else {
- const { buy_num } = sg;
- const sgList = _.get(sg, 'goods', []);
- for (const g of sgList) {
- const goods_id = _.get(g, 'goods._id');
- const set_num = _.get(g, 'set_num');
- const goodsInfo = await this.goodsModel.findById(goods_id, { sell_num: 1 });
- const newNum = this.ctx.plus(_.get(goodsInfo, 'sell_num'), this.ctx.multiply(buy_num, set_num));
- tran.update('Goods', goods_id, { sell_num: newNum });
- }
- }
- }
- }
- /**
- * 关闭订单
- * @param {Object} pay 支付信息
- */
- async closeOrder(pay) {
- const { pay_type, pay_no } = pay;
- if (pay_type === '0') {
- // 微信支付方式
- const params = { config: this.appConfig, order_no: pay_no };
- const url = `${this.wxDomain}/pay/closeOrder`;
- const res = await this.httpUtil.cpost(url, params);
- return res || 'ok';
- }
- }
- /**
- * 微信支付:整理出uniapp需要的数据
- * @param {Object} data 微信接口的数据
- */
- preparToUniAppWxPay(data) {
- const obj = {
- // appid: _.get(data, 'appid'),
- // prepayid: _.get(data, 'prepay_id'),
- nonceStr: _.get(data, 'nonceStr'),
- package: `prepay_id=${_.get(data, 'prepay_id')}`,
- signType: _.get(data, 'signType'),
- timeStamp: _.get(data, 'timestamp'),
- paySign: _.get(data, 'paySign'),
- };
- return obj;
- }
- /**
- * 组织微信支付数据
- * @param {String} order_id 订单号
- * @param {Number} money 支付金额
- * @param {String} no 支付订单号
- */
- getWxPayData(order_id, money, no) {
- const openid = _.get(this.ctx, 'user.openid');
- const data = { config: this.appConfig, money, openid, order_no: no, desc: '购物', notice_url: this.payOrderReturnUrl };
- return data;
- }
- /**
- * 查询订单
- * @param {String} order_no 订单号
- */
- async search(order_no) {
- assert(order_no, '缺少订单号,无法查询订单信息');
- const params = { config: this.appConfig, order_no };
- const url = `${this.wxDomain}/pay/searchOrderByOrderNo`;
- const wxOrderReq = await this.httpUtil.cpost(url, params);
- return wxOrderReq;
- }
- /**
- * 创建订单,获取微信支付签名
- * @param {Object} data 数据
- */
- async create(data) {
- const { money, openid, order_no, desc, notice_url } = data;
- const wxOrderData = { config: this.appConfig, money, openid, order_no, desc, notice_url };
- const url = `${this.wxDomain}/pay/payOrder`;
- const res = await this.httpUtil.cpost(url, wxOrderData);
- if (res) return res;
- throw new BusinessError(ErrorCode.SERVICE_FAULT, '微信下单失败!');
- }
- /**
- * 关闭订单
- * @param {String} order_no 订单号
- */
- async close(order_no) {
- assert(order_no, '缺少订单号,无法查询订单信息');
- const params = { config: this.appConfig, order_no };
- const url = `${this.wxDomain}/pay/closeOrder`;
- const res = await this.httpUtil.cpost(url, params);
- return res || 'ok';
- }
- /**
- * TODO 退款,金额需要指定.可能是部分退款,也可能是全额退款
- * @param {String} order_no 支付订单号
- * @param {String} out_refund_no 退款单号
- * @param {String} money 退款金额
- * @param {String} reason 原因
- */
- async refund({ order_no, out_refund_no, money, reason }) {
- assert(order_no, '缺少订单号,无法查询订单信息');
- assert(out_refund_no, '缺少退款单号,无法退款');
- const url = `${this.wxDomain}/pay/refundOrder`;
- const params = { config: this.appConfig, order_no, out_refund_no, money, reason };
- const wxRefundReq = await this.httpUtil.cpost(url, params);
- return wxRefundReq || 'ok';
- }
- }
- module.exports = PayService;
|