pay.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. 'use strict';
  2. const { CrudService } = require('naf-framework-mongoose-free/lib/service');
  3. const { BusinessError, ErrorCode } = require('naf-core').Error;
  4. const _ = require('lodash');
  5. const assert = require('assert');
  6. const Transaction = require('mongoose-transactions');
  7. const moment = require('moment');
  8. //
  9. class PayService extends CrudService {
  10. constructor(ctx) {
  11. super(ctx, 'pay');
  12. this.httpUtil = this.ctx.service.util.httpUtil;
  13. this.appConfig = 'pointApp';
  14. this.orderModel = this.ctx.model.Trade.Order;
  15. this.dictDataModel = this.ctx.model.Dev.DictData;
  16. this.payOrderReturnUrl = this.app.config.payReturn.order;
  17. this.wxDomain = _.get(this.app, 'config.httpPrefix.wechat');
  18. this.tran = new Transaction();
  19. }
  20. /**
  21. * 去支付订单
  22. * 1.有支付方式之分; 微信/支付宝
  23. * 2.根据不同方式去请求
  24. * @param {Object} body 请求体
  25. * @param body.order_id 订单id
  26. * @param body.type 支付方式
  27. */
  28. async toPayOrder({ order_id, type = '0' }) {
  29. const payWay = await this.dictDataModel.findOne({ value: type, status: '0' });
  30. if (!payWay) throw new BusinessError(ErrorCode.DATA_INVALID, '该支付方式暂时无法使用');
  31. const order = await this.orderModel.findById(order_id);
  32. if (!order) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到订单数据');
  33. const { no, pay, stauts } = order;
  34. let rePayTimes = 0;
  35. if (stauts === '1') throw new BusinessError(ErrorCode.DATA_EXISTED, '订单已支付,无需重复支付');
  36. // 检查是否有支付信息如果有的话,支付方式是否一致
  37. if (_.isObject(pay) && Object.keys(pay).length > 0) {
  38. const pay_no = _.get(pay, 'pay_no');
  39. if (pay_no) {
  40. // 有支付订单.则先查支付订单是否可以继续支付
  41. // 1.如果有支付信息及支付单号,直接关闭.重开
  42. const arr = pay_no.split('-');
  43. // 获取重支付的次数,重支付次数+1
  44. const last = _.last(arr);
  45. rePayTimes = this.ctx.plus(last, 1);
  46. }
  47. await this.closeOrder(pay);
  48. }
  49. // 没有支付信息(要是有支付信息,上面直接return了.漏下来的都是没有的处理方案)
  50. const str = this.ctx.service.util.trade.createNonceStr();
  51. const arr = no.split('-');
  52. // 订单中pay的信息
  53. const payObject = { pay_type: type, pay_no: `${_.last(arr)}-${str}-${rePayTimes}` };
  54. const totalMoney = this.getOrderNeedPay(order);
  55. payObject.pay_money = totalMoney;
  56. let payData;
  57. let res;
  58. // 找到当前用户的openid:这里涉及问题是: 如果自己下单,自己付款.那没有问题;
  59. // 如果是自己下单.如果使用账号密码登录再付款.还用下单的人找到的openid就不能在这个微信号上进行支付了;
  60. // 所以此处是需要用当前用户的openid进行支付,如果之前生成单子.还需要检查当前用户的openid和之前的openid是否一致
  61. // 如果不一致.则需要将之前的订单关闭,重新生成
  62. // 请求微信支付接口的数据
  63. if (type === '0') {
  64. payData = this.getWxPayData(order_id, totalMoney, payObject.pay_no);
  65. payObject.openid = payData.openid;
  66. }
  67. try {
  68. this.tran.update('Order', order_id, { pay: payObject });
  69. await this.tran.run();
  70. } catch (error) {
  71. await this.tran.rollback();
  72. console.error(error);
  73. }
  74. if (totalMoney <= 0) {
  75. // 小于等于0的支付金额,不需要付款
  76. return { needPay: false };
  77. }
  78. if (type === '0') {
  79. res = await this.create(payData);
  80. res = this.preparToUniAppWxPay(res);
  81. }
  82. return res;
  83. }
  84. async withoutPay({ order_id }) {
  85. try {
  86. this.tran.update('Order', order_id, { status: '1' });
  87. await this.tran.run();
  88. // 拆订单
  89. await this.ctx.service.trade.orderDetail.create({ order_id }, this.tran);
  90. await this.tran.run();
  91. } catch (error) {
  92. console.error(error);
  93. await this.tran.rollback();
  94. throw new BusinessError(ErrorCode.SERVICE_FAULT, '支付回调:修改失败');
  95. } finally {
  96. // 清空事务
  97. this.tran.clean();
  98. }
  99. }
  100. /**
  101. * 支付订单回调函数
  102. * @param {Object} body 请求地址参数
  103. * @param body.result 支付回调结果
  104. */
  105. async callBackPayOrder({ result }) {
  106. const { out_trade_no, payer } = result;
  107. // TODO: 没有需要报警,没有订单号就出问题了
  108. if (!out_trade_no) {
  109. console.error('没有支付订单号');
  110. return;
  111. }
  112. const openid = _.get(payer, 'openid');
  113. const query = { 'pay.pay_no': new RegExp(`${out_trade_no}`), 'pay.openid': openid };
  114. let orderData = await this.orderModel.findOne(query);
  115. // TODO: 没找到订单也是有问题的,需要报警
  116. if (!orderData) {
  117. console.error('没有找到订单');
  118. return;
  119. }
  120. orderData = JSON.parse(JSON.stringify(orderData));
  121. const payData = _.get(orderData, 'pay', {});
  122. // 支付结果全都存起来
  123. payData.result = result;
  124. // 支付时间
  125. payData.pay_time = moment().format('YYYY-MM-DD HH:mm:ss');
  126. // 修改状态
  127. try {
  128. this.tran.update('Order', orderData._id, { pay: payData, status: '1' });
  129. await this.tran.run();
  130. // 拆订单
  131. await this.ctx.service.trade.orderDetail.create({ order_id: orderData._id }, this.tran);
  132. await this.tran.run();
  133. } catch (error) {
  134. console.error(error);
  135. await this.tran.rollback();
  136. throw new BusinessError(ErrorCode.SERVICE_FAULT, '支付回调:修改失败');
  137. } finally {
  138. // 清空事务
  139. this.tran.clean();
  140. }
  141. }
  142. /**
  143. * 关闭订单
  144. * @param {Object} pay 支付信息
  145. */
  146. async closeOrder(pay) {
  147. const { pay_type, pay_no } = pay;
  148. if (pay_type === '0') {
  149. // 微信支付方式
  150. const params = { config: this.appConfig, order_no: pay_no };
  151. const url = `${this.wxDomain}/pay/closeOrder`;
  152. const res = await this.httpUtil.cpost(url, params);
  153. return res || 'ok';
  154. }
  155. }
  156. /**
  157. * 微信支付:整理出uniapp需要的数据
  158. * @param {Object} data 微信接口的数据
  159. */
  160. preparToUniAppWxPay(data) {
  161. const obj = {
  162. // appid: _.get(data, 'appid'),
  163. // prepayid: _.get(data, 'prepay_id'),
  164. nonceStr: _.get(data, 'nonceStr'),
  165. package: `prepay_id=${_.get(data, 'prepay_id')}`,
  166. signType: _.get(data, 'signType'),
  167. timeStamp: _.get(data, 'timestamp'),
  168. paySign: _.get(data, 'paySign'),
  169. };
  170. return obj;
  171. }
  172. /**
  173. * 组织微信支付数据
  174. * @param {String} order_id 订单号
  175. * @param {Number} money 支付金额
  176. * @param {String} no 支付订单号
  177. */
  178. getWxPayData(order_id, money, no) {
  179. const openid = _.get(this.ctx, 'user.openid');
  180. const data = { config: this.appConfig, money, openid, order_no: no, desc: '购物', notice_url: this.payOrderReturnUrl(order_id) };
  181. return data;
  182. }
  183. /**
  184. * 计算订单需支付的金额
  185. * @param {Object} order 要支付的订单数据
  186. */
  187. getOrderNeedPay(order) {
  188. let total = 0;
  189. const { total_detail = {} } = order;
  190. const { discount_detail, ...others } = total_detail;
  191. for (const key in others) {
  192. total = this.ctx.plus(total_detail[key], total);
  193. }
  194. // 优惠券部分
  195. if (discount_detail) {
  196. let totalDiscount = 0;
  197. for (const uc in discount_detail) {
  198. // uc:用户领取的优惠券id
  199. // detail: 该优惠券下的明细
  200. const detail = discount_detail[uc];
  201. if (!detail) continue;
  202. totalDiscount = this.ctx.plus(totalDiscount, Object.values(detail).reduce((p, n) => this.ctx.plus(p, n), 0));
  203. }
  204. console.log(total, totalDiscount);
  205. total = this.ctx.minus(total, totalDiscount);
  206. console.log(total);
  207. if (total <= 0) total = 0;
  208. }
  209. return total;
  210. }
  211. /**
  212. * 查询订单
  213. * @param {String} order_no 订单号
  214. */
  215. async search(order_no) {
  216. assert(order_no, '缺少订单号,无法查询订单信息');
  217. const params = { config: this.appConfig, order_no };
  218. const url = `${this.wxDomain}/pay/searchOrderByOrderNo`;
  219. const wxOrderReq = await this.httpUtil.cpost(url, params);
  220. return wxOrderReq;
  221. }
  222. /**
  223. * 创建订单,获取微信支付签名
  224. * @param {Object} data 数据
  225. */
  226. async create(data) {
  227. const { money, openid, order_no, desc, notice_url } = data;
  228. const wxOrderData = { config: this.appConfig, money, openid, order_no, desc, notice_url };
  229. const url = `${this.wxDomain}/pay/payOrder`;
  230. const res = await this.httpUtil.cpost(url, wxOrderData);
  231. if (res) return res;
  232. throw new BusinessError(ErrorCode.SERVICE_FAULT, '微信下单失败!');
  233. }
  234. /**
  235. * 关闭订单
  236. * @param {String} order_no 订单号
  237. */
  238. async close(order_no) {
  239. assert(order_no, '缺少订单号,无法查询订单信息');
  240. const params = { config: this.appConfig, order_no };
  241. const url = `${this.wxDomain}/pay/closeOrder`;
  242. const res = await this.httpUtil.cpost(url, params);
  243. return res || 'ok';
  244. }
  245. /**
  246. * TODO 退款,金额需要指定.可能是部分退款,也可能是全额退款
  247. * @param {String} order_no 支付订单号
  248. * @param {String} out_refund_no 退款单号
  249. * @param {String} money 退款金额
  250. * @param {String} reason 原因
  251. */
  252. async refund({ order_no, out_refund_no, money, reason }) {
  253. assert(order_no, '缺少订单号,无法查询订单信息');
  254. assert(out_refund_no, '缺少退款单号,无法退款');
  255. const url = `${this.wxDomain}/pay/refundOrder`;
  256. const params = { config: this.appConfig, order_no, out_refund_no, money, reason };
  257. const wxRefundReq = await this.httpUtil.cpost(url, params);
  258. return wxRefundReq || 'ok';
  259. }
  260. }
  261. module.exports = PayService;