const { getDatabase } = require("../../mongodb"); const dayjs = require("dayjs"); const BigNumber = require("bignumber.js"); const alipayApi = require("../../alipayApi"); const unionPayApi = require("../union-pay"); const common = require("../../common"); let db = null; async function getDb() { if (!db) { db = await getDatabase("Internet-hospital"); } if (!db) { throw new Error("数据库连接失败"); } return db; } exports.handlePayNotify = async function (req, res) { let logInfo = { createTime: new Date(), type: 'alipayPayNotify' }; let db = null; try { db = await getDb(); const requestData = req.body; const { success, data, message } = await alipayApi.main({ type: "parsePayNotify", data: requestData }); // 传递 user 信息 logInfo.status = success ? 'success' : 'fail'; logInfo.message = message; logInfo.data = data; logInfo.originData = JSON.stringify(requestData); if (success) { const tradeNo = data.tradeNo; const payTime = new Date(data.gmtPayment); const trade = await db.collection('trades').findOne({ tradeNo }); if (trade) { await Promise.all([ db.collection('trades').updateOne({ tradeNo }, { $set: { tradeStatus: 'paid', payTime, updateTime: dayjs().valueOf() } }), exports.invokePaySuccess({ trade, tradeNo, payTime }, db) ]) } } res.send('success') } catch (error) { logInfo.status = 'error'; logInfo.message = error.message; logInfo.originData = JSON.stringify(req.body); res.status(500).send(error.message); } try { db.collection("third_merchant_notify").insertOne(logInfo) } catch (e) { } } exports.handleUnionPayNotify = async function (req, res) { let logInfo = { createTime: new Date(), type: 'unionPayNotify' }; let db = null; const requestData = req.body; try { db = await getDb(); logInfo.originData = JSON.stringify(requestData); if (requestData && requestData.billNo && requestData.qrCodeId && requestData.billStatus === 'PAID') { const trade = await db.collection('trades').findOne({ tradeNo: requestData.qrCodeId, outTradeNo: requestData.billNo, tradeStatus: 'unpay' }); if (trade) { const { data } = await unionPayApi.main({ type: "getUnionPayBill", billNo: trade.outTradeNo, billDate: trade.createTime }); // 传递 user 信息 const paid = data && data.billStatus === 'PAID'; const payTime = data && data.billPayment && data.billPayment.payTime ? data.billPayment.payTime : Date.now(); if (paid) { await Promise.all([ db.collection('trades').updateOne({ _id: trade._id }, { $set: { tradeStatus: 'paid', payTime, updateTime: dayjs().valueOf() } }), exports.invokePaySuccess({ trade, tradeNo: requestData.qrCodeId, payTime }, db) ]) } else { res.status(500).send('解析银联支付通知失败'); } } } res.send('success') } catch (error) { logInfo.status = 'error'; logInfo.message = error.message; logInfo.originData = JSON.stringify(requestData); res.status(500).send(error.message); } try { db.collection("third_merchant_notify").insertOne(logInfo) } catch (e) { } console.log('收到银联通知了~~~~~') } exports.getAlipayTradeNo = async function (data) { try { const db = await getDb(); const record = await db.collection("trades").findOne({ businessNo: data.businessNo, businessType: data.businessType, platform: 'alipay', tradeStatus: { $ne: 'closed' }, }) // 不存在 if (!record || record.tradeStatus === 'closed') { return await exports.createAlipayTrade(data); // 传递 user 信息 } // 存在待支付且未过期 if (record.tradeStatus === 'unpay') { return await exports.reUseTradeNo(data, record, db); } if (record.tradeStatus === 'paid') { return { success: true, message: '订单已经支付', tradeNo: record.tradeNo, paid: true } } if (record.tradeStatus === 'refunding' || record.tradeStatus === 'refunded') { return { success: true, message: '订单已经退款', refunded: true } } return { success: false, message: '订单状态异常, 请联系工作人员' } } catch (e) { return { success: false, message: e.message }; } } exports.createAlipayTrade = async function (data) { try { let db = await getDb(); const trade = { outTradeNo: common.generateUniqueStr('ZFB'), tradeStatus: 'unpay', totalAmount: data.totalAmount, businessNo: data.businessNo, businessType: data.businessType, platform: 'alipay', desc: data.desc, openId: data.openId, expireTime: dayjs().add(30, 'minute').valueOf(), createTime: dayjs().valueOf(), updateTime: dayjs().valueOf(), } const { success, message, data: tradeInfo } = await alipayApi.main({ type: "createTrade", data: trade }); // 传递 user 信息 if (success) { trade.appId = tradeInfo.appId; trade.tradeNo = tradeInfo.tradeNo; trade.outTradeNo = tradeInfo.outTradeNo; trade.notifyUrl = tradeInfo.notifyUrl; const result = await db.collection("trades").insertOne(trade); return { success: true, id: result.insertedId, message: '创建交易订单成功', tradeNo: tradeInfo.tradeNo }; } else { return { success: false, message: message }; } } catch (error) { return { success: false, message: error.message }; } } exports.queryAlipayTradeNo = async function ({ tradeNo }) { try { const res = await alipayApi.main({ type: "queryTrade", tradeNo: tradeNo }); return res } catch (e) { return { success: false, message: e.message }; } } exports.reUseTradeNo = async function (data, record, db) { try { const { tradeNo } = record; const result = await exports.queryAlipayTradeNo({ tradeNo }); if (!result || !result.success || !result.data) { return { success: false, message: result && result.message ? result.message : '查询交易订单失败' }; } if (result.data.tradeStatus === 'WAIT_BUYER_PAY') { return { success: true, message: '交易订单未支付,可以继续使用', tradeNo }; } if (result.data.tradeStatus === 'TRADE_SUCCESS' || result.data.tradeStatus === 'TRADE_FINISHED') { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'paid', payTime: new Date(result.data.sendPayDate), updateTime: dayjs().valueOf() } }) // 执行支付成功逻辑 return { success: true, message: '订单已经支付', paid: true, tradeNo } } if (result.data.tradeStatus === 'TRADE_CLOSED') { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'closed', updateTime: dayjs().valueOf() } }) return await exports.createAlipayTrade(data); } return { success: false, message: '交易订单状态异常, 请联系工作人员' }; } catch (e) { return { success: false, message: e.message }; } } exports.invokePaySuccess = async function ({ trade, tradeNo, payTime }, db) { const { businessNo, businessType } = trade; if (businessType === 'onlineMedicinePurchase') { const medicinePurchaseApi = require('../medicine-purchase-order'); await medicinePurchaseApi({ type: "invokeOnlineMedicinePurchasePaySuccess", tradeNo, businessNo, payTime }, db); } else if (businessType === 'chineseMedicineOrder') { const medicalWinePurchaseApi = require('../chinese-medicine-order'); await medicalWinePurchaseApi.main({ type: "invokeMedicalWinePurchasePaySuccess", tradeNo, businessNo, payTime }, db); } } exports.refundTrade = async function ({ tradeNo, reason }) { try { const db = await getDb(); const record = await db.collection("trades").findOne({ tradeNo }); if (!record) { return { success: false, message: '交易记录不存在' }; } if (record.tradeStatus === 'refunding') { //执行查询操作 判断是否退款成功 } if (record.tradeStatus === 'refunded') { return { success: true, message: '订单已经退款', data: { tradeStatus: 'refunded', refundTime: record.refundTime } }; } if (record.tradeStatus == 'paid') { const result = await alipayApi.main({ type: "refundTrade", tradeNo, reason, totalAmount: record.totalAmount }); if (!result || !result.success) { return { success: false, message: result && result.message ? result.message : '退款失败' }; } const { tradeStatus, refundTime } = result; await db.collection('trades').updateOne({ _id: record._id }, { $set: { tradeStatus, updateTime: dayjs().valueOf(), refundReason: reason, refundTime, refundApplyTime: dayjs().valueOf() } }); return { success: true, message: '申请退款成功', data: { tradeStatus, refundTime } }; } return { success: false, message: '当前交易状态不支持退款' } } catch (e) { return { success: false, message: e.message }; } } exports.queryAlipayRefundDetail = async function ({ tradeNo }) { try { const db = await getDb(); const record = await db.collection("trades").findOne({ tradeNo }); if (!record) { return { success: false, message: '交易记录不存在' }; } const result = await alipayApi.main({ type: "queryRefundDetail", tradeNo, outTradeNo: record.outTradeNo }); if (!result || !result.success) { return { success: false, message: result && result.message ? result.message : '查询退款详情失败' }; } return { success: true, data: result.data }; } catch (e) { return { success: false, message: e.message }; } } exports.createUnionPayTrade = async function (data) { try { let db = await getDb(); const { totalAmount, businessNo, businessType, desc, expireTime } = data; const record = await db.collection("trades").findOne({ businessNo, businessType, tradeStatus: { $ne: 'closed' } }); if (record && record.tradeStatus === 'unpay') { const { data: bill } = await unionPayApi.main({ type: "getUnionPayBill", billNo: record.outTradeNo, billDate: record.createTime }) const billStatus = bill && bill.billDate ? bill.billStatus : ''; if ('REFUND' === billStatus) { return { success: false, message: '账单已经退款' } } if ('PAID' === billStatus) { return { success: false, message: '账单已经支付, 请刷新' } } if (!['UNPAID', 'CLOSED'].includes(billStatus)) { return { success: false, message: '账单状态异常, 请联系工作人员' } } const res = await unionPayApi.main({ type: 'closeUnpayTrade', billNo: record.outTradeNo, billDate: record.createTime, qrCodeId: record.tradeNo }) console.log('关闭交易单: ', res) if (!res || !res.success) { return res } await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'closed', updateTime: dayjs().valueOf() } }) // 查询是否支付完成 支付完成执行回调 未完成支付则关闭 } else if (record && record.tradeStatus === 'paid') { await exports.invokePaySuccess({ trade: record, tradeNo: record.tradeNo, payTime: record.payTime }, db); return { success: false, status: 'paid', message: '账单已经支付' } } else if (record && ['refunding', 'refunded'].includes(record.tradeStatus)) { return { success: false, message: '账单已经退款' } } else if (record) { return { success: false, message: '当前业务账单异常, 请联系工作人员' } } const trade = { outTradeNo: common.generateUniqueStr('3JHR'), tradeStatus: 'unpay', totalAmount, businessNo, businessType, desc, platform: 'unionPay', expireTime: expireTime && dayjs(expireTime).isValid() ? dayjs(expireTime).valueOf() : dayjs().add(30, 'minute').valueOf(), createTime: dayjs().valueOf(), updateTime: dayjs().valueOf(), } const cent = new BigNumber(totalAmount).times(100).toNumber(); const res = await unionPayApi.main({ type: "getUnionPayQrcode", totalAmount: cent, billNo: trade.outTradeNo, desc, expireTime: dayjs(trade.expireTime).format("YYYY-MM-DD HH:mm:ss") }); if (res && res.success) { trade.tradeNo = res.qrCodeId; trade.notifyUrl = res.notifyUrl || ''; trade.appId = res.appId; const { insertedId } = await db.collection("trades").insertOne(trade); if (insertedId) { return { success: true, message: '创建成功', qrcode: res.qrcode } } return { success: false, message: '创建失败' } } return res; } catch (error) { return { success: false, message: error.message }; } } exports.checkUnionPayUnpay = async function (data) { try { const db = await getDb(); const { businessNo, businessType } = data; const record = await db.collection("trades").findOne({ businessNo, businessType, tradeStatus: { $ne: 'closed' } }); if (!record) { return { success: true, status: 'unpay' } } if (record.tradeStatus === 'unpay') { const { data } = await unionPayApi.main({ type: "getUnionPayBill", billNo: record.outTradeNo, billDate: record.createTime }) if (data && ['UNPAID', 'CLOSED'].includes(data.billStatus)) { return { success: true, status: 'unpay' } } else if (data && 'REFUND' === data.billStatus) { return { success: true, status: 'refunded' } } else if (data && data.billStatus !== 'PAID') { return { success: true, status: 'unpay' } } else if (data && data.billStatus == 'PAID') { return { success: true, status: 'paid' } } } return { success: true, status: record.tradeStatus } } catch (e) { return { success: false, message: e.message }; } } exports.refundUnionPayTrade = async function (data) { try { const db = await getDb(); const { tradeNo, businessNo, businessType, refundOrderId, refundAmount } = data; const record = await db.collection("trades").findOne({ tradeNo, businessNo, businessType }); if (!record) { return { success: false, message: '交易记录不存在' }; } const cent = new BigNumber(refundAmount).times(100).toNumber(); const res = await unionPayApi.main({ type: "refundUnionPayBill", billNo: record.outTradeNo, billDate: record.createTime, refundAmount: cent, refundOrderId }); if (res && res.success) { const refundList = record.refundList || []; const refund = { refundOrderId, refundAmount, refundTime: dayjs().valueOf(), } refundList.push(refund); const refundedAmount = refundList.reduce((acc, cur) => { return acc.plus(new BigNumber(cur.refundAmount)); }, new BigNumber(0)).toNumber(); const tradeStatus = refundedAmount === record.totalAmount ? 'refunded' : 'partialRefunded'; await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus, refundTime: dayjs().valueOf() } }); return { success: true, message: '退款成功', data: res.data } } return res; } catch (error) { return { success: false, message: error.message }; } } let scheduleTaskIsRunning = false; exports.runScheduleTask = async function () { if (scheduleTaskIsRunning) return; scheduleTaskIsRunning = true; try { const db = await getDb(); // 查询 未支付且创建时间大于5分钟且过 const records = await db.collection("trades").find({ tradeStatus: 'unpay', createTime: { $lt: dayjs().subtract(5, 'minute').valueOf() }, }).toArray(); console.log(`[交易订单状态查询定时任务]: 查询到待支付条数: ${records.length}`) for (let record of records) { if (record.platform === 'alipay') { const { data } = await alipayApi.main({ type: "queryTrade", tradeNo: record.tradeNo }); if (data && data.tradeStatus === 'TRADE_CLOSED') { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'closed', updateTime: dayjs().valueOf() } }); } else if (data && data.tradeStatus === 'TRADE_SUCCESS' || data.tradeStatus === 'TRADE_FINISHED') { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'paid', payTime: new Date(data.sendPayDate), updateTime: dayjs().valueOf() } }); await exports.invokePaySuccess({ trade: record, tradeNo: record.tradeNo, payTime: new Date(data.sendPayDate) }, db); } } else if (record.platform === 'unionPay') { const { data } = await unionPayApi.main({ type: "getUnionPayBill", billNo: record.outTradeNo, billDate: record.createTime }) if (data && data.billStatus === 'PAID') { const payTime = data && data.billPayment && data.billPayment.payTime ? new Date(data.billPayment.payTime) : new Date(); await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'paid', payTime, updateTime: dayjs().valueOf() } }); await exports.invokePaySuccess({ trade: record, tradeNo: record.tradeNo, payTime }, db); } else if (data && ['UNPAID', 'CLOSED'].includes(data.billStatus)) { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'closed', updateTime: dayjs().valueOf() } }); } else if (data && 'REFUND' === data.billStatus) { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'refunded', updateTime: dayjs().valueOf() } }); } else if (data) { await db.collection("trades").updateOne({ _id: record._id }, { $set: { tradeStatus: 'abnormal', updateTime: dayjs().valueOf() } }); } } } } catch (e) { console.log(`[交易订单状态查询定时任务]: 执行失败: ${e?.message}`) } scheduleTaskIsRunning = false; } exports.tradeTest = async function () { // return await exports.createUnionPayTrade({ // totalAmount: 0.01, // businessNo: `hxj202605301850`, // businessType: 'medicalWinePurchase', // desc: '测试银联支付', // }) }