1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276 |
- /**
- * uni-pay-co 统一支付服务实现
- */
- const crypto = require("crypto");
- const uniPay = require("uni-pay");
- const configCenter = require("uni-config-center");
- const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');
- const dao = require('../dao');
- const libs = require('../libs');
- const { UniCloudError, isUniPayError, ERROR } = require('../common/error')
- const db = uniCloud.database();
- const _ = db.command;
- const notifyPath = "/payNotify/";
- class service {
- constructor(obj) {
- }
- /**
- * 获取支付插件的完整配置
- */
- getConfig() {
- return config;
- }
- /**
- * 支付成功 - 异步通知
- */
- async paymentNotify(data = {}) {
- let {
- httpInfo,
- clientInfo,
- cloudInfo,
- isWxpayVirtual
- } = data;
- console.log('httpInfo: ', httpInfo);
- let path = httpInfo.path;
- let pay_type = path.substring(notifyPath.length);
- let provider = pay_type.split("-")[0]; // 获取支付供应商
- let provider_pay_type = pay_type.split("-")[1]; // 获取支付方式
- if (isWxpayVirtual) {
- // 微信虚拟支付固定参数
- provider = "wxpay-virtual";
- provider_pay_type = "mp";
- }
- // 初始化uniPayInstance
- let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
- let notifyType = await uniPayInstance.checkNotifyType(httpInfo);
- console.log('notifyType: ', notifyType)
- if (notifyType === "token") {
- let verifyResult = await uniPayInstance.verifyTokenNotify(httpInfo);
- console.log('verifyResult: ', verifyResult)
- if (!verifyResult) {
- console.log('---------!签名验证未通过!---------');
- return;
- }
- // 校验token的测试接口,直接返回echostr
- return verifyResult.echostr;
- }
- if (notifyType !== "payment") {
- // 非支付通知直接返回成功
- console.log(`---------!非支付通知!---------`);
- return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
- }
- // 支付通知,验证签名
- let verifyResult = await uniPayInstance.verifyPaymentNotify(httpInfo);
- if (!verifyResult) {
- console.log('---------!签名验证未通过!---------');
- console.log('---------!签名验证未通过!---------');
- console.log('---------!签名验证未通过!---------');
- return {}
- }
- console.log('---------!签名验证通过!---------');
- verifyResult = JSON.parse(JSON.stringify(verifyResult)); // 这一句代码有用,请勿删除。
- console.log('verifyResult: ', verifyResult)
- let {
- outTradeNo,
- totalFee,
- transactionId,
- resultCode, // 微信支付v2和支付宝支付判断成功的字段
- openid,
- appId,
- tradeState, // 微信支付v3和微信虚拟支付判断支付成功的字段
- } = verifyResult;
- if (resultCode == "SUCCESS" || tradeState === "SUCCESS") {
- let time = Date.now();
- let payOrderInfo = await dao.uniPayOrders.updateAndReturn({
- whereJson: {
- status: 0, // status:0 为必须条件,防止重复推送时的错误
- out_trade_no: outTradeNo, // 商户订单号
- },
- dataJson: {
- status: 1, // 设置为已付款
- transaction_id: transactionId, // 第三方支付单号
- pay_date: time, // 更新支付时间(暂无法准确获取到支付时间,故用通知时间代替支付时间)
- notify_date: time, // 更新通知时间
- openid,
- provider, // 更新provider
- provider_pay_type, // 更新provider_pay_type
- original_data: httpInfo, // http回调信息,便于丢单时手动触发回调
- }
- });
- //console.log('payOrderInfo: ', payOrderInfo)
- if (payOrderInfo) {
- // 只有首次推送才执行用户自己的逻辑处理。
- // 用户自己的逻辑处理 开始-----------------------------------------------------------
- let userOrderSuccess = false;
- let orderPaySuccess;
- try {
- // 加载自定义异步回调函数
- orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
- } catch (err) {
- console.log(err);
- }
- if (typeof orderPaySuccess === "function") {
- console.log('用户自己的回调逻辑 - 开始执行');
- userOrderSuccess = await orderPaySuccess({
- verifyResult,
- data: payOrderInfo,
- clientInfo,
- cloudInfo
- });
- console.log('用户自己的回调逻辑 - 执行完成');
- }
- console.log('userOrderSuccess', userOrderSuccess);
- // 用户自己的逻辑处理 结束-----------------------------------------------------------
- await dao.uniPayOrders.updateAndReturn({
- whereJson: {
- status: 1,
- out_trade_no: outTradeNo,
- },
- dataJson: {
- user_order_success: userOrderSuccess,
- }
- });
- } else {
- console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
- console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
- console.log('---------!注意:本次回调非首次回调,已被插件拦截,插件不会执行你的回调函数!---------');
- console.log('verifyResult:', verifyResult);
- }
- } else {
- console.log('verifyResult:', verifyResult);
- }
- return libs.common.returnNotifySUCCESS({ provider, provider_pay_type });
- }
- /**
- * 微信虚拟支付异步通知
- */
- async wxpayVirtualNotify(data = {}) {
- return this.paymentNotify({
- ...data,
- isWxpayVirtual: true
- });
- }
- /**
- * 统一支付 - 创建支付订单
- */
- async createOrder(data = {}) {
- let {
- provider, // 支付供应商
- total_fee, // 支付金额
- user_id, // 用户user_id(统计需要)
- openid, // 用户openid
- order_no, // 订单号
- out_trade_no, // 支付插件订单号
- description, // 订单描述
- type, // 回调类型
- qr_code, // 是否强制使用扫码支付
- custom, // 自定义参数(不会发送给第三方支付服务器)
- other, // 其他请求参数(会发送给第三方支付服务器)
- clientInfo, // 客户端信息
- cloudInfo, // 云端信息
- wxpay_virtual, // 仅用于微信虚拟支付
- apple_virtual, // 仅用于苹果虚拟支付
- } = data;
- let subject = description;
- let body = description;
- if (!out_trade_no) out_trade_no = libs.common.createOrderNo();
- if (!order_no || typeof order_no !== "string") {
- throw { errCode: ERROR[51003] };
- }
- if (!type || typeof type !== "string") {
- throw { errCode: ERROR[51004] };
- }
- if (provider === "wxpay-virtual") {
- if (typeof wxpay_virtual !== "object") {
- throw { errCode: ERROR[51011] };
- }
- if (typeof wxpay_virtual.buy_quantity !== "number" || wxpay_virtual.buy_quantity <= 0) {
- throw { errCode: ERROR[51012] };
- }
- } else if (provider === "appleiap") {
- if (typeof apple_virtual !== "object") {
- throw { errCode: ERROR[51013] };
- }
- if (typeof apple_virtual.buy_quantity !== "number" || apple_virtual.buy_quantity <= 0) {
- throw { errCode: ERROR[51012] };
- }
- } else {
- if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
- throw { errCode: ERROR[51005] };
- }
- }
- if (!description || typeof description !== "string") {
- throw { errCode: ERROR[51006] };
- }
- if (!provider || typeof provider !== "string") {
- throw { errCode: ERROR[51007] };
- }
- if (!clientInfo) {
- throw { errCode: ERROR[51008] };
- }
- if (!cloudInfo) {
- throw { errCode: ERROR[51009] };
- }
- let res = { errCode: 0, errMsg: 'ok', order_no, out_trade_no, provider };
- let {
- clientIP: client_ip,
- userAgent: ua,
- appId: appid,
- deviceId: device_id,
- platform
- } = clientInfo;
- let {
- spaceId, // 服务空间ID
- } = cloudInfo;
- let {
- notifyUrl = {}
- } = config;
- // 业务逻辑开始-----------------------------------------------------------
- // 以下代码是为了兼容公测版迁移到正式版的空间
- let notifySpaceId = spaceId;
- if (!notifyUrl[notifySpaceId]) {
- if (notifySpaceId.indexOf("mp-") === 0) {
- notifySpaceId = notifySpaceId.substring(3);
- } else {
- notifySpaceId = `mp-${notifySpaceId}`
- }
- }
- // 以上代码是为了兼容公测版迁移到正式版的空间
- let currentNotifyUrl = notifyUrl[notifySpaceId] || notifyUrl["default"]; // 异步回调地址
- if (!currentNotifyUrl || currentNotifyUrl.indexOf("http") !== 0) {
- throw { errCode: ERROR[52002] };
- }
- platform = libs.common.getPlatform(platform);
- // 如果需要二维码支付模式,则清空下openid
- if (qr_code) {
- openid = undefined;
- res.qr_code = qr_code;
- }
- // 获取并自动匹配支付供应商的支付类型
- let provider_pay_type = libs.common.getProviderPayType({
- platform,
- provider,
- ua,
- qr_code
- });
- res.provider_pay_type = provider_pay_type;
- // 拼接实际异步回调地址
- let finalNotifyUrl = `${currentNotifyUrl}${notifyPath}${provider}-${provider_pay_type}`;
- // 获取uniPay交易类型
- let tradeType = libs.common.getTradeType({ provider, provider_pay_type });
- let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
- // 初始化uniPayInstance
- let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
- // 获取支付信息
- let getOrderInfoParam = {
- openid: openid,
- subject: subject,
- body: body,
- outTradeNo: out_trade_no,
- totalFee: total_fee,
- notifyUrl: finalNotifyUrl,
- tradeType: tradeType
- };
- if (provider === "wxpay" && provider_pay_type === "mweb") {
- getOrderInfoParam.spbillCreateIp = client_ip;
- if (uniPayConifg.version !== 3) {
- // v2版本
- getOrderInfoParam.sceneInfo = uniPayConifg.sceneInfo;
- } else {
- // v3版本特殊处理
- getOrderInfoParam.sceneInfo = JSON.parse(JSON.stringify(uniPayConifg.sceneInfo));
- if (getOrderInfoParam.sceneInfo.h5_info.wap_url) {
- getOrderInfoParam.sceneInfo.h5_info.app_url = getOrderInfoParam.sceneInfo.h5_info.wap_url;
- delete getOrderInfoParam.sceneInfo.h5_info.wap_url;
- }
- if (getOrderInfoParam.sceneInfo.h5_info.wap_name) {
- getOrderInfoParam.sceneInfo.h5_info.app_name = getOrderInfoParam.sceneInfo.h5_info.wap_name;
- delete getOrderInfoParam.sceneInfo.h5_info.wap_name;
- }
- }
- }
- let expand_data;
- try {
- // 如果是苹果内购,不需要执行uniPayInstance.getOrderInfo等操作
- if (provider !== "appleiap") {
- // 第三方支付服务器返回的订单信息
- let orderInfo;
- if (other) {
- // other 内的键名转驼峰
- other = libs.common.snake2camelJson(other);
- getOrderInfoParam = Object.assign(getOrderInfoParam, other);
- }
- getOrderInfoParam = JSON.parse(JSON.stringify(getOrderInfoParam)); // 此为去除undefined的参数
- if (provider === "wxpay-virtual") {
- // 微信虚拟支付扩展数据
- expand_data = {
- mode: wxpay_virtual.mode, // short_series_coin 代币充值; short_series_goods 道具直购
- buy_quantity: wxpay_virtual.buy_quantity,
- rate: uniPayConifg.rate || 100,
- sandbox: uniPayConifg.sandbox,
- };
- if (wxpay_virtual.mode === "short_series_goods") {
- expand_data.product_id = wxpay_virtual.product_id;
- expand_data.goods_price = wxpay_virtual.goods_price;
- }
- // 获取用户的sessionKey
- let { session_key } = await dao.opendbOpenData.getSessionKey({
- appId: uniPayConifg.appId,
- platform: "weixin-mp",
- openid: openid
- });
- getOrderInfoParam.sessionKey = session_key;
- getOrderInfoParam.mode = expand_data.mode;
- getOrderInfoParam.buyQuantity = expand_data.buy_quantity;
- getOrderInfoParam.productId = expand_data.product_id;
- getOrderInfoParam.goodsPrice = expand_data.goods_price;
- if (getOrderInfoParam.mode === "short_series_coin") {
- // 计算支付金额
- total_fee = expand_data.buy_quantity / (expand_data.rate || 100) * 100;
- } else if (getOrderInfoParam.mode === "short_series_goods") {
- // 计算支付金额
- total_fee = expand_data.buy_quantity * expand_data.goods_price;
- }
- }
- orderInfo = await uniPayInstance.getOrderInfo(getOrderInfoParam);
- if (qr_code && orderInfo.codeUrl) {
- res.qr_code_image = await libs.qrcode.toDataURL(orderInfo.codeUrl, {
- type: "image/png",
- width: 200,
- margin: 1,
- scale: 1,
- color: {
- dark: "#000000",
- light: "#ffffff",
- },
- errorCorrectionLevel: "Q",
- quality: 1
- });
- }
- // 支付宝支付参数特殊处理
- if (provider === "alipay") {
- if (typeof orderInfo === "object" && orderInfo.code && orderInfo.code !== "10000") {
- res.errCode = orderInfo.code;
- res.errMsg = orderInfo.subMsg;
- }
- }
- res.order = orderInfo;
- }
- } catch (err) {
- let errMsg = err.errorMessage || err.message;
- console.error("data: ", data);
- console.error("getOrderInfoParam: ", getOrderInfoParam);
- console.error("err: ", err);
- console.error("errMsg: ", errMsg);
- throw { errCode: ERROR[53001], errMsg };
- }
- // 尝试获取下订单信息
- let payOrderInfo = await dao.uniPayOrders.find({
- order_no,
- out_trade_no
- });
- let create_date = Date.now();
- // 如果订单不存在,则添加
- if (!payOrderInfo) {
- // 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
- let stat_platform = clientInfo.platform;
- if (stat_platform === "app") {
- stat_platform = clientInfo.os;
- }
- let nickname;
- if (user_id) {
- // 获取nickname(冗余昵称)
- let userInfo = await dao.uniIdUsers.findById(user_id);
- if (userInfo) nickname = userInfo.nickname;
- }
- let appleiap_account_token;
- if (provider === "appleiap") {
- appleiap_account_token = libs.crypto.generateUUID();
- res.appleiap_account_token = appleiap_account_token;
- }
- await dao.uniPayOrders.add({
- provider,
- provider_pay_type,
- uni_platform: platform,
- status: 0,
- type,
- order_no,
- out_trade_no,
- user_id,
- nickname,
- device_id,
- client_ip,
- openid,
- description,
- total_fee,
- refund_fee: 0,
- refund_count: 0,
- provider_appid: uniPayConifg.appId,
- appid,
- custom,
- create_date,
- expand_data,
- appleiap_account_token, // 苹果虚拟支付专用字段
- stat_data: {
- platform: stat_platform,
- app_version: clientInfo.appVersion,
- app_version_code: clientInfo.appVersionCode,
- app_wgt_version: clientInfo.appWgtVersion,
- os: clientInfo.os,
- ua: clientInfo.ua,
- channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
- scene: clientInfo.scene
- }
- });
- } else {
- // 如果订单已经存在,则修改下支付方式(用户可能先点微信支付,未付款,又点了支付宝支付)
- await dao.uniPayOrders.updateById(payOrderInfo._id, {
- provider,
- provider_pay_type,
- });
- }
- // 自动删除3天前的订单(未付款订单)
- // await dao.uniPayOrders.deleteExpPayOrders();
- // 业务逻辑结束-----------------------------------------------------------
- return res;
- }
- /**
- * 统一支付结果查询
- * @description 根据商户订单号或者平台订单号查询订单信息,主要用于未接收到支付通知时可以使用此接口进行支付结果验证
- */
- async getOrder(data = {}) {
- let {
- out_trade_no, // 支付插件订单号
- transaction_id, // 支付平台的交易单号
- await_notify = false, // 是否需要等待异步通知执行完成才返回前端支付结果
- } = data;
- let res = { errCode: 0, errMsg: 'ok' };
- // 业务逻辑开始-----------------------------------------------------------
- if (!out_trade_no && !transaction_id) {
- throw { errCode: ERROR[51010] };
- }
- let payOrderInfo;
- if (transaction_id) {
- payOrderInfo = await dao.uniPayOrders.find({
- transaction_id
- });
- } else if (out_trade_no) {
- payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no
- });
- }
- if (!payOrderInfo) {
- throw { errCode: ERROR[52001] };
- }
- // 初始化uniPayInstance
- let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
- let orderQueryJson = {};
- if (out_trade_no) {
- orderQueryJson.outTradeNo = out_trade_no;
- } else {
- orderQueryJson.transactionId = transaction_id;
- }
- if (payOrderInfo.provider === "wxpay-virtual") {
- orderQueryJson.openid = payOrderInfo.openid;
- }
- let queryRes;
- if (typeof uniPayInstance.orderQuery === "function") {
- queryRes = await uniPayInstance.orderQuery(orderQueryJson);
- console.log('queryRes: ', queryRes)
- } else {
- // 无uniPayInstance.orderQuery函数时的兼容处理
- if ([1, 2].indexOf(payOrderInfo.status) > -1) {
- queryRes = {
- tradeState: "SUCCESS",
- tradeStateDesc: "订单已支付"
- };
- } else if ([3].indexOf(payOrderInfo.status) > -1) {
- queryRes = {
- tradeState: "REFUNDED",
- tradeStateDesc: "订单已退款"
- };
- } else {
- queryRes = {
- tradeState: "NOPAY",
- tradeStateDesc: "订单未支付"
- };
- }
- }
- if (queryRes.tradeState === 'SUCCESS' || queryRes.tradeState === 'FINISHED') {
- if (typeof payOrderInfo.user_order_success == "undefined" && await_notify) {
- let whileTime = 0; // 当前循环已执行的时间(毫秒)
- let whileInterval = 500; // 每次循环间隔时间(毫秒)
- let maxTime = 20000; // 循环执行时间超过此值则退出循环(毫秒)
- while (typeof payOrderInfo.user_order_success == "undefined" && whileTime <= maxTime) {
- await libs.common.sleep(whileInterval);
- whileTime += whileInterval;
- payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no
- });
- }
- }
- res = {
- errCode: 0,
- errMsg: "ok",
- has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
- out_trade_no, // 支付插件订单号
- transaction_id, // 支付平台订单号
- status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款
- user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功)
- pay_order: payOrderInfo,
- }
- } else {
- let errMsg = queryRes.tradeStateDesc || "未支付或已退款";
- if (errMsg.indexOf("订单发生过退款") > -1) {
- errMsg = "订单已退款";
- }
- res = {
- errCode: -1,
- errMsg: errMsg,
- has_paid: false,
- out_trade_no, // 支付插件订单号
- transaction_id, // 支付平台订单号
- }
- }
- // 业务逻辑结束-----------------------------------------------------------
- return res;
- }
- /**
- * 统一退款
- * @description 当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家。
- */
- async refund(data = {}) {
- let {
- out_trade_no, // 插件支付单号
- out_refund_no, // 退款单号(若不传,则自动生成)
- refund_desc = "用户申请退款",
- refund_fee: myRefundFee,
- refund_fee_type = "CNY"
- } = data;
- let res = { errCode: 0, errMsg: 'ok' };
- // 业务逻辑开始-----------------------------------------------------------
- if (!out_trade_no) {
- throw { errCode: ERROR[51001] };
- }
- let payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no
- });
- if (!payOrderInfo) {
- throw { errCode: ERROR[52001] };
- }
- let refund_count = payOrderInfo.refund_count || 0;
- refund_count++;
- // 生成退款订单号
- let outRefundNo = out_refund_no ? out_refund_no : `${out_trade_no}-${refund_count}`;
- // 订单总金额
- let totalFee = payOrderInfo.total_fee;
- // 退款总金额
- let refundFee = myRefundFee || totalFee;
- let provider = payOrderInfo.provider;
- let uniPayConifg = await this.getUniPayConfig(payOrderInfo);
- let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
- let refundParams = {
- outTradeNo: out_trade_no,
- outRefundNo,
- totalFee,
- refundFee,
- refundDesc: refund_desc,
- refundFeeType: refund_fee_type
- };
- if (payOrderInfo.provider === "wxpay-virtual") {
- refundParams.openid = payOrderInfo.openid;
- refundParams.refundReason = "3"; // 0-暂无描述 1-产品问题,影响使用或效果不佳 2-售后问题,无法满足需求 3-意愿问题,用户主动退款 4-价格问题 5-其他原因
- refundParams.reqFrom = "2"; // 当前只支持"1"-人工客服退款,即用户电话给客服,由客服发起退款流程 "2"-用户自己发起退款流程 "3"-其他
- // 查询当前可退款金额
- refundParams.leftFee = totalFee - (payOrderInfo.refund_fee || 0);
- // 实时查询当前可退款金额(此处注释可省一次请求)
- // const orderQueryRes = await uniPayInstance.orderQuery({
- // openid: payOrderInfo.openid,
- // outTradeNo: payOrderInfo.out_trade_no
- // });
- // refundParams.leftFee = orderQueryRes.leftFee;
- }
- console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
- // 退款操作
- try {
- res.result = await uniPayInstance.refund(refundParams);
- } catch (err) {
- console.error(err);
- let errMsg = err.message;
- if (errMsg) {
- if (errMsg.indexOf("verify failure") > -1) {
- throw { errCode: ERROR[53005] };
- }
- if (errMsg.indexOf("header too long") > -1) {
- throw { errCode: ERROR[53005] };
- }
- }
- return { errCode: -1, errMsg: errMsg, err }
- }
- if (res.result.refundFee) {
- res.errCode = 0;
- res.errMsg = "ok";
- // 修改数据库
- try {
- let time = Date.now();
- // 修改订单状态
- payOrderInfo = await dao.uniPayOrders.updateAndReturn({
- whereJson: {
- _id: payOrderInfo._id,
- "refund_list.out_refund_no": _.neq(outRefundNo)
- },
- dataJson: {
- status: 2,
- refund_fee: _.inc(refundFee),
- refund_count: refund_count,
- refund_date: time, // 更新最近一次退款时间
- // 记录每次的退款详情
- refund_list: _.unshift({
- refund_date: time,
- refund_fee: refundFee,
- out_refund_no: outRefundNo,
- refund_desc
- })
- }
- });
- if (payOrderInfo && payOrderInfo.refund_fee >= payOrderInfo.total_fee) {
- // 修改订单状态为已全额退款
- await dao.uniPayOrders.updateById(payOrderInfo._id, {
- status: 3,
- refund_fee: payOrderInfo.total_fee,
- });
- }
- } catch (err) {
- console.error(err);
- }
- } else {
- console.log('res.result: ', res.result);
- throw { errCode: ERROR[53002] };
- }
- // 业务逻辑结束-----------------------------------------------------------
- return res;
- }
- /**
- * 查询退款(查询退款情况)
- * @description 提交退款申请后,通过调用该接口查询退款状态。
- */
- async getRefund(data = {}) {
- let {
- out_trade_no, // 插件支付单号
- } = data;
- if (!out_trade_no) {
- throw { errCode: ERROR[51001] };
- }
- let payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no
- });
- if (!payOrderInfo) {
- throw { errCode: ERROR[52001] };
- }
- let provider = payOrderInfo.provider;
- let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
- let queryRes;
- try {
- let refundQueryJson = {
- outTradeNo: out_trade_no,
- outRefundNo: payOrderInfo.refund_list[0].out_refund_no
- };
- if (provider === "wxpay-virtual") {
- refundQueryJson.openid = payOrderInfo.openid;
- }
- queryRes = await uniPayInstance.refundQuery(refundQueryJson);
- } catch (err) {
- throw { errCode: ERROR[53003], errMsg: err.errMsg };
- }
- let orderInfo = {
- total_fee: payOrderInfo.total_fee,
- refund_fee: payOrderInfo.refund_fee,
- refund_count: payOrderInfo.refund_count,
- refund_list: payOrderInfo.refund_list,
- provider: payOrderInfo.provider,
- provider_pay_type: payOrderInfo.provider_pay_type,
- status: payOrderInfo.status,
- type: payOrderInfo.type,
- out_trade_no: payOrderInfo.out_trade_no,
- transaction_id: payOrderInfo.transaction_id,
- };
- if (queryRes.refundFee > 0) {
- let msg = "ok";
- if (payOrderInfo.refund_list && payOrderInfo.refund_list.length > 0) {
- msg = `合计退款 ${payOrderInfo.refund_fee/100}\r\n`;
- for (let i in payOrderInfo.refund_list) {
- let item = payOrderInfo.refund_list[i];
- let index = Number(i) + 1;
- let timeStr = libs.common.timeFormat(item.refund_date, "yyyy-MM-dd hh:mm:ss");
- msg += `${index}、 ${timeStr} \r\n退款 ${item.refund_fee/100} \r\n`;
- }
- }
- return {
- errCode: 0,
- errMsg: msg,
- pay_order: orderInfo,
- result: queryRes
- }
- } else {
- throw { errCode: ERROR[53003] };
- }
- }
- /**
- * 关闭订单
- * @description 用于交易创建后,用户在一定时间内未进行支付,可调用该接口直接将未付款的交易进行关闭,避免重复支付。
- * 注意
- * 微信支付:订单生成后不能马上调用关单接口,最短调用时间间隔为 5 分钟。
- * 微信虚拟支付:不支持关闭订单
- */
- async closeOrder(data = {}) {
- let {
- out_trade_no, // 插件支付单号
- } = data;
- if (!out_trade_no) {
- throw { errCode: ERROR[51001] };
- }
- let payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no
- });
- if (!payOrderInfo) {
- throw { errCode: ERROR[52001] };
- }
- let { provider } = payOrderInfo;
- let uniPayInstance = await this.initUniPayInstance(payOrderInfo);
- let closeOrderRes = await uniPayInstance.closeOrder({
- outTradeNo: out_trade_no
- });
- let wxpayResult = (provider === "wxpay" && closeOrderRes.resultCode === "SUCCESS");
- let alipayResult = (provider === "alipay" && closeOrderRes.code === "10000");
- if (wxpayResult || alipayResult) {
- // 修改订单状态为已取消
- await dao.uniPayOrders.update({
- whereJson: {
- _id: payOrderInfo._id,
- status: 0
- },
- dataJson: {
- status: -1,
- cancel_date: Date.now()
- }
- });
- return {
- errCode: 0,
- errMsg: "订单关闭成功",
- result: closeOrderRes
- }
- } else {
- throw { errCode: ERROR[53004] };
- }
- }
- /**
- * 根据code获取openid
- */
- async getOpenid(data = {}) {
- let {
- provider, // 支付供应商
- code, // 用户登录获取的code
- clientInfo, // 客户端环境
- } = data;
- if (!code) {
- throw { errCode: ERROR[51002] };
- }
- let { platform, ua } = clientInfo;
- // 获取并自动匹配支付供应商的支付类型
- let provider_pay_type = libs.common.getProviderPayType({
- provider,
- platform,
- ua
- });
- let needCacheSessionKey = false;
- let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
- if (provider === "wxpay") {
- try {
- // 如果配置了微信虚拟支付,则使用微信虚拟支付的配置作为微信获取openid的配置
- let wxpayVirtualPayConifg = await this.getUniPayConfig({ provider: "wxpay-virtual", provider_pay_type: "mp" });
- if (wxpayVirtualPayConifg && wxpayVirtualPayConifg.appId && wxpayVirtualPayConifg.secret) {
- uniPayConifg = wxpayVirtualPayConifg;
- needCacheSessionKey = true;
- }
- } catch (err) {}
- let res = await libs.wxpay.getOpenid({
- config: uniPayConifg,
- code,
- provider_pay_type,
- });
- if (needCacheSessionKey) {
- // 将session_key保存到缓存表中
- let cacheKey = {
- appId: uniPayConifg.appId,
- platform: "weixin-mp",
- openid: res.openid
- }
- let session_key = res.session_key;
- delete res.session_key;
- await dao.opendbOpenData.setSessionKey(cacheKey, { session_key }, 30 * 24 * 60 * 60);
- }
- return res;
- } else if (provider === "alipay") {
- return await libs.alipay.getOpenid({
- config: uniPayConifg,
- code,
- });
- }
- }
- /**
- * 获取支持的支付方式
- * let payTypes = await service.pay.getPayProviderFromCloud();
- */
- async getPayProviderFromCloud() {
- let wxpay = config.wxpay && config.wxpay.enable ? true : false;
- let alipay = config.alipay && config.alipay.enable ? true : false;
- let provider = [];
- if (wxpay) provider.push("wxpay");
- if (alipay) provider.push("alipay");
- return {
- errCode: 0,
- errMsg: "ok",
- wxpay,
- alipay,
- provider
- };
- }
- /**
- * 验证iosIap苹果内购支付凭据
- * let payTypes = await service.pay.verifyReceiptFromAppleiap();
- */
- async verifyReceiptFromAppleiap(data) {
- let {
- out_trade_no,
- appleiap_account_token,
- transaction_receipt,
- transaction_identifier,
- clientInfo,
- } = data;
- if (!out_trade_no) {
- if (!appleiap_account_token) {
- return {
- errCode: 0,
- errMsg: "Invalid out_trade_no"
- }
- }
- appleiap_account_token = appleiap_account_token.toLowerCase(); // 转小写
- let payOrderInfo = await dao.uniPayOrders.find({
- provider: "appleiap",
- appleiap_account_token
- });
- if (!payOrderInfo || !payOrderInfo.out_trade_no) {
- return {
- errCode: 0,
- errMsg: "Invalid out_trade_no"
- }
- }
- out_trade_no = payOrderInfo.out_trade_no;
- }
- let payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no,
- });
- if (!payOrderInfo) {
- throw { errCode: ERROR[52001] };
- }
- const verifyReceipt = async (uniPayConifg) => {
- const jwt = libs.jsonwebtoken;
- const fs = require('fs');
- const privateKey = fs.readFileSync(uniPayConifg.appCertPath, 'utf8');
- const header = {
- alg: 'ES256',
- kid: uniPayConifg.appId, // 替换为您的密钥ID
- typ: "JWT"
- };
- const nowTime = Date.now();
- const bundleId = uniPayConifg.sandbox ? uniPayConifg.devBundleId || uniPayConifg.bundleId : uniPayConifg.bundleId;
- const payload = {
- iss: uniPayConifg.issuerId, // 替换为您的团队ID
- iat: Math.floor(nowTime / 1000), // 当前时间戳
- exp: Math.floor(nowTime / 1000) + 3600, // 当前时间戳加1小时
- aud: 'appstoreconnect-v1',
- bid: bundleId
- };
- const iapToken = jwt.sign(payload, privateKey, {
- algorithm: 'ES256',
- header: header
- });
- const serviceUrl = uniPayConifg.sandbox ? "https://api.storekit-sandbox.itunes.apple.com" : "https://api.appstoreconnect.apple.com";
- const url = `${serviceUrl}/inApps/v1/transactions/${transaction_identifier}`;
- let requestRes;
- // 如果请求苹果服务器失败,则重试5次
- for (let i = 0; i <= 5; i++) {
- try {
- requestRes = await uniCloud.request({
- method: "GET",
- header: {
- 'Authorization': `Bearer ${iapToken}`,
- 'Content-Type': 'application/json'
- },
- url
- });
- break;
- } catch (err) {
- // console.log('errCode: ', err.code || err.errCode, 'errMsg: ', err.message || err.errMsg)
- }
- }
- if (requestRes.statusCode !== 200) {
- return {};
- }
- const signedInfoTokenArr = requestRes.data.signedTransactionInfo.split('.');
- const signedInfoString = Buffer.from(signedInfoTokenArr[1], 'base64').toString('utf8');
- const verifyReceiptRes = JSON.parse(signedInfoString);
- const appAccountToken = verifyReceiptRes.appAccountToken.toLowerCase();
- verifyReceiptRes.tradeState = verifyReceiptRes.inAppOwnershipType === "PURCHASED" && payOrderInfo.appleiap_account_token === appAccountToken ? "SUCCESS" : "fail";
- return verifyReceiptRes;
- };
- let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" });
- let verifyReceiptRes = await verifyReceipt(uniPayConifg);
- let userOrderSuccess = false;
- let pay_date;
- if (verifyReceiptRes.tradeState !== "SUCCESS") {
- // 尝试使用相反的环境再次验证
- console.log('尝试使用相反的环境再次验证: ');
- verifyReceiptRes = await verifyReceipt({
- ...uniPayConifg,
- sandbox: !uniPayConifg.sandbox
- });
- if (verifyReceiptRes.tradeState !== "SUCCESS") {
- // 如果还是不成功,则校验不通过
- throw { errCode: ERROR[54002] };
- }
- }
- //console.log('verifyReceiptRes: ', verifyReceiptRes)
- let isSubscribe = false;
- if (["Auto-Renewable Subscription"].indexOf(verifyReceiptRes.type) > -1) {
- isSubscribe = true; // 标记为自动订阅订单
- }
- // 支付成功
- pay_date = Number(verifyReceiptRes.purchaseDate);
- let quantity = verifyReceiptRes.quantity; // 购买数量
- let product_id = verifyReceiptRes.productId; // 对应的内购产品id
- let transaction_id = verifyReceiptRes.transactionId; // 本次交易id
- let original_transaction_id = verifyReceiptRes.originalTransactionId; // 原始交易id
- if ((Date.now() - 1000 * 3600 * 72) > pay_date && !isSubscribe) {
- // 非自动订阅订单,若超72小时,不做处理,通知前端直接关闭订单。
- return {
- errCode: 0,
- errMsg: "ok"
- };
- }
- if (isSubscribe && original_transaction_id !== transaction_id) {
- let findOrderInfo = await dao.uniPayOrders.find({
- appleiap_account_token: payOrderInfo.appleiap_account_token,
- user_order_success: _.exists(true)
- });
- if (findOrderInfo) {
- // 自动订阅产品自动续期时需要创建新的支付订单
- let quantity = verifyReceiptRes.quantity;
- let goods_price = parseFloat((verifyReceiptRes.price / 1000).toFixed(2));
- let total_fee = parseFloat((goods_price * 100 * quantity).toFixed(2));
- let description = "[自动续期]" + payOrderInfo.description.replace(/\[自动续期\]/g, '');
- // 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
- let stat_platform = clientInfo.platform;
- if (stat_platform === "app") {
- stat_platform = clientInfo.os;
- }
- // 创建新的支付订单
- let addId = await dao.uniPayOrders.add({
- provider: payOrderInfo.provider,
- provider_pay_type: payOrderInfo.provider_pay_type,
- uni_platform: clientInfo.platform,
- status: 0,
- type: payOrderInfo.type,
- order_no: payOrderInfo.order_no,
- out_trade_no: transaction_id,
- user_id: payOrderInfo.user_id,
- nickname: payOrderInfo.nickname,
- device_id: clientInfo.deviceId,
- client_ip: clientInfo.client_ip,
- openid: payOrderInfo.openid,
- description,
- total_fee,
- refund_fee: 0,
- refund_count: 0,
- provider_appid: uniPayConifg.appId,
- appid: clientInfo.appId,
- custom: payOrderInfo.custom,
- create_date: Date.now(),
- expand_data: payOrderInfo.expand_data,
- appleiap_account_token, // 苹果虚拟支付专用字段
- stat_data: {
- platform: stat_platform,
- app_version: clientInfo.appVersion,
- app_version_code: clientInfo.appVersionCode,
- app_wgt_version: clientInfo.appWgtVersion,
- os: clientInfo.os,
- ua: clientInfo.ua,
- channel: clientInfo.channel ? clientInfo.channel : String(clientInfo.scene),
- scene: clientInfo.scene
- }
- });
- payOrderInfo = await dao.uniPayOrders.find({
- _id: addId,
- });
- out_trade_no = transaction_id;
- }
- }
- // 查询该transaction_id是否使用过,如果已使用,则不做处理,通知前端直接关闭订单。
- let findOrderInfo = await dao.uniPayOrders.find({
- transaction_id,
- });
- const repeatReceipt = () => {
- return {
- errCode: 0,
- errMsg: "ok",
- repeat: true, // 代表重复通知了
- };
- };
- if (findOrderInfo) {
- // 不允许重复通知
- return repeatReceipt();
- }
- // 否则,执行用户回调
- // 用户自己的逻辑处理 开始-----------------------------------------------------------
- let orderPaySuccess;
- try {
- // 加载自定义异步回调函数
- orderPaySuccess = require(`../notify/${payOrderInfo.type}`);
- } catch (err) {
- console.log(err);
- }
- if (typeof orderPaySuccess === "function") {
- let newPayOrderInfo = await dao.uniPayOrders.updateAndReturn({
- whereJson: {
- status: 0, // status:0 为必须条件,防止重复推送时的错误
- out_trade_no: out_trade_no, // 商户订单号
- },
- dataJson: {
- status: 1, // 设置为已付款
- transaction_id: transaction_id, // 第三方支付单号
- pay_date: pay_date,
- notify_date: pay_date,
- original_data: verifyReceiptRes
- }
- });
- if (!newPayOrderInfo) {
- // 不允许重复通知
- return repeatReceipt();
- }
- payOrderInfo = newPayOrderInfo;
- console.log('用户自己的回调逻辑 - 开始执行');
- userOrderSuccess = await orderPaySuccess({
- verifyResult: verifyReceiptRes,
- data: payOrderInfo,
- });
- console.log('用户自己的回调逻辑 - 执行完成');
- payOrderInfo = await dao.uniPayOrders.updateAndReturn({
- whereJson: {
- status: 1,
- out_trade_no,
- },
- dataJson: {
- user_order_success: userOrderSuccess,
- }
- });
- } else {
- payOrderInfo = await dao.uniPayOrders.find({
- out_trade_no,
- });
- }
- console.log('userOrderSuccess', userOrderSuccess);
- // 用户自己的逻辑处理 结束-----------------------------------------------------------
- //console.log('verifyReceiptRes: ', verifyReceiptRes);
- return {
- errCode: 0,
- errMsg: "ok",
- has_paid: true, // 标记用户是否已付款成功(此参数只能表示用户确实付款了,但系统的异步回调逻辑可能还未执行完成)
- out_trade_no, // 支付插件订单号
- transaction_id, // 支付平台订单号
- status: payOrderInfo.status, // 标记当前支付订单状态 -1:已关闭 0:未支付 1:已支付 2:已部分退款 3:已全额退款
- user_order_success: payOrderInfo.user_order_success, // 用户异步通知逻辑是否全部执行完成,且无异常(建议前端通过此参数是否为true来判断是否支付成功)
- pay_order: payOrderInfo,
- is_subscribe: isSubscribe
- };
- }
- /**
- * 获取对应支付配置
- * let uniPayConifg = await this.getUniPayConfig({ provider, provider_pay_type });
- */
- async getUniPayConfig(data = {}) {
- let {
- provider,
- provider_pay_type,
- } = data;
- if (config && config[provider] && config[provider][provider_pay_type]) {
- let uniPayConfig = config[provider][provider_pay_type];
- if (!uniPayConfig.appId && provider !== "appleiap") {
- throw new Error(`uni-pay配置${provider}.${provider_pay_type}节点下的appId不能为空`);
- }
- return uniPayConfig;
- } else {
- throw new Error(`${provider}_${provider_pay_type} : 商户支付配置错误`);
- }
- }
- /**
- * 初始化uniPayInstance
- * let uniPayInstance = await service.pay.initUniPayInstance({ provider, provider_pay_type });
- */
- async initUniPayInstance(data = {}) {
- let {
- provider,
- } = data;
- let uniPayConifg = await this.getUniPayConfig(data);
- let uniPayInstance;
- if (provider === "wxpay") {
- // 微信
- if (uniPayConifg.version === 3) {
- try {
- uniPayInstance = uniPay.initWeixinV3(uniPayConifg);
- } catch (err) {
- console.error(err);
- let errMsg = err.message;
- if (errMsg && errMsg.indexOf("invalid base64 body") > -1) {
- throw { errCode: ERROR[53005] };
- }
- throw err;
- }
- } else {
- uniPayInstance = uniPay.initWeixin(uniPayConifg);
- }
- } else if (provider === "alipay") {
- // 支付宝
- uniPayInstance = uniPay.initAlipay(uniPayConifg);
- } else if (provider === "appleiap") {
- // 苹果虚拟支付
- uniPayInstance = uniPay.initAppleIapPayment(uniPayConifg);
- } else if (provider === "wxpay-virtual") {
- // 微信虚拟支付
- // 还需要额外传accessToken
- uniPayConifg.accessToken = await this.getAccessToken(data);
- uniPayInstance = uniPay.initWeixinVirtualPayment(uniPayConifg);
- } else {
- throw new Error(`${provider} : 不支持的支付方式`);
- }
- return uniPayInstance;
- }
- /**
- * 获取accessToken
- * let uniPayInstance = await service.pay.getAccessToken({ provider, provider_pay_type });
- */
- async getAccessToken(data = {}) {
- let uniPayConifg = await this.getUniPayConfig(data);
- let cacheKey = {
- appId: uniPayConifg.appId,
- platform: "weixin-mp"
- }
- let cacheInfo = await dao.opendbOpenData.getAccessToken(cacheKey);
- if (cacheInfo) {
- // 缓存有值
- return cacheInfo.access_token;
- } else {
- // 缓存无值
- let getAccessTokenRes = await libs.wxpay.getAccessToken(uniPayConifg);
- let accessToken = getAccessTokenRes.accessToken;
- // 缓存accessToken
- await dao.opendbOpenData.setAccessToken(cacheKey, {
- access_token: getAccessTokenRes.accessToken,
- }, getAccessTokenRes.expiresIn);
- return accessToken;
- }
- }
- /**
- * 获取sessionKey
- * let sessionKey = await service.pay.getSessionKey({ provider, provider_pay_type, openid });
- */
- async getSessionKey(data = {}) {
- let {
- openid,
- } = data;
- // 获取用户的sessionKey
- let uniPayConifg = await this.getUniPayConfig(data);
- let { session_key } = await dao.opendbOpenData.getSessionKey({
- appId: uniPayConifg.appId,
- platform: "weixin-mp",
- openid
- });
- return session_key;
- }
- /**
- * 请求微信小程序虚拟支付API
- * let res = await service.pay.requestWxpayVirtualApi(data);
- */
- async requestWxpayVirtualApi(options = {}) {
- let {
- method,
- data = {}
- } = options;
- // 微信虚拟支付固定参数
- let provider = "wxpay-virtual";
- let provider_pay_type = "mp";
- // 获得微信小程序虚拟支付实例
- let uniPayInstance = await this.initUniPayInstance({ provider, provider_pay_type });
- // 调用微信小程序虚拟支付云端API
- if (["currencyPay"].indexOf(method) > -1) {
- if (!data.sessionKey) {
- data.sessionKey = await this.getSessionKey({ ...data, provider, provider_pay_type });
- }
- }
- let res = await uniPayInstance[method](data);
- return res;
- }
- }
- module.exports = new service();
|