219 lines
7.3 KiB
JavaScript
219 lines
7.3 KiB
JavaScript
|
|
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 || '未知错误'}` }
|
||
|
|
}
|
||
|
|
|