'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 moment = require('moment'); const { ObjectId } = require('mongoose').Types; // class PayOrderService extends CrudService { constructor(ctx) { super(ctx, 'payorder'); this.model = this.ctx.model.Business.PayOrder; this.payService = this.ctx.service.wxpay; this.lessonStudentModel = this.ctx.model.Business.LessonStudent; this.lessonStudentService = this.ctx.service.business.lessonStudent; this.tempLessonApplyModel = this.ctx.model.Apply.TempLessonApply; this.userModel = this.ctx.model.User.User; this.studentModel = this.ctx.model.User.Student; this.coachModel = this.ctx.model.User.Coach; this.rcsModel = this.ctx.model.Relation.RelationCoachSchool; this.rssModel = this.ctx.model.Relation.RelationStudentSchool; this.billModel = this.ctx.model.Business.Bill; } async beforeCreate(data) { if (!_.get(data, 'order_no')) data.order_no = this.getOrderNo(); const { data: nd, next } = await this.checkSurplus(data); if (!next) throw new BusinessError(0, '扣除余额成功', nd); data = nd; return data; } // 检查是否扣除余额 async checkSurplus(data) { const pay_for = _.get(data, 'pay_for'); // 充值不检查余额 if (pay_for && pay_for === 'Bill') return { data, next: true }; const payer_id = _.get(data, 'payer_id'); const payer_role = _.get(data, 'payer_role'); const school_id = _.get(data, 'school_id'); let billData; let relation; // 没有要支付的人及其角色,那就找不到余额,返回下单 if (!(payer_id && payer_role)) return { data, next: true }; // 有支付的人,可以查查这个人的余额 if (payer_role === 'Student') relation = await this.rssModel.findOne({ student_id: payer_id, school_id }); else if (payer_role === 'Coach') relation = await this.rcsModel.findOne({ coach_id: payer_id, school_id }); // 没找到关系,那就直接返回正常下单缴费 if (!relation) return { data, next: true }; // 有关系,找余额 const { money: surplus } = relation; const { money } = data; // 没有余额,或者余额没钱,返回正常下单缴费 if (surplus && surplus <= 0) return { data, next: true }; // 有余额,看余额够不够 if (this.ctx.minus(surplus, money) >= 0) { try { // 余额够, 无需生成订单,直接去使用余额缴费 const bill = _.pick(data, [ 'school_id', 'payer_id', 'payer_role', 'pay_for', 'from_id' ]); // 本次扣除余额的金额就是本次需要支付的金额 bill.money = money; bill.type = '-2'; bill.is_pay = '1'; billData = await this.billModel.create({ ...bill, time: moment().format('YYYY-MM-DD HH:mm:ss') }); // 余额需要扣除本次支付的费用 relation.money = surplus - money; await relation.save(); // 前端生成报名数据 return { data: billData, next: false }; } catch (error) { // 抓取数据库创建和修改的异常, // 如果 billData没有值,说明是创建消费记录时发生的错误,不需要做回滚处理;如果有值,那就删除 if (billData) { await this.billModel.deleteOne({ _id: billData._id }); } // 如果是余额修改错误,上面的回滚删除就足够了.因为保存新余额发生错误,余额不会保存,只要把消费记录删了,就回到原样了 } } else if (surplus !== 0) { // 余额不足且不为0,在config里记录部分使用了余额,再进行下单,下单完后,生成余额的扣款 const needPay = this.ctx.minus(money, surplus); const bill = _.pick(data, [ 'school_id', 'payer_id', 'payer_role', 'pay_for', 'from_id' ]); bill.money = surplus; bill.type = '-2'; if (!data.config) data.config = {}; // 添加余额的设置, 等支付成功后,再生成余额的消费记录 data.config.useSurplus = true; data.config.bill = bill; data.money = needPay; } return { data, next: true }; } async afterCreate(body, data) { // await this.syncData(data); const wxSign = await this.payService.create(data); return { data, wxSign }; } async afterUpdate(filter, body, data) { await this.syncData(data); return data; } /** * 支付同步 * @param {Object} data 支付单数据 */ async syncData(data) { const pay_for = _.get(data, 'pay_for'); // 不知道该去同步哪个表的支付状态,不处理 if (!pay_for) return; const { from_id: _id, status: is_pay, money, _id: pay_id, config } = data; if (pay_for === 'lessonStudent') { // 因为上课产生的支付,去找lessonStudent,修改指定学生的支付状态 await this.lessonStudentModel.updateOne({ _id }, { is_pay, pay_id }); // 检查下各种记录 await this.makeRecord(data); } else if (pay_for === 'tempLessonApply') { // 私教课临时上课,需要到临时申请那找到相关数据 const tempApply = await this.tempLessonApplyModel.findById(_id); if (!tempApply) return; tempApply.pay_id = pay_id; await tempApply.save(); // 修改完申请,再创建 lessonStudent if (is_pay === '1') { const { lesson_id, student_id, school_id } = tempApply; const obj = { lesson_id, student_id, school_id, is_pay, pay_id, config, money }; await this.lessonStudentService.create(obj); } await this.makeRecord(data); } else if (pay_for === 'Bill' && is_pay !== '0') { // 充值记录,找到充值记录,没有就生成 const billData = await this.billModel.findOne({ pay_id }); if (billData) { // 有就修改 billData.is_pay = is_pay; await billData.save(); } else { const obj = _.pick(data, [ 'school_id', 'payer_id', 'payer_role' ]); await this.billModel.create({ ...obj, pay_for: 'Bill', pay_id, is_pay, time: moment().format('YYYY-MM-DD HH:mm:ss'), type: '1' }); } } } // 检查记录 async makeRecord(data) { const { config, _id: pay_id, status: is_pay, pay_for, from_id } = data; let billData; let wxBill; // 检查是否有本次微信支付部分的账单 wxBill = await this.billModel.findOne({ pay_id }); if (!wxBill) { // 没有微信支付账单,需要创建 const obj = _.pick(data, [ 'school_id', 'payer_id', 'payer_role', 'pay_for', 'from_id', 'time', 'money' ]); obj.type = '-1'; obj.pay_id = data._id; obj.is_pay = is_pay; wxBill = await this.billModel.create(obj); } // 使用余额的处理 if (_.get(config, 'useSurplus')) { const { bill } = config; // 使用了余额,但是余额记录不是直接生成的,需要检查下bill是否为ObjectId // 如果是ObjectId,说明余额记录已经生成.无需操作 if (_.isObject(bill)) { // 是数据,生成账单记录 billData = await this.billModel.create({ ...bill, is_pay, pay_for, from_id, time: moment().format('YYYY-MM-DD HH:mm:ss') }); if (billData) data.config.bill = ObjectId(billData._id).toString(); await this.model.updateOne({ _id: data._id }, data); // 创建账单后,扣余额 const { type, payer_role, payer_id, money, school_id } = billData; if (type === '-2') { let relation; if (payer_role === 'Student') relation = await this.rssModel.findOne({ student_id: payer_id, school_id }); else if (payer_role === 'Coach') relation = await this.rcsModel.findOne({ coach_id: payer_id, school_id }); relation.money = relation.money - money; await relation.save(); } } // 账单的退款是退款,支付是支付,各自生成数据,不需要修改 // else { // // 已经有数据了, 修改账单.并根据is_pay去做相应的处理 // await this.billModel.updateOne({ _id: data.config.bill }, { is_pay }); // billData = await this.billModel.findById(data.config.bill); // } } } /** * 重新支付 * @param {String} body 参数体 * @property {String} id 数据id */ async toRePay({ id }) { const data = await this.model.findById(id); if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到支付的信息'); // 找到这个订单,然后获取到订单号 const { order_no } = data; // 利用订单号去查询微信那边,该订单是否还存在 const wxOrder = await this.payService.search(order_no); let wxSign; if (_.get(wxOrder, 'trade_state') === 'NOTPAY') { // 未支付就用原数据,去支付 wxSign = await this.payService.create(data); } else if (_.get(wxOrder, 'trade_state') === 'SUCCESS') { throw new BusinessError(ErrorCode.SERVICE_FAULT, '订单已支付,无需重复支付'); } else if (_.get(wxOrder, 'trade_state') === 'USERPAYING') { throw new BusinessError(ErrorCode.SERVICE_FAULT, '订单支付中'); } else { // 其他情况就当订单被关闭了.直接新订单 const order_no = this.getOrderNo(); data.order_no = order_no; await data.save(); wxSign = await this.payService.create(data); } return { data, wxSign }; } /** * 申请退款 * @param {Object} body 参数体 * @property {String} id 支付单的数据id */ async toRefund({ id }) { // 查询支付 const data = await this.model.findById(id); if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到要退款的支付数据'); const wxOrderReq = await this.payService.search(data.order_no); if (wxOrderReq) { if (!wxOrderReq) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未查询到微信支付订单'); const wxRefundReq = await this.payService.refund(data.order_no, '退赛'); if (wxRefundReq) { if (wxRefundReq.status !== 'REFUND') throw new BusinessError(ErrorCode.SERVICE_FAULT, '退款失败'); // 退款成功 data.status = '-3'; await data.save(); await this.syncData(data); return 'ok'; } } throw new BusinessError(ErrorCode.SERVICE_FAULT, '查询微信支付订单失败'); } /** * 关闭订单 * @param {String} id 支付id * @return {Object} 关闭订单结果 */ async closeOrder(id) { const data = await this.model.findById(id); if (!data) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到要退款的支付数据'); const res = await this.payService.close(data.order_no); return res; } getOrderNo() { return `ONCAPP${moment().format('YYYYMMDDHHmmss')}-${_.random(1, 999999)}`; } // 二维码扫码 async toPayNative(body) { body = await this.beforeCreate(body); const data = await this.model.create(body); const qrcode = await this.payService.createNative({ ...body, notice_url: '/newCourt/v2/api/payOrder/nativeReturn' }); return qrcode; } // 二维码扫码回调 async nativeReturn(body) { const result = _.get(body, 'result'); if (!result) throw new BusinessError(ErrorCode.BADPARAM, '参数错误'); const { out_trade_no: order_no, trade_state, payer } = result; const obj = {}; if (trade_state === 'SUCCESS') obj.status = '1'; else if (trade_state === 'REFUND') obj.status = '-3'; const openid = _.get(payer, 'openid'); if (openid) { obj.openid = openid; // 找payer_id的信息,学员,教练 const user = await this.userModel.findOne({ openid }); if (user) { const { _id: user_id } = user; let roleUser = await this.studentModel.findOne({ user_id }); if (!roleUser) { roleUser = await this.coachModel.findOne({ user_id }); if (roleUser) { obj.payer_role = 'Coach'; obj.payer_id = roleUser._id; } } else { obj.payer_role = 'Student'; obj.payer_id = roleUser._id; } } } await this.model.updateOne({ order_no }, obj); const data = await this.model.findOne({ order_no }); await this.syncData(data); } } module.exports = PayOrderService;