123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128 |
- /**
- * 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();
|