185 lines
5.0 KiB
JavaScript
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');
|
|
}
|
|
},
|
|
);
|
|
}
|