/** * 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('用户自己的回调逻辑 - 开始执行'); try { userOrderSuccess = await orderPaySuccess({ verifyResult, data: payOrderInfo, clientInfo, cloudInfo }); console.log('用户自己的回调逻辑 - 执行完成'); } catch(err){ userOrderSuccess = false; console.log('用户自己的回调逻辑 - 执行异常', err); } } 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, // 仅用于微信虚拟支付 } = 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 (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; } 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, 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, transaction_receipt, transaction_identifier, } = data; if (!out_trade_no) { throw { errCode: ERROR[51001] }; } // 初始化uniPayInstance let uniPayInstance = await this.initUniPayInstance({ provider: "appleiap", provider_pay_type: "app" }); let verifyReceiptRes = await uniPayInstance.verifyReceipt({ receiptData: transaction_receipt }); let userOrderSuccess = false; let pay_date; if (verifyReceiptRes.tradeState !== "SUCCESS") { // 尝试使用相反的环境再次验证 let uniPayConifg = await this.getUniPayConfig({ provider: "appleiap", provider_pay_type: "app" }); uniPayInstance = uniPay.initAppleIapPayment({ ...uniPayConifg, sandbox: !uniPayConifg.sandbox, }); verifyReceiptRes = await uniPayInstance.verifyReceipt({ receiptData: transaction_receipt }); if (verifyReceiptRes.tradeState !== "SUCCESS") { // 如果还是不成功,则校验不通过 throw { errCode: ERROR[54002] }; } } // 支付成功 pay_date = Number(verifyReceiptRes.receipt.receipt_creation_date_ms); let inAppList = verifyReceiptRes.receipt.in_app; let inApp = inAppList.find((item) => { return item.transaction_id === transaction_identifier; }); if (!inApp) { // 校验不通过 throw { errCode: ERROR[54002] }; } let quantity = inApp.quantity; // 购买数量 let product_id = inApp.product_id; // 对应的内购产品id let transaction_id = inApp.transaction_id; // 本次交易id if ((Date.now() - 1000 * 3600 * 24) > pay_date) { // 订单已超24小时,不做处理,通知前端直接关闭订单。 return { errCode: 0, errMsg: "ok" }; } // 查询该transaction_id是否使用过,如果已使用,则不做处理,通知前端直接关闭订单。 let findOrderInfo = await dao.uniPayOrders.find({ transaction_id, }); if (findOrderInfo) { return { errCode: 0, errMsg: "ok" }; } // 否则,执行用户回调 // 用户自己的逻辑处理 开始----------------------------------------------------------- let orderPaySuccess; let payOrderInfo = await dao.uniPayOrders.find({ out_trade_no, }); if (!payOrderInfo) { throw { errCode: ERROR[52001] }; } try { // 加载自定义异步回调函数 orderPaySuccess = require(`../notify/${payOrderInfo.type}`); } catch (err) { console.log(err); } if (typeof orderPaySuccess === "function") { payOrderInfo = 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 } }); 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, }; } /** * 获取对应支付配置 * 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") { // ios内购 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();