Files
memind/server/portal-webhook-routes.mjs
T

185 lines
5.0 KiB
JavaScript

import { WECHAT_NOTIFY_SUCCESS_V2 } from '../wechat-pay.mjs';
export function attachPortalWebhookRoutes({
app,
userAuthReady = Promise.resolve(),
wechatNotifyBody,
wechatMpBody,
wechatMpConfig,
getRechargeService = () => null,
getWechatPayClient = () => null,
getWechatMpService = () => null,
logger = console,
} = {}) {
if (
!app ||
typeof wechatNotifyBody !== 'function' ||
typeof wechatMpBody !== 'function' ||
!wechatMpConfig
) {
throw new Error(
'attachPortalWebhookRoutes requires route dependencies',
);
}
app.post(
'/webhooks/wechat-pay/notify',
wechatNotifyBody,
async (req, res) => {
await userAuthReady;
const rechargeService = getRechargeService();
const wechatPayClient = getWechatPayClient();
const isV2 = wechatPayClient?.apiVersion === 'v2';
if (!rechargeService || !wechatPayClient?.enabled) {
if (isV2) {
return res
.status(503)
.type('text/xml')
.send(
'<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[支付未启用]]></return_msg></xml>',
);
}
return res
.status(503)
.json({
code: 'FAIL',
message: '支付未启用',
});
}
try {
const bodyText = Buffer.isBuffer(req.body)
? req.body.toString('utf8')
: String(req.body ?? '');
await rechargeService.handleWechatNotify({
headers: req.headers,
body: bodyText,
});
if (isV2) {
return res
.type('text/xml')
.send(WECHAT_NOTIFY_SUCCESS_V2);
}
return res.json({
code: 'SUCCESS',
message: '成功',
});
} catch (error) {
logger.error('WeChat notify failed:', error);
const message =
error instanceof Error
? error.message
: '处理失败';
if (isV2) {
return res
.status(500)
.type('text/xml')
.send(
`<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[${message}]]></return_msg></xml>`,
);
}
return res
.status(500)
.json({ code: 'FAIL', message });
}
},
);
if (!wechatMpConfig.enabled) return;
app.get(
'/webhooks/wechat-mp/messages',
async (req, res) => {
await userAuthReady;
const wechatMpService = getWechatMpService();
if (!wechatMpService?.enabled) {
return res
.status(503)
.send('wechat mp disabled');
}
const result =
wechatMpService.verifyUrlChallenge(req.query);
if (!result.ok) {
logger.warn('WeChat MP verify failed:', {
encryptType:
req.query.encrypt_type ?? null,
timestamp: req.query.timestamp ?? null,
nonce: req.query.nonce ?? null,
});
return res
.status(result.status ?? 403)
.send(
result.body ?? 'invalid signature',
);
}
logger.log('WeChat MP verify ok:', {
encryptType:
req.query.encrypt_type ?? null,
timestamp: req.query.timestamp ?? null,
nonce: req.query.nonce ?? null,
});
return res
.type('text/plain')
.send(String(result.body ?? ''));
},
);
app.post(
'/webhooks/wechat-mp/messages',
wechatMpBody,
async (req, res) => {
await userAuthReady;
const wechatMpService = getWechatMpService();
if (!wechatMpService?.enabled) {
return res
.status(503)
.send('wechat mp disabled');
}
try {
const bodyText = String(req.body ?? '');
const fromUser =
bodyText.match(
/<FromUserName><!\[CDATA\[([\s\S]*?)\]\]><\/FromUserName>/,
)?.[1] ?? null;
const msgType =
bodyText.match(
/<MsgType><!\[CDATA\[([\s\S]*?)\]\]><\/MsgType>/,
)?.[1] ?? null;
const content =
bodyText.match(
/<Content><!\[CDATA\[([\s\S]*?)\]\]><\/Content>/,
)?.[1] ?? null;
logger.log('WeChat MP message received:', {
at: new Date().toISOString(),
fromUser: fromUser
? `${fromUser.slice(0, 8)}...`
: null,
msgType,
contentPreview: content
? `${String(content).slice(0, 24)}`
: null,
});
const result =
await wechatMpService.handleInboundMessage(
req.body,
req.query,
);
if (result.task) void result.task;
if (result.contentType) {
res.type(result.contentType);
}
return res
.status(result.status ?? 200)
.send(result.body ?? 'success');
} catch (error) {
logger.error(
'WeChat MP message failed:',
error,
);
return res
.status(500)
.send('internal error');
}
},
);
}