pay.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. /**
  2. * uni-pay-co 统一支付服务实现
  3. */
  4. const crypto = require("crypto");
  5. const uniPay = require("uni-pay");
  6. const configCenter = require("uni-config-center");
  7. const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
  8. const dao = require('../dao');
  9. const libs = require('../libs');
  10. const { UniCloudError, isUniPayError, ERROR } = require('../common/error')
  11. const db = uniCloud.database();
  12. const _ = db.command;
  13. const notifyPath = "/payNotify/";
  14. class service {
  15. constructor(obj) {
  16. }
  17. /**
  18. * 获取支付插件的完整配置
  19. */
  20. getConfig() {
  21. return config;
  22. }
  23. /**
  24. * 支付成功 - 异步通知
  25. */
  26. async paymentNotify(data = {}) {
  27. let {
  28. httpInfo,
  29. clientInfo,
  30. cloudInfo,
  31. isWxpayVirtual
  32. } = data;
  33. console.log('httpInfo: ', httpInfo);
  34. let path = httpInfo.path;
  35. let pay_type = path.substring(notifyPath.length);
  36. let provider = pay_type.split("-")[0]; // 获取支付供应商
  37. let provider_pay_type = pay_type.split("-")[1]; // 获取支付方式
  38. if (isWxpayVirtual) {
  39. // 微信虚拟支付固定参数
  40. provider = "wxpay-virtual";
  41. provider_pay_type = "mp";
  42. }
  43. // 初始化uniPayInstance
  44. let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
  45. let notifyType = await uniPayInstance.checkNotifyType(httpInfo);
  46. console.log('notifyType: ', notifyType)
  47. if (notifyType === "token") {
  48. let verifyResult = await uniPayInstance.verifyTokenNotify(httpInfo);
  49. console.log('verifyResult: ', verifyResult)
  50. if (!verifyResult) {
  51. console.log('---------!签名验证未通过!---------');
  52. return;
  53. }
  54. // 校验token的测试接口,直接返回echostr
  55. return verifyResult.echostr;
  56. }
  57. if (notifyType !== "payment") {
  58. // 非支付通知直接返回成功
  59. console.log(`---------!非支付通知!---------`);
  60. return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
  61. }
  62. // 支付通知,验证签名
  63. let verifyResult = await uniPayInstance.verifyPaymentNotify(httpInfo);
  64. if (!verifyResult) {
  65. console.log('---------!签名验证未通过!---------');
  66. console.log('---------!签名验证未通过!---------');
  67. console.log('---------!签名验证未通过!---------');
  68. return {}
  69. }
  70. console.log('---------!签名验证通过!---------');
  71. verifyResult = JSON.parse(JSON.stringify(verifyResult)); // 这一句代码有用,请勿删除。
  72. console.log('verifyResult: ', verifyResult)
  73. let {
  74. outTradeNo,
  75. totalFee,
  76. transactionId,
  77. resultCode, // 微信支付v2和支付宝支付判断成功的字段
  78. openid,
  79. appId,
  80. tradeState, // 微信支付v3和微信虚拟支付判断支付成功的字段
  81. } = verifyResult;
  82. if (resultCode == "SUCCESS" || tradeState === "SUCCESS") {
  83. let time = Date.now();
  84. let payOrderInfo = await dao.uniPayOrders.updateAndReturn({
  85. whereJson: {
  86. status: 0, // status:0 为必须条件,防止重复推送时的错误
  87. out_trade_no: outTradeNo, // 商户订单号
  88. },
  89. dataJson: {
  90. status: 1, // 设置为已付款
  91. transaction_id: transactionId, // 第三方支付单号
  92. pay_date: time, // 更新支付时间(暂无法准确获取到支付时间,故用通知时间代替支付时间)
  93. notify_date: time, // 更新通知时间
  94. openid,
  95. provider, // 更新provider
  96. provider_pay_type, // 更新provider_pay_type
  97. original_data: httpInfo, // http回调信息,便于丢单时手动触发回调
  98. }
  99. });
  100. //console.log('payOrderInfo: ', payOrderInfo)
  101. if (payOrderInfo) {
  102. // 只有首次推送才执行用户自己的逻辑处理。
  103. // 用户自己的逻辑处理 开始-----------------------------------------------------------
  104. let userOrderSuccess = false;
  105. let orderPaySuccess;
  106. try {
  107. // 加载自定义异步回调函数
  108. orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
  109. } catch (err) {
  110. console.log(err);
  111. }
  112. if (typeof orderPaySuccess === "function") {
  113. console.log('用户自己的回调逻辑 - 开始执行');
  114. try {
  115. userOrderSuccess = await orderPaySuccess({
  116. verifyResult,
  117. data: payOrderInfo,
  118. clientInfo,
  119. cloudInfo
  120. });
  121. console.log('用户自己的回调逻辑 - 执行完成');
  122. } catch(err){
  123. userOrderSuccess = false;
  124. console.log('用户自己的回调逻辑 - 执行异常', err);
  125. }
  126. }
  127. console.log('userOrderSuccess', userOrderSuccess);
  128. // 用户自己的逻辑处理 结束-----------------------------------------------------------
  129. await dao.uniPayOrders.updateAndReturn({
  130. whereJson: {
  131. status: 1,
  132. out_trade_no: outTradeNo,
  133. },
  134. dataJson: {
  135. user_order_success: userOrderSuccess,
  136. }
  137. });
  138. } else {
  139. console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
  140. console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
  141. console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
  142. console.log('verifyResult:', verifyResult);
  143. }
  144. } else {
  145. console.log('verifyResult:', verifyResult);
  146. }
  147. return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
  148. }
  149. /**
  150. * 微信虚拟支付异步通知
  151. */
  152. async wxpayVirtualNotify(data = {}) {
  153. return this.paymentNotify({
  154. ...data,
  155. isWxpayVirtual: true
  156. });
  157. }
  158. /**
  159. * 统一支付 - 创建支付订单
  160. */
  161. async createOrder(data = {}) {
  162. let {
  163. provider, // 支付供应商
  164. total_fee, // 支付金额
  165. user_id, // 用户user_id(统计需要)
  166. openid, // 用户openid
  167. order_no, // 订单号
  168. out_trade_no, // 支付插件订单号
  169. description, // 订单描述
  170. type, // 回调类型
  171. qr_code, // 是否强制使用扫码支付
  172. custom, // 自定义参数(不会发送给第三方支付服务器)
  173. other, // 其他请求参数(会发送给第三方支付服务器)
  174. clientInfo, // 客户端信息
  175. cloudInfo, // 云端信息
  176. wxpay_virtual, // 仅用于微信虚拟支付
  177. } = data;
  178. let subject = description;
  179. let body = description;
  180. if (!out_trade_no) out_trade_no = libs.common.createOrderNo();
  181. if (!order_no || typeof order_no !== "string") {
  182. throw { errCode: ERROR[51003] };
  183. }
  184. if (!type || typeof type !== "string") {
  185. throw { errCode: ERROR[51004] };
  186. }
  187. if (provider === "wxpay-virtual") {
  188. if (typeof wxpay_virtual !== "object") {
  189. throw { errCode: ERROR[51011] };
  190. }
  191. if (typeof wxpay_virtual.buy_quantity !== "number" || wxpay_virtual.buy_quantity <= 0) {
  192. throw { errCode: ERROR[51012] };
  193. }
  194. } else {
  195. if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
  196. throw { errCode: ERROR[51005] };
  197. }
  198. }
  199. if (!description || typeof description !== "string") {
  200. throw { errCode: ERROR[51006] };
  201. }
  202. if (!provider || typeof provider !== "string") {
  203. throw { errCode: ERROR[51007] };
  204. }
  205. if (!clientInfo) {
  206. throw { errCode: ERROR[51008] };
  207. }
  208. if (!cloudInfo) {
  209. throw { errCode: ERROR[51009] };
  210. }
  211. let res = { errCode: 0, errMsg: 'ok', order_no, out_trade_no, provider };
  212. let {
  213. clientIP: client_ip,
  214. userAgent: ua,
  215. appId: appid,
  216. deviceId: device_id,
  217. platform
  218. } = clientInfo;
  219. let {
  220. spaceId, // 服务空间ID
  221. } = cloudInfo;
  222. let {
  223. notifyUrl = {}
  224. } = config;
  225. // 业务逻辑开始-----------------------------------------------------------
  226. // 以下代码是为了兼容公测版迁移到正式版的空间
  227. let notifySpaceId = spaceId;
  228. if (!notifyUrl[notifySpaceId]) {
  229. if (notifySpaceId.indexOf("mp-") === 0) {
  230. notifySpaceId = notifySpaceId.substring(3);
  231. } else {
  232. notifySpaceId = `mp-${notifySpaceId}`
  233. }
  234. }
  235. // 以上代码是为了兼容公测版迁移到正式版的空间
  236. let currentNotifyUrl = notifyUrl[notifySpaceId] || notifyUrl["default"]; // 异步回调地址
  237. if (!currentNotifyUrl || currentNotifyUrl.indexOf("http") !== 0) {
  238. throw { errCode: ERROR[52002] };
  239. }
  240. platform = libs.common.getPlatform(platform);
  241. // 如果需要二维码支付模式,则清空下openid
  242. if (qr_code) {
  243. openid = undefined;
  244. res.qr_code = qr_code;
  245. }
  246. // 获取并自动匹配支付供应商的支付类型
  247. let provider_pay_type = libs.common.getProviderPayType({
  248. platform,
  249. provider,
  250. ua,
  251. qr_code
  252. });
  253. res.provider_pay_type = provider_pay_type;
  254. // 拼接实际异步回调地址
  255. let finalNotifyUrl = `${currentNotifyUrl}${notifyPath}${provider}-${provider_pay_type}`;
  256. // 获取uniPay交易类型
  257. let tradeType = libs.common.getTradeType({ provider, provider_pay_type });
  258. let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
  259. // 初始化uniPayInstance
  260. let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
  261. // 获取支付信息
  262. let getOrderInfoParam = {
  263. openid: openid,
  264. subject: subject,
  265. body: body,
  266. outTradeNo: out_trade_no,
  267. totalFee: total_fee,
  268. notifyUrl: finalNotifyUrl,
  269. tradeType: tradeType
  270. };
  271. if (provider === "wxpay" && provider_pay_type === "mweb") {
  272. getOrderInfoParam.spbillCreateIp = client_ip;
  273. if (uniPayConifg.version !== 3) {
  274. // v2版本
  275. getOrderInfoParam.sceneInfo = uniPayConifg.sceneInfo;
  276. } else {
  277. // v3版本特殊处理
  278. getOrderInfoParam.sceneInfo = JSON.parse(JSON.stringify(uniPayConifg.sceneInfo));
  279. if (getOrderInfoParam.sceneInfo.h5_info.wap_url) {
  280. getOrderInfoParam.sceneInfo.h5_info.app_url = getOrderInfoParam.sceneInfo.h5_info.wap_url;
  281. delete getOrderInfoParam.sceneInfo.h5_info.wap_url;
  282. }
  283. if (getOrderInfoParam.sceneInfo.h5_info.wap_name) {
  284. getOrderInfoParam.sceneInfo.h5_info.app_name = getOrderInfoParam.sceneInfo.h5_info.wap_name;
  285. delete getOrderInfoParam.sceneInfo.h5_info.wap_name;
  286. }
  287. }
  288. }
  289. let expand_data;
  290. try {
  291. // 如果是苹果内购,不需要执行uniPayInstance.getOrderInfo等操作
  292. if (provider !== "appleiap") {
  293. // 第三方支付服务器返回的订单信息
  294. let orderInfo;
  295. if (other) {
  296. // other 内的键名转驼峰
  297. other = libs.common.snake2camelJson(other);
  298. getOrderInfoParam = Object.assign(getOrderInfoParam, other);
  299. }
  300. getOrderInfoParam = JSON.parse(JSON.stringify(getOrderInfoParam)); // 此为去除undefined的参数
  301. if (provider === "wxpay-virtual") {
  302. // 微信虚拟支付扩展数据
  303. expand_data = {
  304. mode: wxpay_virtual.mode, // short_series_coin 代币充值; short_series_goods 道具直购
  305. buy_quantity: wxpay_virtual.buy_quantity,
  306. rate: uniPayConifg.rate || 100,
  307. sandbox: uniPayConifg.sandbox,
  308. };
  309. if (wxpay_virtual.mode === "short_series_goods") {
  310. expand_data.product_id = wxpay_virtual.product_id;
  311. expand_data.goods_price = wxpay_virtual.goods_price;
  312. }
  313. // 获取用户的sessionKey
  314. let { session_key } = await dao.opendbOpenData.getSessionKey({
  315. appId: uniPayConifg.appId,
  316. platform: "weixin-mp",
  317. openid: openid
  318. });
  319. getOrderInfoParam.sessionKey = session_key;
  320. getOrderInfoParam.mode = expand_data.mode;
  321. getOrderInfoParam.buyQuantity = expand_data.buy_quantity;
  322. getOrderInfoParam.productId = expand_data.product_id;
  323. getOrderInfoParam.goodsPrice = expand_data.goods_price;
  324. if (getOrderInfoParam.mode === "short_series_coin") {
  325. // 计算支付金额
  326. total_fee = expand_data.buy_quantity / (expand_data.rate || 100) * 100;
  327. } else if (getOrderInfoParam.mode === "short_series_goods") {
  328. // 计算支付金额
  329. total_fee = expand_data.buy_quantity * expand_data.goods_price;
  330. }
  331. }
  332. orderInfo = await uniPayInstance.getOrderInfo(getOrderInfoParam);
  333. if (qr_code && orderInfo.codeUrl) {
  334. res.qr_code_image = await libs.qrcode.toDataURL(orderInfo.codeUrl, {
  335. type: "image/png",
  336. width: 200,
  337. margin: 1,
  338. scale: 1,
  339. color: {
  340. dark: "#000000",
  341. light: "#ffffff",
  342. },
  343. errorCorrectionLevel: "Q",
  344. quality: 1
  345. });
  346. }
  347. // 支付宝支付参数特殊处理
  348. if (provider === "alipay") {
  349. if (typeof orderInfo === "object" && orderInfo.code && orderInfo.code !== "10000") {
  350. res.errCode = orderInfo.code;
  351. res.errMsg = orderInfo.subMsg;
  352. }
  353. }
  354. res.order = orderInfo;
  355. }
  356. } catch (err) {
  357. let errMsg = err.errorMessage || err.message;
  358. console.error("data: ", data);
  359. console.error("getOrderInfoParam: ", getOrderInfoParam);
  360. console.error("err: ", err);
  361. console.error("errMsg: ", errMsg);
  362. throw { errCode: ERROR[53001], errMsg };
  363. }
  364. // 尝试获取下订单信息
  365. let payOrderInfo = await dao.uniPayOrders.find({
  366. order_no,
  367. out_trade_no
  368. });
  369. let create_date = Date.now();
  370. // 如果订单不存在,则添加
  371. if (!payOrderInfo) {
  372. // 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
  373. let stat_platform = clientInfo.platform;
  374. if (stat_platform === "app") {
  375. stat_platform = clientInfo.os;
  376. }
  377. let nickname;
  378. if (user_id) {
  379. // 获取nickname(冗余昵称)
  380. let userInfo = await dao.uniIdUsers.findById(user_id);
  381. if (userInfo) nickname = userInfo.nickname;
  382. }
  383. await dao.uniPayOrders.add({
  384. provider,
  385. provider_pay_type,
  386. uni_platform: platform,
  387. status: 0,
  388. type,
  389. order_no,
  390. out_trade_no,
  391. user_id,
  392. nickname,
  393. device_id,
  394. client_ip,
  395. openid,
  396. description,
  397. total_fee,
  398. refund_fee: 0,
  399. refund_count: 0,
  400. provider_appid: uniPayConifg.appId,
  401. appid,
  402. custom,
  403. create_date,
  404. expand_data,
  405. stat_data: {
  406. platform: stat_platform,
  407. app_version: clientInfo.appVersion,
  408. app_version_code: clientInfo.appVersionCode,
  409. app_wgt_version: clientInfo.appWgtVersion,
  410. os: clientInfo.os,
  411. ua: clientInfo.ua,
  412. channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
  413. scene: clientInfo.scene
  414. }
  415. });
  416. } else {
  417. // 如果订单已经存在,则修改下支付方式(用户可能先点微信支付,未付款,又点了支付宝支付)
  418. await dao.uniPayOrders.updateById(payOrderInfo._id, {
  419. provider,
  420. provider_pay_type,
  421. });
  422. }
  423. // 自动删除3天前的订单(未付款订单)
  424. // await dao.uniPayOrders.deleteExpPayOrders();
  425. // 业务逻辑结束-----------------------------------------------------------
  426. return res;
  427. }
  428. /**
  429. * 统一支付结果查询
  430. * @description 根据商户订单号或者平台订单号查询订单信息,主要用于未接收到支付通知时可以使用此接口进行支付结果验证
  431. */
  432. async getOrder(data = {}) {
  433. let {
  434. out_trade_no, // 支付插件订单号
  435. transaction_id, // 支付平台的交易单号
  436. await_notify = false, // 是否需要等待异步通知执行完成才返回前端支付结果
  437. } = data;
  438. let res = { errCode: 0, errMsg: 'ok' };
  439. // 业务逻辑开始-----------------------------------------------------------
  440. if (!out_trade_no && !transaction_id) {
  441. throw { errCode: ERROR[51010] };
  442. }
  443. let payOrderInfo;
  444. if (transaction_id) {
  445. payOrderInfo = await dao.uniPayOrders.find({
  446. transaction_id
  447. });
  448. } else if (out_trade_no) {
  449. payOrderInfo = await dao.uniPayOrders.find({
  450. out_trade_no
  451. });
  452. }
  453. if (!payOrderInfo) {
  454. throw { errCode: ERROR[52001] };
  455. }
  456. // 初始化uniPayInstance
  457. let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
  458. let orderQueryJson = {};
  459. if (out_trade_no) {
  460. orderQueryJson.outTradeNo = out_trade_no;
  461. } else {
  462. orderQueryJson.transactionId = transaction_id;
  463. }
  464. if (payOrderInfo.provider === "wxpay-virtual") {
  465. orderQueryJson.openid = payOrderInfo.openid;
  466. }
  467. let queryRes;
  468. if (typeof uniPayInstance.orderQuery === "function") {
  469. queryRes = await uniPayInstance.orderQuery(orderQueryJson);
  470. console.log('queryRes: ', queryRes)
  471. } else {
  472. // 无uniPayInstance.orderQuery函数时的兼容处理
  473. if ([1, 2].indexOf(payOrderInfo.status) > -1) {
  474. queryRes = {
  475. tradeState: "SUCCESS",
  476. tradeStateDesc: "订单已支付"
  477. };
  478. } else if ([3].indexOf(payOrderInfo.status) > -1) {
  479. queryRes = {
  480. tradeState: "REFUNDED",
  481. tradeStateDesc: "订单已退款"
  482. };
  483. } else {
  484. queryRes = {
  485. tradeState: "NOPAY",
  486. tradeStateDesc: "订单未支付"
  487. };
  488. }
  489. }
  490. if (queryRes.tradeState === 'SUCCESS' || queryRes.tradeState === 'FINISHED') {
  491. if (typeof payOrderInfo.user_order_success == "undefined" && await_notify) {
  492. let whileTime = 0; // 当前循环已执行的时间(毫秒)
  493. let whileInterval = 500; // 每次循环间隔时间(毫秒)
  494. let maxTime = 20000; // 循环执行时间超过此值则退出循环(毫秒)
  495. while (typeof payOrderInfo.user_order_success == "undefined" && whileTime <= maxTime) {
  496. await libs.common.sleep(whileInterval);
  497. whileTime += whileInterval;
  498. payOrderInfo = await dao.uniPayOrders.find({
  499. out_trade_no
  500. });
  501. }
  502. }
  503. res = {
  504. errCode: 0,
  505. errMsg: "ok",
  506. has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
  507. out_trade_no, // 支付插件订单号
  508. transaction_id, // 支付平台订单号
  509. status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款
  510. user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功)
  511. pay_order: payOrderInfo,
  512. }
  513. } else {
  514. let errMsg = queryRes.tradeStateDesc || "未支付或已退款";
  515. if (errMsg.indexOf("订单发生过退款") > -1) {
  516. errMsg = "订单已退款";
  517. }
  518. res = {
  519. errCode: -1,
  520. errMsg: errMsg,
  521. has_paid: false,
  522. out_trade_no, // 支付插件订单号
  523. transaction_id, // 支付平台订单号
  524. }
  525. }
  526. // 业务逻辑结束-----------------------------------------------------------
  527. return res;
  528. }
  529. /**
  530. * 统一退款
  531. * @description 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家。
  532. */
  533. async refund(data = {}) {
  534. let {
  535. out_trade_no, // 插件支付单号
  536. out_refund_no, // 退款单号(若不传,则自动生成)
  537. refund_desc = "用户申请退款",
  538. refund_fee: myRefundFee,
  539. refund_fee_type = "CNY"
  540. } = data;
  541. let res = { errCode: 0, errMsg: 'ok' };
  542. // 业务逻辑开始-----------------------------------------------------------
  543. if (!out_trade_no) {
  544. throw { errCode: ERROR[51001] };
  545. }
  546. let payOrderInfo = await dao.uniPayOrders.find({
  547. out_trade_no
  548. });
  549. if (!payOrderInfo) {
  550. throw { errCode: ERROR[52001] };
  551. }
  552. let refund_count = payOrderInfo.refund_count || 0;
  553. refund_count++;
  554. // 生成退款订单号
  555. let outRefundNo = out_refund_no ? out_refund_no : `${out_trade_no}-${refund_count}`;
  556. // 订单总金额
  557. let totalFee = payOrderInfo.total_fee;
  558. // 退款总金额
  559. let refundFee = myRefundFee || totalFee;
  560. let provider = payOrderInfo.provider;
  561. let uniPayConifg = await this.getUniPayConfig(payOrderInfo);
  562. let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
  563. let refundParams = {
  564. outTradeNo: out_trade_no,
  565. outRefundNo,
  566. totalFee,
  567. refundFee,
  568. refundDesc: refund_desc,
  569. refundFeeType: refund_fee_type
  570. };
  571. if (payOrderInfo.provider === "wxpay-virtual") {
  572. refundParams.openid = payOrderInfo.openid;
  573. refundParams.refundReason = "3"; // 0-暂无描述 1-产品问题,影响使用或效果不佳 2-售后问题,无法满足需求 3-意愿问题,用户主动退款 4-价格问题 5-其他原因
  574. refundParams.reqFrom = "2"; // 当前只支持"1"-人工客服退款,即用户电话给客服,由客服发起退款流程 "2"-用户自己发起退款流程 "3"-其他
  575. // 查询当前可退款金额
  576. refundParams.leftFee = totalFee - (payOrderInfo.refund_fee || 0);
  577. // 实时查询当前可退款金额(此处注释可省一次请求)
  578. // const orderQueryRes = await uniPayInstance.orderQuery({
  579. // openid: payOrderInfo.openid,
  580. // outTradeNo: payOrderInfo.out_trade_no
  581. // });
  582. // refundParams.leftFee = orderQueryRes.leftFee;
  583. }
  584. console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
  585. // 退款操作
  586. try {
  587. res.result = await uniPayInstance.refund(refundParams);
  588. } catch (err) {
  589. console.error(err);
  590. let errMsg = err.message;
  591. if (errMsg) {
  592. if (errMsg.indexOf("verify failure") > -1) {
  593. throw { errCode: ERROR[53005] };
  594. }
  595. if (errMsg.indexOf("header too long") > -1) {
  596. throw { errCode: ERROR[53005] };
  597. }
  598. }
  599. return { errCode: -1, errMsg: errMsg, err }
  600. }
  601. if (res.result.refundFee) {
  602. res.errCode = 0;
  603. res.errMsg = "ok";
  604. // 修改数据库
  605. try {
  606. let time = Date.now();
  607. // 修改订单状态
  608. payOrderInfo = await dao.uniPayOrders.updateAndReturn({
  609. whereJson: {
  610. _id: payOrderInfo._id,
  611. "refund_list.out_refund_no": _.neq(outRefundNo)
  612. },
  613. dataJson: {
  614. status: 2,
  615. refund_fee: _.inc(refundFee),
  616. refund_count: refund_count,
  617. refund_date: time, // 更新最近一次退款时间
  618. // 记录每次的退款详情
  619. refund_list: _.unshift({
  620. refund_date: time,
  621. refund_fee: refundFee,
  622. out_refund_no: outRefundNo,
  623. refund_desc
  624. })
  625. }
  626. });
  627. if (payOrderInfo && payOrderInfo.refund_fee >= payOrderInfo.total_fee) {
  628. // 修改订单状态为已全额退款
  629. await dao.uniPayOrders.updateById(payOrderInfo._id, {
  630. status: 3,
  631. refund_fee: payOrderInfo.total_fee,
  632. });
  633. }
  634. } catch (err) {
  635. console.error(err);
  636. }
  637. } else {
  638. console.log('res.result: ', res.result);
  639. throw { errCode: ERROR[53002] };
  640. }
  641. // 业务逻辑结束-----------------------------------------------------------
  642. return res;
  643. }
  644. /**
  645. * 查询退款(查询退款情况)
  646. * @description 提交退款申请后,通过调用该接口查询退款状态。
  647. */
  648. async getRefund(data = {}) {
  649. let {
  650. out_trade_no, // 插件支付单号
  651. } = data;
  652. if (!out_trade_no) {
  653. throw { errCode: ERROR[51001] };
  654. }
  655. let payOrderInfo = await dao.uniPayOrders.find({
  656. out_trade_no
  657. });
  658. if (!payOrderInfo) {
  659. throw { errCode: ERROR[52001] };
  660. }
  661. let provider = payOrderInfo.provider;
  662. let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
  663. let queryRes;
  664. try {
  665. let refundQueryJson = {
  666. outTradeNo: out_trade_no,
  667. outRefundNo: payOrderInfo.refund_list[0].out_refund_no
  668. };
  669. if (provider === "wxpay-virtual") {
  670. refundQueryJson.openid = payOrderInfo.openid;
  671. }
  672. queryRes = await uniPayInstance.refundQuery(refundQueryJson);
  673. } catch (err) {
  674. throw { errCode: ERROR[53003], errMsg: err.errMsg };
  675. }
  676. let orderInfo = {
  677. total_fee: payOrderInfo.total_fee,
  678. refund_fee: payOrderInfo.refund_fee,
  679. refund_count: payOrderInfo.refund_count,
  680. refund_list: payOrderInfo.refund_list,
  681. provider: payOrderInfo.provider,
  682. provider_pay_type: payOrderInfo.provider_pay_type,
  683. status: payOrderInfo.status,
  684. type: payOrderInfo.type,
  685. out_trade_no: payOrderInfo.out_trade_no,
  686. transaction_id: payOrderInfo.transaction_id,
  687. };
  688. if (queryRes.refundFee > 0) {
  689. let msg = "ok";
  690. if (payOrderInfo.refund_list && payOrderInfo.refund_list.length > 0) {
  691. msg = `合计退款 ${payOrderInfo.refund_fee/100}\r\n`;
  692. for (let i in payOrderInfo.refund_list) {
  693. let item = payOrderInfo.refund_list[i];
  694. let index = Number(i) + 1;
  695. let timeStr = libs.common.timeFormat(item.refund_date, "yyyy-MM-dd hh:mm:ss");
  696. msg += `${index}、 ${timeStr} \r\n退款 ${item.refund_fee/100} \r\n`;
  697. }
  698. }
  699. return {
  700. errCode: 0,
  701. errMsg: msg,
  702. pay_order: orderInfo,
  703. result: queryRes
  704. }
  705. } else {
  706. throw { errCode: ERROR[53003] };
  707. }
  708. }
  709. /**
  710. * 关闭订单
  711. * @description 用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭,避免重复支付。
  712. * 注意
  713. * 微信支付:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
  714. * 微信虚拟支付:不支持关闭订单
  715. */
  716. async closeOrder(data = {}) {
  717. let {
  718. out_trade_no, // 插件支付单号
  719. } = data;
  720. if (!out_trade_no) {
  721. throw { errCode: ERROR[51001] };
  722. }
  723. let payOrderInfo = await dao.uniPayOrders.find({
  724. out_trade_no
  725. });
  726. if (!payOrderInfo) {
  727. throw { errCode: ERROR[52001] };
  728. }
  729. let { provider } = payOrderInfo;
  730. let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
  731. let closeOrderRes = await uniPayInstance.closeOrder({
  732. outTradeNo: out_trade_no
  733. });
  734. let wxpayResult = (provider === "wxpay" && closeOrderRes.resultCode === "SUCCESS");
  735. let alipayResult = (provider === "alipay" && closeOrderRes.code === "10000");
  736. if (wxpayResult || alipayResult) {
  737. // 修改订单状态为已取消
  738. await dao.uniPayOrders.update({
  739. whereJson: {
  740. _id: payOrderInfo._id,
  741. status: 0
  742. },
  743. dataJson: {
  744. status: -1,
  745. cancel_date: Date.now()
  746. }
  747. });
  748. return {
  749. errCode: 0,
  750. errMsg: "订单关闭成功",
  751. result: closeOrderRes
  752. }
  753. } else {
  754. throw { errCode: ERROR[53004] };
  755. }
  756. }
  757. /**
  758. * 根据code获取openid
  759. */
  760. async getOpenid(data = {}) {
  761. let {
  762. provider, // 支付供应商
  763. code, // 用户登录获取的code
  764. clientInfo, // 客户端环境
  765. } = data;
  766. if (!code) {
  767. throw { errCode: ERROR[51002] };
  768. }
  769. let { platform, ua } = clientInfo;
  770. // 获取并自动匹配支付供应商的支付类型
  771. let provider_pay_type = libs.common.getProviderPayType({
  772. provider,
  773. platform,
  774. ua
  775. });
  776. let needCacheSessionKey = false;
  777. let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
  778. if (provider === "wxpay") {
  779. try {
  780. // 如果配置了微信虚拟支付,则使用微信虚拟支付的配置作为微信获取openid的配置
  781. let wxpayVirtualPayConifg = await this.getUniPayConfig({ provider: "wxpay-virtual", provider_pay_type: "mp" });
  782. if (wxpayVirtualPayConifg && wxpayVirtualPayConifg.appId && wxpayVirtualPayConifg.secret) {
  783. uniPayConifg = wxpayVirtualPayConifg;
  784. needCacheSessionKey = true;
  785. }
  786. } catch (err) {}
  787. let res = await libs.wxpay.getOpenid({
  788. config: uniPayConifg,
  789. code,
  790. provider_pay_type,
  791. });
  792. if (needCacheSessionKey) {
  793. // 将session_key保存到缓存表中
  794. let cacheKey = {
  795. appId: uniPayConifg.appId,
  796. platform: "weixin-mp",
  797. openid: res.openid
  798. }
  799. let session_key = res.session_key;
  800. delete res.session_key;
  801. await dao.opendbOpenData.setSessionKey(cacheKey, { session_key }, 30 * 24 * 60 * 60);
  802. }
  803. return res;
  804. } else if (provider === "alipay") {
  805. return await libs.alipay.getOpenid({
  806. config: uniPayConifg,
  807. code,
  808. });
  809. }
  810. }
  811. /**
  812. * 获取支持的支付方式
  813. * let payTypes = await service.pay.getPayProviderFromCloud();
  814. */
  815. async getPayProviderFromCloud() {
  816. let wxpay = config.wxpay && config.wxpay.enable ? true : false;
  817. let alipay = config.alipay && config.alipay.enable ? true : false;
  818. let provider = [];
  819. if (wxpay) provider.push("wxpay");
  820. if (alipay) provider.push("alipay");
  821. return {
  822. errCode: 0,
  823. errMsg: "ok",
  824. wxpay,
  825. alipay,
  826. provider
  827. };
  828. }
  829. /**
  830. * 验证iosIap苹果内购支付凭据
  831. * let payTypes = await service.pay.verifyReceiptFromAppleiap();
  832. */
  833. async verifyReceiptFromAppleiap(data) {
  834. let {
  835. out_trade_no,
  836. transaction_receipt,
  837. transaction_identifier,
  838. } = data;
  839. if (!out_trade_no) {
  840. throw { errCode: ERROR[51001] };
  841. }
  842. // 初始化uniPayInstance
  843. let uniPayInstance = await this.initUniPayInstance({ provider: "appleiap", provider_pay_type: "app" });
  844. let verifyReceiptRes = await uniPayInstance.verifyReceipt({
  845. receiptData: transaction_receipt
  846. });
  847. let userOrderSuccess = false;
  848. let pay_date;
  849. if (verifyReceiptRes.tradeState !== "SUCCESS") {
  850. // 尝试使用相反的环境再次验证
  851. let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" });
  852. uniPayInstance = uniPay.initAppleIapPayment({
  853. ...uniPayConifg,
  854. sandbox: !uniPayConifg.sandbox,
  855. });
  856. verifyReceiptRes = await uniPayInstance.verifyReceipt({
  857. receiptData: transaction_receipt
  858. });
  859. if (verifyReceiptRes.tradeState !== "SUCCESS") {
  860. // 如果还是不成功,则校验不通过
  861. throw { errCode: ERROR[54002] };
  862. }
  863. }
  864. // 支付成功
  865. pay_date = Number(verifyReceiptRes.receipt.receipt_creation_date_ms);
  866. let inAppList = verifyReceiptRes.receipt.in_app;
  867. let inApp = inAppList.find((item) => {
  868. return item.transaction_id === transaction_identifier;
  869. });
  870. if (!inApp) {
  871. // 校验不通过
  872. throw { errCode: ERROR[54002] };
  873. }
  874. let quantity = inApp.quantity; // 购买数量
  875. let product_id = inApp.product_id; // 对应的内购产品id
  876. let transaction_id = inApp.transaction_id; // 本次交易id
  877. if ((Date.now() - 1000 * 3600 * 24) > pay_date) {
  878. // 订单已超24小时,不做处理,通知前端直接关闭订单。
  879. return {
  880. errCode: 0,
  881. errMsg: "ok"
  882. };
  883. }
  884. // 查询该transaction_id是否使用过,如果已使用,则不做处理,通知前端直接关闭订单。
  885. let findOrderInfo = await dao.uniPayOrders.find({
  886. transaction_id,
  887. });
  888. if (findOrderInfo) {
  889. return {
  890. errCode: 0,
  891. errMsg: "ok"
  892. };
  893. }
  894. // 否则,执行用户回调
  895. // 用户自己的逻辑处理 开始-----------------------------------------------------------
  896. let orderPaySuccess;
  897. let payOrderInfo = await dao.uniPayOrders.find({
  898. out_trade_no,
  899. });
  900. if (!payOrderInfo) {
  901. throw { errCode: ERROR[52001] };
  902. }
  903. try {
  904. // 加载自定义异步回调函数
  905. orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
  906. } catch (err) {
  907. console.log(err);
  908. }
  909. if (typeof orderPaySuccess === "function") {
  910. payOrderInfo = await dao.uniPayOrders.updateAndReturn({
  911. whereJson: {
  912. status: 0, // status:0 为必须条件,防止重复推送时的错误
  913. out_trade_no: out_trade_no, // 商户订单号
  914. },
  915. dataJson: {
  916. status: 1, // 设置为已付款
  917. transaction_id: transaction_id, // 第三方支付单号
  918. pay_date: pay_date,
  919. notify_date: pay_date,
  920. original_data: verifyReceiptRes
  921. }
  922. });
  923. console.log('用户自己的回调逻辑 - 开始执行');
  924. userOrderSuccess = await orderPaySuccess({
  925. verifyResult: verifyReceiptRes,
  926. data: payOrderInfo,
  927. });
  928. console.log('用户自己的回调逻辑 - 执行完成');
  929. payOrderInfo = await dao.uniPayOrders.updateAndReturn({
  930. whereJson: {
  931. status: 1,
  932. out_trade_no,
  933. },
  934. dataJson: {
  935. user_order_success: userOrderSuccess,
  936. }
  937. });
  938. } else {
  939. payOrderInfo = await dao.uniPayOrders.find({
  940. out_trade_no,
  941. });
  942. }
  943. console.log('userOrderSuccess', userOrderSuccess);
  944. // 用户自己的逻辑处理 结束-----------------------------------------------------------
  945. //console.log('verifyReceiptRes: ', verifyReceiptRes);
  946. return {
  947. errCode: 0,
  948. errMsg: "ok",
  949. has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
  950. out_trade_no, // 支付插件订单号
  951. transaction_id, // 支付平台订单号
  952. status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款
  953. user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功)
  954. pay_order: payOrderInfo,
  955. };
  956. }
  957. /**
  958. * 获取对应支付配置
  959. * let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
  960. */
  961. async getUniPayConfig(data = {}) {
  962. let {
  963. provider,
  964. provider_pay_type,
  965. } = data;
  966. if (config && config[provider] && config[provider][provider_pay_type]) {
  967. let uniPayConfig = config[provider][provider_pay_type];
  968. if (!uniPayConfig.appId && provider !== "appleiap") {
  969. throw new Error(`uni-pay配置${provider}.${provider_pay_type}节点下的appId不能为空`);
  970. }
  971. return uniPayConfig;
  972. } else {
  973. throw new Error(`${provider}_${provider_pay_type} : 商户支付配置错误`);
  974. }
  975. }
  976. /**
  977. * 初始化uniPayInstance
  978. * let uniPayInstance = await service.pay.initUniPayInstance({ provider, provider_pay_type });
  979. */
  980. async initUniPayInstance(data = {}) {
  981. let {
  982. provider,
  983. } = data;
  984. let uniPayConifg = await this.getUniPayConfig(data);
  985. let uniPayInstance;
  986. if (provider === "wxpay") {
  987. // 微信
  988. if (uniPayConifg.version === 3) {
  989. try {
  990. uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
  991. } catch (err) {
  992. console.error(err);
  993. let errMsg = err.message;
  994. if (errMsg && errMsg.indexOf("invalid base64 body") > -1) {
  995. throw { errCode: ERROR[53005] };
  996. }
  997. throw err;
  998. }
  999. } else {
  1000. uniPayInstance = uniPay.initWeixin(uniPayConifg);
  1001. }
  1002. } else if (provider === "alipay") {
  1003. // 支付宝
  1004. uniPayInstance = uniPay.initAlipay(uniPayConifg);
  1005. } else if (provider === "appleiap") {
  1006. // ios内购
  1007. uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
  1008. } else if (provider === "wxpay-virtual") {
  1009. // 微信虚拟支付
  1010. // 还需要额外传accessToken
  1011. uniPayConifg.accessToken = await this.getAccessToken(data);
  1012. uniPayInstance = uniPay.initWeixinVirtualPayment(uniPayConifg);
  1013. } else {
  1014. throw new Error(`${provider} : 不支持的支付方式`);
  1015. }
  1016. return uniPayInstance;
  1017. }
  1018. /**
  1019. * 获取accessToken
  1020. * let uniPayInstance = await service.pay.getAccessToken({ provider, provider_pay_type });
  1021. */
  1022. async getAccessToken(data = {}) {
  1023. let uniPayConifg = await this.getUniPayConfig(data);
  1024. let cacheKey = {
  1025. appId: uniPayConifg.appId,
  1026. platform: "weixin-mp"
  1027. }
  1028. let cacheInfo = await dao.opendbOpenData.getAccessToken(cacheKey);
  1029. if (cacheInfo) {
  1030. // 缓存有值
  1031. return cacheInfo.access_token;
  1032. } else {
  1033. // 缓存无值
  1034. let getAccessTokenRes = await libs.wxpay.getAccessToken(uniPayConifg);
  1035. let accessToken = getAccessTokenRes.accessToken;
  1036. // 缓存accessToken
  1037. await dao.opendbOpenData.setAccessToken(cacheKey, {
  1038. access_token: getAccessTokenRes.accessToken,
  1039. }, getAccessTokenRes.expiresIn);
  1040. return accessToken;
  1041. }
  1042. }
  1043. /**
  1044. * 获取sessionKey
  1045. * let sessionKey = await service.pay.getSessionKey({ provider, provider_pay_type, openid });
  1046. */
  1047. async getSessionKey(data = {}) {
  1048. let {
  1049. openid,
  1050. } = data;
  1051. // 获取用户的sessionKey
  1052. let uniPayConifg = await this.getUniPayConfig(data);
  1053. let { session_key } = await dao.opendbOpenData.getSessionKey({
  1054. appId: uniPayConifg.appId,
  1055. platform: "weixin-mp",
  1056. openid
  1057. });
  1058. return session_key;
  1059. }
  1060. /**
  1061. * 请求微信小程序虚拟支付API
  1062. * let res = await service.pay.requestWxpayVirtualApi(data);
  1063. */
  1064. async requestWxpayVirtualApi(options = {}) {
  1065. let {
  1066. method,
  1067. data = {}
  1068. } = options;
  1069. // 微信虚拟支付固定参数
  1070. let provider = "wxpay-virtual";
  1071. let provider_pay_type = "mp";
  1072. // 获得微信小程序虚拟支付实例
  1073. let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
  1074. // 调用微信小程序虚拟支付云端API
  1075. if (["currencyPay"].indexOf(method) > -1) {
  1076. if (!data.sessionKey) {
  1077. data.sessionKey = await this.getSessionKey({ ...data, provider, provider_pay_type });
  1078. }
  1079. }
  1080. let res = await uniPayInstance[method](data);
  1081. return res;
  1082. }
  1083. }
  1084. module.exports = new service();