wechat.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. 'use strict';
  2. const Service = require('egg').Service;
  3. const querystring = require('querystring');
  4. // 微信接口操作类
  5. class WechatService extends Service {
  6. /**
  7. * [cacheAccessTokenKey 微信公众号access_token key]
  8. * @return {[type]} [description]
  9. */
  10. get cacheAccessTokenKey() {
  11. return `${process.env.APP_CUSTOME || 'universal'}_wechat_account_access_token`;
  12. }
  13. /**
  14. * [cacheJsapiTicketKey 缓存jsapi ticket key]
  15. * @return {[type]} [description]
  16. */
  17. get cacheJsapiTicketKey() {
  18. return `${process.env.APP_CUSTOME || 'universal'}_jsapi_ticket`;
  19. }
  20. /**
  21. * [msgText 响应文本消息]
  22. * @param {String} text [回复内容]
  23. * @param {Object} data [原消息结构]
  24. * @return {[type]} [string]
  25. */
  26. async msgText(text = '', data = {}) {
  27. const that = this;
  28. const result = {
  29. FromUserName: data.ToUserName,
  30. ToUserName: data.FromUserName,
  31. CreateTime: that.app.szjcomo.time(),
  32. MsgType: 'text',
  33. Content: text,
  34. };
  35. return await that.app.szjcomo.createXml(result);
  36. }
  37. /**
  38. * [msgNews 回复图文消息]
  39. * @param {Array} list [description]
  40. * @param {Object} data [description]
  41. * @return {[type]} [description]
  42. */
  43. async msgNews(list = [], data = {}, fields = {}) {
  44. const that = this;
  45. const fieldsMap = Object.assign({
  46. title: 'title',
  47. description: 'description',
  48. image_path: 'image_path',
  49. link_url: 'link_url',
  50. }, fields);
  51. const Articles = [];
  52. list.forEach(item => {
  53. Articles.push({
  54. Title: item[fieldsMap.title],
  55. Description: item[fieldsMap.description] || item[fieldsMap.title],
  56. PicUrl: item[fieldsMap.image_path] || '',
  57. Url: item[fieldsMap.link_url] || '',
  58. });
  59. });
  60. const result = {
  61. FromUserName: data.ToUserName,
  62. ToUserName: data.FromUserName,
  63. CreateTime: that.app.szjcomo.time(),
  64. MsgType: 'news',
  65. ArticleCount: list.length,
  66. Articles: { item: Articles },
  67. };
  68. return await that.app.szjcomo.createXml(result);
  69. }
  70. /**
  71. * [publicAccessToken 获取众号的access_token]
  72. * @param {[type]} appid [description]
  73. * @param {[type]} secret [description]
  74. * @return {[type]} [description]
  75. */
  76. async publicAccessToken(appid, secret) {
  77. const that = this;
  78. appid = appid || await that.service.configs.getConfigValue('wechat_account_appid');
  79. secret = secret || await that.service.configs.getConfigValue('wechat_account_secret');
  80. let result = await that.service.redis.get(that.cacheAccessTokenKey);
  81. if (!result) {
  82. const uri = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
  83. const respone = await that.app.curl(uri, { dataType: 'json' });
  84. if (respone.data && respone.data.access_token) {
  85. result = respone.data.access_token;
  86. await that.service.redis.set(that.cacheAccessTokenKey, result, 1.5 * 60 * 60);
  87. } else {
  88. throw new Error(that.app.szjcomo.json_encode(respone.data));
  89. }
  90. }
  91. return result;
  92. }
  93. /**
  94. * [authLogin 网页用户授权登录]
  95. * @param {[type]} appid [description]
  96. * @param {[type]} secret [description]
  97. * @param {String} code [description]
  98. * @return {[type]} [description]
  99. */
  100. async authLogin(appid, secret, code = '') {
  101. const that = this;
  102. const uri = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=${secret}&code=${code}&grant_type=authorization_code`;
  103. const respone = await that.app.curl(uri, { dataType: 'json' });
  104. return respone.data;
  105. }
  106. /**
  107. * [authUserInfo 获取用户信息]
  108. * @param {[type]} access_token [description]
  109. * @param {[type]} openid [description]
  110. * @return {[type]} [description]
  111. */
  112. async authUserInfo(data = {}) {
  113. const that = this;
  114. const uri = `https://api.weixin.qq.com/sns/userinfo?access_token=${data.access_token}&openid=${data.openid}&lang=zh_CN`;
  115. const respone = await that.app.curl(uri, { dataType: 'json' });
  116. return respone.data;
  117. }
  118. /**
  119. * [jsapiTicket 获取微信公众号jsapi_ticket]
  120. * @author szjcomo
  121. * @date 2021-05-16
  122. * @return {[type]} [description]
  123. */
  124. async jsapiTicket() {
  125. const that = this;
  126. const access_token = await that.publicAccessToken();
  127. let result = await that.service.redis.get(that.cacheJsapiTicketKey);
  128. if (!result) {
  129. const uri = `https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=${access_token}&type=jsapi`;
  130. const res = await that.app.curl(uri, { method: 'GET', dataType: 'json' });
  131. if (res.data && res.data.ticket) {
  132. result = res.data.ticket;
  133. await that.service.redis.set(that.cacheJsapiTicketKey, result, 1.5 * 60 * 60);
  134. } else {
  135. throw new Error(that.app.szjcomo.json_encode(res.data));
  136. }
  137. }
  138. return result;
  139. }
  140. /**
  141. * [jsapiConfig 获取微信公众号jsapi_config]
  142. * @author szjcomo
  143. * @date 2021-05-16
  144. * @return {[type]} [description]
  145. */
  146. async jsapiConfig(urlData = {}) {
  147. const that = this;
  148. const configs = await that.service.configs.getConfigMoreValue([ 'wechat_account_appid', 'wechat_account_jsapi_uri', 'wechat_account_jsapi_list', 'wechat_account_jsapi_debug' ]);
  149. const jsApiList = configs.wechat_account_jsapi_list.split(',');
  150. const data = {
  151. nonceStr: that.app.szjcomo.str_rand(16),
  152. timestamp: that.app.szjcomo.time(),
  153. url: urlData.url || configs.wechat_account_jsapi_uri,
  154. debug: !!Number(configs.wechat_account_jsapi_debug),
  155. jsApiList,
  156. };
  157. const jsapi_ticket = await that.jsapiTicket();
  158. const configSign = `jsapi_ticket=${jsapi_ticket}&noncestr=${data.nonceStr}&timestamp=${data.timestamp}&url=${data.url}`;
  159. data.signature = that.app.szjcomo.sha1(configSign);
  160. data.appId = configs.wechat_account_appid;
  161. return data;
  162. }
  163. /**
  164. * [orderPay 订单下单支付]
  165. * @param {Object} data [description]
  166. * @return {[type]} [description]
  167. */
  168. async orderPay(data = {}) {
  169. const that = this;
  170. const config = await that.service.configs.getConfigMoreValue([ 'wechat_pay_token', 'wechat_pay_domain', 'wechat_pay_callback' ]);
  171. const defaults = {
  172. identity: config.wechat_pay_token, total_fee: 1, notify_url: config.wechat_pay_callback,
  173. detail: '', trade_type: 'NATIVE',
  174. };
  175. const bodyData = Object.assign(defaults, data);
  176. // 2022/12/09 微信最终下单价格向上取整
  177. bodyData.total_fee = Math.ceil(bodyData.total_fee);
  178. const respone = await that.app.curl(`${config.wechat_pay_domain}/pays/v1/order`, {
  179. method: 'POST', data: bodyData, dataType: 'json', contentType: 'json',
  180. });
  181. return respone.data;
  182. }
  183. /**
  184. * [wechatAccountJsApiPay 微信公众号发起js微信支付功能]
  185. * @param {Object} data [description]
  186. * @return {[type]} [description]
  187. */
  188. async wechatAccountJsApiPay(data = {}) {
  189. const that = this;
  190. const respone = await that.orderPay(data);
  191. if (respone.error !== false) throw new Error(respone.message);
  192. if (respone.result.result_code != 'SUCCESS') throw new Error(respone.result.err_code_des);
  193. const options = {
  194. timeStamp: `${that.app.szjcomo.time()}`,
  195. nonceStr: respone.result.nonce_str,
  196. package: `prepay_id=${respone.result.prepay_id}`,
  197. signType: 'MD5',
  198. appId: respone.result.appid || respone.result.sub_appid,
  199. };
  200. const tmpOptions = {
  201. timestamp: options.timeStamp,
  202. nonceStr: respone.result.nonce_str,
  203. package: `prepay_id=${respone.result.prepay_id}`,
  204. signType: 'MD5',
  205. appId: respone.result.appid || respone.result.sub_appid,
  206. };
  207. const wxpay_key = await that.service.configs.getConfigValue('wechat_pay_key');
  208. const paySign = await that.jsPaySign(options, wxpay_key);
  209. tmpOptions.paySign = paySign;
  210. return tmpOptions;
  211. }
  212. /**
  213. * [jsSign js对象签名算法]
  214. * @author szjcomo
  215. * @date 2021-05-16
  216. * @param {Object} data [description]
  217. * @return {[type]} [description]
  218. */
  219. async jsPaySign(data = {}, key = '') {
  220. const that = this;
  221. const keysArr = Object.keys(data)
  222. .sort();
  223. const sortObj = {};
  224. for (const i in keysArr) {
  225. sortObj[keysArr[i]] = data[keysArr[i]];
  226. }
  227. const str = querystring.unescape(querystring.stringify(sortObj));
  228. return that.app.szjcomo.MD5(`${str}&key=${key}`)
  229. .toUpperCase();
  230. }
  231. /**
  232. * openid转换接口
  233. */
  234. // async changeOpenid(openidList, token) {
  235. // const that = this;
  236. // let access_token = '';
  237. // if (token) {
  238. // access_token = token;
  239. // } else {
  240. // const appid = 'wx34b388c12eb961c7';
  241. // const secret = '0b325cbee8ca9a660aa66b554768c18d';
  242. // const uri = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${appid}&secret=${secret}`;
  243. // const respone = await that.app.curl(uri, { dataType: 'json' });
  244. // if (respone.data && respone.data.access_token) {
  245. // access_token = respone.data.access_token;
  246. // } else {
  247. // throw new Error(that.app.szjcomo.json_encode(respone.data));
  248. // }
  249. // }
  250. // console.log('==new token ==:' + access_token);
  251. // if (access_token) {
  252. // const uri = `https://api.weixin.qq.com/cgi-bin/changeopenid?access_token=${access_token}`;
  253. // // 需要转换的openid,即第1步中拉取的原帐号用户列表,这些必须是旧账号目前关注的才行,否则会出错;一次最多100个,不能多。
  254. // const res = await that.app.curl(uri, {
  255. // method: 'POST', dataType: 'json', contentType: 'json',
  256. // data: {
  257. // from_appid: 'wxc3b3607411a67f7b',
  258. // openid_list: openidList,
  259. // },
  260. // });
  261. // if (res.data.result_list) {
  262. // for (const re of res.data.result_list) {
  263. // const updateBean = await that.app.comoBean.instance({
  264. // new_openid: re.new_openid,
  265. // ori_openid: re.ori_openid,
  266. // update_time: that.app.szjcomo.date('Y-m-d H:i:s'),
  267. // }, { where: { openid: re.ori_openid } });
  268. // // 2022/9/27 用户新openid更新
  269. // await that.app.comoBean.update(updateBean, that.app.model.Users, '用户新openid更新失败,请稍候重试');
  270. // continue;
  271. // }
  272. // } else {
  273. // console.log('res.data.errmsg === :');
  274. // console.log(res.data.errmsg);
  275. // // throw new Error(that.app.szjcomo.json_encode(res.data));
  276. // }
  277. // return res.data.result_list;
  278. // }
  279. // return 'access_token null';
  280. // }
  281. }
  282. module.exports = WechatService;