feat(ops): WeCom webhook alerts for pay/redeem anomalies

Add outbound group-bot alerts, rate/amount rules, stuck-order cron, HQ test button, and health db/redis checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-02 16:01:30 +08:00
parent cb6ecaaba6
commit 3328c52cb4
27 changed files with 1036 additions and 103 deletions
@@ -3,6 +3,8 @@ import type { Request, Response } from 'express';
import { TradeService } from '../modules/trade/trade.service';
import { WECHAT_PROVIDER } from '../integrations/integrations.constants';
import type { IWechatProvider } from '../integrations/wechat/wechat.interface';
import { PayRedeemAnomalyService } from '../common/alert/pay-redeem-anomaly.service';
import { AlertService } from '../common/alert/alert.service';
type RawBodyRequest = Request & { body: Buffer };
@@ -11,6 +13,8 @@ export class WechatPayCallbackController {
constructor(
private readonly tradeService: TradeService,
@Inject(WECHAT_PROVIDER) private readonly wechat: IWechatProvider,
private readonly payRedeemAnomaly: PayRedeemAnomalyService,
private readonly alert: AlertService,
) {}
@Post('pay')
@@ -19,11 +23,13 @@ export class WechatPayCallbackController {
@Headers() headers: Record<string, string | string[] | undefined>,
@Res() res: Response,
) {
let orderNo: string | undefined;
try {
const rawBody =
(req as Request & { rawBody?: Buffer }).rawBody?.toString('utf8') ??
(typeof req.body === 'string' ? req.body : JSON.stringify(req.body ?? {}));
const notify = await this.wechat.parsePayNotification(headers, rawBody);
orderNo = notify.outTradeNo;
await this.tradeService.handlePaySuccess({
orderNo: notify.outTradeNo,
transactionId: notify.transactionId,
@@ -32,6 +38,15 @@ export class WechatPayCallbackController {
return res.status(200).json({ code: 'SUCCESS', message: '成功' });
} catch (err) {
const message = err instanceof Error ? err.message : '处理失败';
this.payRedeemAnomaly.onPayFail(message, { orderNo });
this.alert.notify({
level: 'P0',
category: 'pay',
title: '支付回调处理失败',
detail: `${orderNo ? `订单 ${orderNo}\n` : ''}${message}`,
dedupeKey: `pay_callback_fail|${orderNo ?? message.slice(0, 40)}`,
dedupeTtlSec: 120,
});
return res.status(500).json({ code: 'FAIL', message });
}
}
@@ -1,10 +1,14 @@
import { Controller, Headers, Post, Req, Res } from '@nestjs/common';
import type { Request, Response } from 'express';
import { PrismaService } from '../common/prisma/prisma.module';
import { AlertService } from '../common/alert/alert.service';
@Controller('callbacks/wechat')
export class WechatRefundCallbackController {
constructor(private readonly prisma: PrismaService) {}
constructor(
private readonly prisma: PrismaService,
private readonly alert: AlertService,
) {}
@Post('refund')
async refundNotify(
@@ -38,6 +42,14 @@ export class WechatRefundCallbackController {
return res.status(200).json({ code: 'SUCCESS', message: '成功' });
} catch (err) {
const message = err instanceof Error ? err.message : '处理失败';
this.alert.notify({
level: 'P0',
category: 'pay',
title: '退款回调处理失败',
detail: message,
dedupeKey: `refund_callback_fail|${message.slice(0, 40)}`,
dedupeTtlSec: 120,
});
return res.status(500).json({ code: 'FAIL', message });
}
}