'use strict'; const { CrudService } = require('naf-framework-mongoose/lib/service'); const { BusinessError, ErrorCode } = require('naf-core').Error; const _ = require('lodash'); const assert = require('assert'); const random = require('string-random'); const moment = require('moment'); const crypto = require('crypto'); const xml2js = require('xml2js'); const fs = require('fs'); // wxpay class WxpayService extends CrudService { constructor(ctx) { super(ctx, 'wxpay'); this.appInfo = this.app.config.appInfo; this.redis = this.app.redis; this.model = this.ctx.model.Card; this.cashModel = this.ctx.model.Cash; this.cashError = this.ctx.model.CashError; } async toAuth({ code, id }) { const token = await this.redis.get(id); if (token) return; const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${this.appInfo.id}&secret=${this.appInfo.secret}&js_code=${code}&grant_type=authorization_code`; const res = await this.ctx.curl(url, { method: 'get', dataType: 'json', }); const info = JSON.stringify(res.data); await this.redis.set(id, info, 'EX', 3600); } async cash({ id, money, points }) { money = parseInt(money); const token = await this.redis.get(id); if (!token) throw new BusinessError(ErrorCode.SERVICE_FAULT, '未找到用户的openid,请重新登陆小程序!'); const { openid } = JSON.parse(token); if (!this.appInfo) throw new BusinessError(ErrorCode.FILE_FAULT, '未设置小程序相关设置'); const user = await this.model.findById(id); if (!user) throw new BusinessError(ErrorCode.DATA_NOT_EXIST, '未找到用户信息!'); const opoints = _.get(user, 'points', 0); const pres = _.subtract(opoints, points); if (pres < 0) throw new BusinessError(ErrorCode.BADPARAM, '积分不足'); // check_name:校验真名,暂不校验 const object = { amount: money, check_name: 'NO_CHECK', desc: '提现', mch_appid: this.appInfo.id, mchid: this.appInfo.store, nonce_str: random(16), openid, partner_trade_no: `cash${moment().format('YYYYMMDDHHmmss')}${random(12)}`, re_user_name: user.name }; const clientIp = _.get(this.ctx.request, 'header.x-real-ip'); if (clientIp) object.spbill_create_ip = clientIp; const sign = this.turnToSign(object); const xml = ` ${this.appInfo.id} ${this.appInfo.store} ${object.nonce_str} ${object.partner_trade_no} ${openid} NO_CHECK ${user.name} ${money} 提现 ${clientIp ? '' : ''} ${sign} `; const url = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; const res = await this.ctx.curl(url, { method: 'post', data: xml, dataType: 'xml', key: fs.readFileSync('cert/apiclient_key.pem'), cert: fs.readFileSync('cert/apiclient_cert.pem'), }); const data = res.data; const parse = new xml2js.Parser({ trim: true, explicitArray: false, explicitRoot: false }); const result = await parse.parseStringPromise(data); // TODO 添加错误记录 const { return_code, return_msg, result_code, err_code, err_code_des } = result; // 请求是否成功 if (return_code !== 'SUCCESS') { this.cashError.create({ word: `${return_code}:${return_msg}`, create_time: moment().format('YYYY-MM-DD HH:mm:ss') }); throw new BusinessError(ErrorCode.SERVICE_FAULT, `${return_code}:${return_msg}`); } // 请求失败具体问题 if (result_code === 'FAIL') { this.cashError.create({ word: `${err_code}:${err_code_des}`, create_time: moment().format('YYYY-MM-DD HH:mm:ss') }); throw new BusinessError(ErrorCode.SERVICE_FAULT, `${err_code}:${err_code_des}`); } // 成功,添加记录=>减去积分 const cashRecord = { name: user.name, mobile: user.mobile, b_point: opoints, i_point: points, e_point: pres }; try { await this.cashModel.create({ ...cashRecord, create_time: moment().format('YYYY-MM-DD HH:mm:ss') }); } catch (error) { const word = '添加提现记录失败!但钱已转出.'; this.cashError.create({ word, create_time: moment().format('YYYY-MM-DD HH:mm:ss') }); throw new BusinessError(ErrorCode.SERVICE_FAULT, word); } user.points = pres; try { await user.save(); } catch (error) { const word = '用户减掉已转出积分失败!钱已转出;提现记录已添加'; this.cashError.create({ word, create_time: moment().format('YYYY-MM-DD HH:mm:ss') }); throw new BusinessError(ErrorCode.SERVICE_FAULT, word); } let newUser = await this.model.findById(id); newUser = JSON.parse(JSON.stringify(newUser)); const { meta, __v, ...userInfo } = newUser; return userInfo; } turnToSign(object) { const _arr = []; for (const key in object) { _arr.push(key + '=' + object[key]); } const str = `${_arr.join('&')}&key=${this.appInfo.storeKey}`; return crypto.createHash('md5').update(str, 'utf8').digest('hex') .toUpperCase(); } NoticeCode(code) { const arr = [ 'NOTENOUGH', 'SYSTEMERROR', 'NAME_MISMATCH', 'SIGN_ERROR', 'FREQ_LIMIT', 'MONEY_LIMIT', 'CA_ERROR', 'V2_ACCOUNT_SIMPLE_BAN', 'PARAM_IS_NOT_UTF8', 'SENDNUM_LIMIT' ]; return arr; } } module.exports = WxpayService;