146 lines
5.2 KiB
JavaScript
146 lines
5.2 KiB
JavaScript
|
|
const { express, requireAuth, isSystemAdmin, wechatPay, alipay, pool } = require("./context");
|
||
|
|
|
||
|
|
function registerPaymentRoutes(router) {
|
||
|
|
// ── Payment ──────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
function getNotifyBaseUrl() {
|
||
|
|
return process.env.PAYMENT_NOTIFY_BASE_URL || "";
|
||
|
|
}
|
||
|
|
|
||
|
|
router.post("/payment/create", requireAuth, async (req, res) => {
|
||
|
|
const { orderNo, paymentMethod = "wechat" } = req.body;
|
||
|
|
if (!orderNo) return res.status(400).json({ error: "缺少订单号" });
|
||
|
|
|
||
|
|
const {
|
||
|
|
rows: [order],
|
||
|
|
} = await pool.query("SELECT * FROM payment_orders WHERE order_no = $1 AND status = $2", [
|
||
|
|
orderNo,
|
||
|
|
"pending",
|
||
|
|
]);
|
||
|
|
if (!order) return res.status(404).json({ error: "订单不存在或已处理" });
|
||
|
|
|
||
|
|
// Verify order belongs to user
|
||
|
|
const isOwnOrder =
|
||
|
|
(order.enterprise_id &&
|
||
|
|
req.user.enterpriseId &&
|
||
|
|
order.enterprise_id === req.user.enterpriseId) ||
|
||
|
|
(order.user_id && order.user_id === req.user.id);
|
||
|
|
if (!isOwnOrder && !isSystemAdmin(req.user)) {
|
||
|
|
return res.status(403).json({ error: "无权操作此订单" });
|
||
|
|
}
|
||
|
|
|
||
|
|
const notifyBase = getNotifyBaseUrl();
|
||
|
|
const description =
|
||
|
|
order.type === "package" ? `套餐购买 - ${order.order_no}` : `积分充值 - ${order.order_no}`;
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (paymentMethod === "wechat") {
|
||
|
|
if (!wechatPay.isWechatPayEnabled())
|
||
|
|
return res.status(503).json({ error: "微信支付未配置" });
|
||
|
|
const notifyUrl = notifyBase ? `${notifyBase}/api/payment/notify/wechat` : "";
|
||
|
|
const result = await wechatPay.createNativeOrder(
|
||
|
|
orderNo,
|
||
|
|
order.amount_cents,
|
||
|
|
description,
|
||
|
|
notifyUrl,
|
||
|
|
);
|
||
|
|
return res.json({ paymentMethod: "wechat", codeUrl: result.codeUrl, orderNo });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (paymentMethod === "alipay") {
|
||
|
|
if (!alipay.isAlipayEnabled()) return res.status(503).json({ error: "支付宝未配置" });
|
||
|
|
const notifyUrl = notifyBase ? `${notifyBase}/api/payment/notify/alipay` : "";
|
||
|
|
const result = await alipay.createPrecreateOrder(
|
||
|
|
orderNo,
|
||
|
|
order.amount_cents,
|
||
|
|
description,
|
||
|
|
notifyUrl,
|
||
|
|
);
|
||
|
|
return res.json({ paymentMethod: "alipay", codeUrl: result.qrCode, orderNo });
|
||
|
|
}
|
||
|
|
|
||
|
|
return res.status(400).json({ error: "不支持的支付方式" });
|
||
|
|
} catch (err) {
|
||
|
|
console.error("[payment/create] failed", err.message);
|
||
|
|
res.status(500).json({ error: `创建支付失败: ${err.message}` });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get("/payment/status", requireAuth, async (req, res) => {
|
||
|
|
const { orderNo } = req.query;
|
||
|
|
if (!orderNo) return res.status(400).json({ error: "缺少订单号" });
|
||
|
|
|
||
|
|
const {
|
||
|
|
rows: [order],
|
||
|
|
} = await pool.query(
|
||
|
|
"SELECT order_no, status, amount_cents, payment_method, paid_at, enterprise_id, user_id FROM payment_orders WHERE order_no = $1",
|
||
|
|
[orderNo],
|
||
|
|
);
|
||
|
|
if (!order) return res.status(404).json({ error: "订单不存在" });
|
||
|
|
|
||
|
|
const isOwn =
|
||
|
|
(order.enterprise_id &&
|
||
|
|
req.user.enterpriseId &&
|
||
|
|
order.enterprise_id === req.user.enterpriseId) ||
|
||
|
|
(order.user_id && order.user_id === req.user.id);
|
||
|
|
if (!isOwn && !isSystemAdmin(req.user)) {
|
||
|
|
return res.status(403).json({ error: "无权查看此订单" });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
orderNo: order.order_no,
|
||
|
|
status: order.status,
|
||
|
|
amountCents: order.amount_cents,
|
||
|
|
paymentMethod: order.payment_method,
|
||
|
|
paidAt: order.paid_at,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post("/payment/notify/wechat", express.raw({ type: "*/*" }), (req, res) => {
|
||
|
|
try {
|
||
|
|
let body = req.body;
|
||
|
|
if (Buffer.isBuffer(body)) body = JSON.parse(body.toString("utf-8"));
|
||
|
|
|
||
|
|
const result = wechatPay.verifyAndDecryptNotification(req.headers, body);
|
||
|
|
if (!result) return res.status(400).json({ code: "FAIL", message: "签名验证失败" });
|
||
|
|
|
||
|
|
if (result.trade_state === "SUCCESS") {
|
||
|
|
wechatPay.handlePaymentSuccess(result.out_trade_no, result.transaction_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({ code: "SUCCESS", message: "OK" });
|
||
|
|
} catch (err) {
|
||
|
|
console.error("[payment/notify/wechat] error:", err.message);
|
||
|
|
res.status(500).json({ code: "FAIL", message: "Internal error" });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.post("/payment/notify/alipay", express.urlencoded({ extended: false }), (req, res) => {
|
||
|
|
try {
|
||
|
|
const body = req.body;
|
||
|
|
const verified = alipay.verifyCallback(req.headers, body);
|
||
|
|
if (!verified) return res.send("fail");
|
||
|
|
|
||
|
|
const orderNo = body.out_trade_no || body.trade_no;
|
||
|
|
const tradeNo = body.trade_no || body.trade_id;
|
||
|
|
|
||
|
|
if (body.trade_status === "TRADE_SUCCESS" || body.trade_status === "TRADE_FINISHED") {
|
||
|
|
alipay.handlePaymentSuccess(orderNo, tradeNo);
|
||
|
|
}
|
||
|
|
|
||
|
|
res.send("success");
|
||
|
|
} catch (err) {
|
||
|
|
console.error("[payment/notify/alipay] error:", err.message);
|
||
|
|
res.send("fail");
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
router.get("/payment/methods", (_req, res) => {
|
||
|
|
res.json({ wechat: wechatPay.isWechatPayEnabled(), alipay: alipay.isAlipayEnabled() });
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
module.exports = {
|
||
|
|
registerPaymentRoutes,
|
||
|
|
};
|