'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', {}); // 支付结果全都存起来 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(); // 拆订单 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(); } } /** * 加销量 * @param {Object} order 支付订单数据 * @param {Transaction} tran 数据库事务 */ async addSell(order, tran) { const goods = _.get(order, 'goods', []); for (const sg of goods) { 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 }); } } } /** * 关闭订单 * @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;