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