const dayjs = require("dayjs"); const request = require("../../request"); const crypto = require('crypto'); const baseUrl = process.env.CONFIG_UNION_PAY_BASE_URL; const appId = process.env.CONFIG_UNION_PAY_APPID; const appKey = process.env.CONFIG_UNION_PAY_APPKEY; const mid = process.env.CONFIG_UNION_PAY_MID; const tid = process.env.CONFIG_UNION_PAY_TID; const notifyUrl = process.env.CONFIG_UNION_PAY_NOTIFY; const BillStatus = { PAID: 'PAID', // 已支付 UNPAID: 'UNPAID', // 未支付 CLOSED: 'CLOSED', // 已关闭 REFUND: 'REFUND', // 已退款 UNKNOWN: 'UNKNOWN' // 未知状态 } const accessToken = { promise: null, token: '', expireTime: 0, } function getNonce() { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < 128; i++) { result += chars[Math.floor(Math.random() * chars.length)]; } return result; } function getSignature(timestamp, nonce) { const str = appId + timestamp + nonce + appKey; return crypto.createHash('sha256').update(str, 'utf8').digest('hex'); } exports.main = async (event) => { switch (event.type) { case 'closeUnpayTrade': return await closeUnpayTrade(event); case 'getUnionPayQrcode': return await getUnionPayQrcode(event); case 'getUnionPayBill': return await getUnionPayBill(event); case 'refundUnionPayBill': return await refundUnionPayBill(event); } } async function closePayQrcode(ctx) { const qrCodeId = typeof ctx.qrCodeId === 'string' ? ctx.qrCodeId.trim() : ''; if (!qrCodeId) { return { success: false, message: '参数错误' } } const data = { qrCodeId, mid, tid, instMid: 'QRPAYDEFAULT', requestTimestamp: dayjs().format("YYYY-MM-DD HH:mm:ss") } const res = await unionPayRequest(`${baseUrl}/netpay/bills/close-qrcode`, data); if (res.success) { return { success: true, data: res.data, message: '银联支付二维码关闭成功' } } return res } async function closeUnpayTrade(ctx) { const billNo = typeof ctx.billNo === 'string' ? ctx.billNo.trim() : ''; const billDate = ctx.billDate && dayjs(ctx.billDate).isValid() ? dayjs(ctx.billDate).format("YYYY-MM-DD") : ''; const qrCodeId = typeof ctx.qrCodeId === 'string' ? ctx.qrCodeId.trim() : ''; if (!billNo || !billDate || !qrCodeId) { return { success: false, message: '参数错误' } } const { success, data, message } = await getUnionPayBill({ billNo, billDate }); if (!success) { return { success: false, message }; } if (data.billStatus == BillStatus.UNPAID) { const res = await closePayQrcode({ qrCodeId }); if (!res.success) { return res; } return { success: true, tradeStatus: 'closed', message: '银联支付订单关闭成功' } } if (data.billStatus == BillStatus.CLOSED) { return { success: true, tradeStatus: 'closed', message: '银联支付订单已关闭' } } if (data.billStatus == BillStatus.PAID) { return { success: false, tradeStatus: 'paid', message: '银联支付订单已支付' } } if (data.billStatus == BillStatus.REFUND) { return { success: false, tradeStatus: 'refund', message: '银联支付订单已退款' } } return { success: false, message: '银联支付订单状态未知' } } async function getAccessToken() { if (accessToken.token && Date.now() < accessToken.expireTime) { return { success: true, data: accessToken.token, message: '获取access token成功' } } accessToken.promise = accessToken.promise || getUnionPayAccessToken(); const res = await accessToken.promise; if (res.success) { accessToken.token = res.data; accessToken.expireTime = res.expireTime; accessToken.promise = null; } return res; } async function getUnionPayAccessToken() { const now = dayjs(); const params = { appId, timestamp: dayjs().format("YYYYMMDDHHmmss"), nonce: getNonce(), signMethod: 'SHA256', } params.signature = getSignature(params.timestamp, params.nonce); const res = await request.main(`${baseUrl}/token/access`, params, "POST"); if (res && res.errCode === '0000') { const expireTime = now.add(res.expiresIn, 'second').valueOf(); const token = `OPEN-ACCESS-TOKEN AccessToken=${res.accessToken}`; return { success: true, data: token, message: '获取access token成功', expireTime } } return { success: false, message: `获取银联access token失败:${res?.errCode || res?.errInfo || '未知错误'}` } } async function getUnionPayBill(ctx) { const billNo = typeof ctx.billNo === 'string' ? ctx.billNo.trim() : ''; const billDate = ctx.billDate && dayjs(ctx.billDate).isValid() ? dayjs(ctx.billDate).format("YYYY-MM-DD") : ''; if (!billNo || !billDate) { return { success: false, message: '参数错误' } } const data = { billNo, billDate, mid, tid, instMid: 'QRPAYDEFAULT', requestTimestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"), } const res = await unionPayRequest(`${baseUrl}/netpay/bills/query`, data); if (res.success) { return { success: true, data: res.data, message: '查询银联支付订单成功' } } return res } async function getUnionPayQrcode(ctx) { // 生产环境 银联分配的账单号必须以3JHR开头 if (typeof ctx.billNo !== 'string' || !ctx.billNo.startsWith('3JHR')) { return { success: false, message: '账单号格式错误(以3JHR开头)' } } const data = { mid, tid, instMid: 'QRPAYDEFAULT', requestTimestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"), billNo: ctx.billNo, billDate: dayjs().format("YYYY-MM-DD"), billDesc: ctx.desc || '', totalAmount: ctx.totalAmount, notifyUrl, expireTime: ctx.expireTime } const res = await unionPayRequest(`${baseUrl}/netpay/bills/get-qrcode`, data); if (res.success) { return { success: true, notifyUrl, appId, qrCodeId: res.data.qrCodeId, qrcode: res.data.billQRCode, message: '获取银联支付二维码成功' } } return res } async function refundUnionPayBill(ctx) { const billNo = typeof ctx.billNo === 'string' ? ctx.billNo.trim() : ''; const billDate = ctx.billDate && dayjs(ctx.billDate).isValid() ? dayjs(ctx.billDate).format("YYYY-MM-DD") : ''; const refundAmount = ctx.refundAmount > 0 && Number.isInteger(Number(ctx.refundAmount)) ? Number(ctx.refundAmount) : 0; const refundOrderId = typeof ctx.refundOrderId === 'string' ? ctx.refundOrderId.trim() : ''; if (!billNo || !billDate || !refundAmount) { return { success: false, message: '参数错误' } } const data = { billDate: billDate, billNo: billNo, mid, refundAmount, tid, instMid: 'QRPAYDEFAULT', requestTimestamp: dayjs().format("YYYY-MM-DD HH:mm:ss"), } if (refundOrderId) { data.refundOrderId = refundOrderId; } const res = await unionPayRequest(`${baseUrl}/netpay/bills/refund`, data); if (res.success) { return { success: true, data: res.data, message: '银联支付订单退款成功' } } return res } async function unionPayRequest(url, data) { const res = await getAccessToken(); if (!res || !res.success) { return res } const headers = { Authorization: res.data }; const result = await request.main(url, data, "POST", { headers }); if (result && result.errCode === 'SUCCESS') { return { success: true, data: result, message: '银联业务请求成功' } } return { success: false, message: `银联业务请求失败:${result?.errMsg || result?.errCode || '未知错误'}` } }