2026-07-27 11:28:33 +08:00

413 lines
18 KiB
JavaScript

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: '测试银联支付',
// })
}