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( '', ); } 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( ``, ); } 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>/, )?.[1] ?? null; const msgType = bodyText.match( /<\/MsgType>/, )?.[1] ?? null; const content = bodyText.match( /<\/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'); } }, ); }