'use strict'; const Service = require('egg').Service; const querystring = require('querystring'); // 微信接口操作类 class WechatService extends Service { /** * [cacheAccessTokenKey 微信公众号access_token key] * @return {[type]} [description] */ get cacheAccessTokenKey() { return `${process.env.APP_CUSTOME || 'universal'}_wechat_account_access_token`; } /** * [cacheJsapiTicketKey 缓存jsapi ticket key] * @return {[type]} [description] */ get cacheJsapiTicketKey() { return `${process.env.APP_CUSTOME || 'universal'}_jsapi_ticket`; } /** * [msgText 响应文本消息] * @param {String} text [回复内容] * @param {Object} data [原消息结构] * @return {[type]} [string] */ async msgText(text = '', data = {}) { const that = this; const result = { FromUserName: data.ToUserName, ToUserName: data.FromUserName, CreateTime: that.app.szjcomo.time(), MsgType: 'text', Content: text, }; return await that.app.szjcomo.createXml(result); } /** * [msgNews 回复图文消息] * @param {Array} list [description] * @param {Object} data [description] * @return {[type]} [description] */ async msgNews(list = [], data = {}, fields = {}) { const that = this; const fieldsMap = Object.assign({ title: 'title', description: 'description', image_path: 'image_path', link_url: 'link_url', }, fields); const Articles = []; list.forEach(item => { Articles.push({ Title: item[fieldsMap.title], Description: item[fieldsMap.description] || item[fieldsMap.title], PicUrl: item[fieldsMap.image_path] || '', Url: item[fieldsMap.link_url] || '', }); }); const result = { FromUserName: data.ToUserName, ToUserName: data.FromUserName, CreateTime: that.app.szjcomo.time(), MsgType: 'news', ArticleCount: list.length, Articles: { item: Articles }, }; return await that.app.szjcomo.createXml(result); } /** * [publicAccessToken 获取众号的access_token] * @param {[type]} appid [description] * @param {[type]} secret [description] * @return {[type]} [description] */ async publicAccessToken(appid, secret) { const that = this; appid = appid || await that.service.configs.getConfigValue('wechat_account_appid'); secret = secret || await that.service.configs.getConfigValue('wechat_account_secret'); let result = await that.service.redis.get(that.cacheAccessTokenKey); if (!result) { const uri = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`; const respone = await that.app.curl(uri, { dataType: 'json' }); if (respone.data && respone.data.access_token) { result = respone.data.access_token; await that.service.redis.set(that.cacheAccessTokenKey, result, 1.5 * 60 * 60); } else { throw new Error(that.app.szjcomo.json_encode(respone.data)); } } return result; } /** * [authLogin 网页用户授权登录] * @param {[type]} appid [description] * @param {[type]} secret [description] * @param {String} code [description] * @return {[type]} [description] */ async authLogin(appid, secret, code = '') { const that = this; const uri = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`; const respone = await that.app.curl(uri, { dataType: 'json' }); return respone.data; } /** * [authUserInfo 获取用户信息] * @param {[type]} access_token [description] * @param {[type]} openid [description] * @return {[type]} [description] */ async authUserInfo(data = {}) { const that = this; const uri = `https://api.weixin.qq.com/sns/userinfo?access_token=${data.access_token}&openid=${data.openid}&lang=zh_CN`; const respone = await that.app.curl(uri, { dataType: 'json' }); return respone.data; } /** * [jsapiTicket 获取微信公众号jsapi_ticket] * @author szjcomo * @date 2021-05-16 * @return {[type]} [description] */ async jsapiTicket() { const that = this; const access_token = await that.publicAccessToken(); let result = await that.service.redis.get(that.cacheJsapiTicketKey); if (!result) { const uri = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi`; const res = await that.app.curl(uri, { method: 'GET', dataType: 'json' }); if (res.data && res.data.ticket) { result = res.data.ticket; await that.service.redis.set(that.cacheJsapiTicketKey, result, 1.5 * 60 * 60); } else { throw new Error(that.app.szjcomo.json_encode(res.data)); } } return result; } /** * [jsapiConfig 获取微信公众号jsapi_config] * @author szjcomo * @date 2021-05-16 * @return {[type]} [description] */ async jsapiConfig(urlData = {}) { const that = this; const configs = await that.service.configs.getConfigMoreValue([ 'wechat_account_appid', 'wechat_account_jsapi_uri', 'wechat_account_jsapi_list', 'wechat_account_jsapi_debug' ]); const jsApiList = configs.wechat_account_jsapi_list.split(','); const data = { nonceStr: that.app.szjcomo.str_rand(16), timestamp: that.app.szjcomo.time(), url: urlData.url || configs.wechat_account_jsapi_uri, debug: !!Number(configs.wechat_account_jsapi_debug), jsApiList, }; const jsapi_ticket = await that.jsapiTicket(); const configSign = `jsapi_ticket=${jsapi_ticket}&noncestr=${data.nonceStr}×tamp=${data.timestamp}&url=${data.url}`; data.signature = that.app.szjcomo.sha1(configSign); data.appId = configs.wechat_account_appid; return data; } /** * [orderPay 订单下单支付] * @param {Object} data [description] * @return {[type]} [description] */ async orderPay(data = {}) { const that = this; const config = await that.service.configs.getConfigMoreValue([ 'wechat_pay_token', 'wechat_pay_domain', 'wechat_pay_callback' ]); const defaults = { identity: config.wechat_pay_token, total_fee: 1, notify_url: config.wechat_pay_callback, detail: '', trade_type: 'NATIVE', }; const bodyData = Object.assign(defaults, data); // 2022/12/09 微信最终下单价格向上取整 bodyData.total_fee = Math.ceil(bodyData.total_fee); const respone = await that.app.curl(`${config.wechat_pay_domain}/pays/v1/order`, { method: 'POST', data: bodyData, dataType: 'json', contentType: 'json', }); return respone.data; } /** * [wechatAccountJsApiPay 微信公众号发起js微信支付功能] * @param {Object} data [description] * @return {[type]} [description] */ async wechatAccountJsApiPay(data = {}) { const that = this; const respone = await that.orderPay(data); if (respone.error !== false) throw new Error(respone.message); if (respone.result.result_code != 'SUCCESS') throw new Error(respone.result.err_code_des); const options = { timeStamp: `${that.app.szjcomo.time()}`, nonceStr: respone.result.nonce_str, package: `prepay_id=${respone.result.prepay_id}`, signType: 'MD5', appId: respone.result.appid || respone.result.sub_appid, }; const tmpOptions = { timestamp: options.timeStamp, nonceStr: respone.result.nonce_str, package: `prepay_id=${respone.result.prepay_id}`, signType: 'MD5', appId: respone.result.appid || respone.result.sub_appid, }; const wxpay_key = await that.service.configs.getConfigValue('wechat_pay_key'); const paySign = await that.jsPaySign(options, wxpay_key); tmpOptions.paySign = paySign; return tmpOptions; } /** * [jsSign js对象签名算法] * @author szjcomo * @date 2021-05-16 * @param {Object} data [description] * @return {[type]} [description] */ async jsPaySign(data = {}, key = '') { const that = this; const keysArr = Object.keys(data) .sort(); const sortObj = {}; for (const i in keysArr) { sortObj[keysArr[i]] = data[keysArr[i]]; } const str = querystring.unescape(querystring.stringify(sortObj)); return that.app.szjcomo.MD5(`${str}&key=${key}`) .toUpperCase(); } /** * openid转换接口 */ // async changeOpenid(openidList, token) { // const that = this; // let access_token = ''; // if (token) { // access_token = token; // } else { // const appid = 'wx34b388c12eb961c7'; // const secret = '0b325cbee8ca9a660aa66b554768c18d'; // const uri = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`; // const respone = await that.app.curl(uri, { dataType: 'json' }); // if (respone.data && respone.data.access_token) { // access_token = respone.data.access_token; // } else { // throw new Error(that.app.szjcomo.json_encode(respone.data)); // } // } // console.log('==new token ==:' + access_token); // if (access_token) { // const uri = `https://api.weixin.qq.com/cgi-bin/changeopenid?access_token=${access_token}`; // // 需要转换的openid,即第1步中拉取的原帐号用户列表,这些必须是旧账号目前关注的才行,否则会出错;一次最多100个,不能多。 // const res = await that.app.curl(uri, { // method: 'POST', dataType: 'json', contentType: 'json', // data: { // from_appid: 'wxc3b3607411a67f7b', // openid_list: openidList, // }, // }); // if (res.data.result_list) { // for (const re of res.data.result_list) { // const updateBean = await that.app.comoBean.instance({ // new_openid: re.new_openid, // ori_openid: re.ori_openid, // update_time: that.app.szjcomo.date('Y-m-d H:i:s'), // }, { where: { openid: re.ori_openid } }); // // 2022/9/27 用户新openid更新 // await that.app.comoBean.update(updateBean, that.app.model.Users, '用户新openid更新失败,请稍候重试'); // continue; // } // } else { // console.log('res.data.errmsg === :'); // console.log(res.data.errmsg); // // throw new Error(that.app.szjcomo.json_encode(res.data)); // } // return res.data.result_list; // } // return 'access_token null'; // } } module.exports = WechatService;