Initial commit: OmniAI backend server

This commit is contained in:
stringadmin
2026-06-02 13:14:10 +08:00
commit 56955e32f7
73 changed files with 25834 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
const _crypto = require("node:crypto");
const fs = require("node:fs");
const WxPay = require("wechatpay-node-v3");
const { pool, withTransaction } = require("./db");
const { creditBalance, creditUserBalance, activatePackage } = require("./billing");
let wxPayInstance = null;
function getWxPay() {
if (wxPayInstance) return wxPayInstance;
const mchId = process.env.WECHAT_MCH_ID;
const appId = process.env.WECHAT_APP_ID;
const apiKey = process.env.WECHAT_API_KEY_V3;
const certPath = process.env.WECHAT_CERT_PATH;
const keyPath = process.env.WECHAT_KEY_PATH;
if (!mchId || !appId || !apiKey) return null;
let publicKey = null;
let privateKey = null;
try {
if (certPath && fs.existsSync(certPath)) publicKey = fs.readFileSync(certPath);
if (keyPath && fs.existsSync(keyPath)) privateKey = fs.readFileSync(keyPath);
} catch (err) {
console.error("[wechatPay] failed to read cert/key files:", err.message);
return null;
}
if (!publicKey || !privateKey) {
console.warn("[wechatPay] cert or key file missing, WeChat Pay disabled");
return null;
}
wxPayInstance = new WxPay({ appid: appId, mchid: mchId, publicKey, privateKey });
return wxPayInstance;
}
function isWechatPayEnabled() {
return getWxPay() !== null;
}
async function createNativeOrder(orderNo, amountCents, description, notifyUrl) {
const pay = getWxPay();
if (!pay) throw new Error("微信支付未配置");
const nonceStr = Math.random().toString(36).substring(2, 17);
const timestamp = Math.floor(Date.now() / 1000).toString();
const url = "/v3/pay/transactions/native";
const params = {
appid: process.env.WECHAT_APP_ID,
mchid: process.env.WECHAT_MCH_ID,
description,
out_trade_no: orderNo,
notify_url: notifyUrl,
amount: { total: amountCents, currency: "CNY" },
};
const signature = pay.getSignature("POST", nonceStr, timestamp, url, params);
const authorization = pay.getAuthorization(nonceStr, timestamp, signature);
const superagent = require("superagent");
const result = await superagent
.post("https://api.mch.weixin.qq.com/v3/pay/transactions/native")
.send(params)
.set({
Accept: "application/json",
"Content-Type": "application/json",
Authorization: authorization,
});
if (!result.body?.code_url) {
throw new Error(result.body?.message || "微信下单失败");
}
return { codeUrl: result.body.code_url };
}
function verifyAndDecryptNotification(headers, body) {
const pay = getWxPay();
if (!pay) return null;
try {
const signature = headers["wechatpay-signature"];
const timestamp = headers["wechatpay-timestamp"];
const nonce = headers["wechatpay-nonce"];
if (!signature || !timestamp || !nonce) {
console.error("[wechatPay] callback missing required headers");
return null;
}
const message = `${timestamp}\n${nonce}\n${typeof body === "string" ? body : JSON.stringify(body)}\n`;
const verified = pay.verifySignature(signature, timestamp, nonce, message);
if (!verified) {
console.error("[wechatPay] callback signature verification failed");
return null;
}
const resource = body.resource || body;
if (resource.ciphertext) {
const apiKey = process.env.WECHAT_API_KEY_V3;
const decrypted = pay.decipher_gcm(
resource.ciphertext,
resource.associated_data || "",
resource.nonce || "",
apiKey,
);
return JSON.parse(decrypted);
}
return body;
} catch (err) {
console.error("[wechatPay] notification processing failed:", err.message);
return null;
}
}
async function handlePaymentSuccess(orderNo, transactionId) {
const { rows } = await pool.query(
"SELECT * FROM payment_orders WHERE order_no = $1 AND status = $2",
[orderNo, "pending"],
);
const order = rows[0];
if (!order) {
console.warn("[wechatPay] no pending order found for", orderNo);
return false;
}
await withTransaction(async (client) => {
await client.query(
`
UPDATE payment_orders
SET status = 'paid', payment_trade_no = $1, paid_at = NOW()
WHERE order_no = $2
`,
[transactionId, orderNo],
);
if (order.type === "personal_recharge" && order.user_id) {
await creditUserBalance(
order.user_id,
order.amount_cents,
`微信充值 ${Math.floor(order.amount_cents / 100)} 积分`,
orderNo,
);
} else if (order.type === "recharge") {
await creditBalance(
order.enterprise_id,
order.amount_cents,
`微信充值 ${Math.floor(order.amount_cents / 100)} 积分`,
orderNo,
);
} else if (order.type === "package" && order.package_id) {
await activatePackage(order.enterprise_id, order.package_id);
}
});
return true;
}
module.exports = {
isWechatPayEnabled,
createNativeOrder,
verifyAndDecryptNotification,
handlePaymentSuccess,
};