订单配置
This commit is contained in:
1 parent
e81481623c
commit
c7f43e30a9
11 files changed
+492
-19
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { Controller, Post, Req, Res } from '@nestjs/common';
|
||||
import { Controller, Get, Logger, Post, Query, Req, Res } from '@nestjs/common';
|
||||
import type { Request, Response } from 'express';
|
||||
import { Public } from '../../common/auth';
|
||||
import { decryptWechatMessage, sameHex, wechatSignature, xmlField } from '../../common/wechat-msg';
|
||||
import { InjectConfig, type AppConfig } from '../../config/config';
|
||||
import { PaymentsService } from './payments.service';
|
||||
|
||||
/** 取原始请求体。虚拟支付回调是 XML,必须拿到未经解析的字节。 */
|
||||
@@ -16,19 +18,74 @@ function rawBodyOf(req: Request): string {
|
||||
*/
|
||||
@Controller('v1/payments')
|
||||
export class PaymentsController {
|
||||
constructor(private readonly payments: PaymentsService) {}
|
||||
private readonly logger = new Logger(PaymentsController.name);
|
||||
|
||||
constructor(
|
||||
private readonly payments: PaymentsService,
|
||||
@InjectConfig() private readonly cfg: AppConfig,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 微信配置「消息推送 URL」时的验签请求。
|
||||
* 后台点保存会发 GET ?signature×tamp&nonce&echostr(安全模式换成 msg_signature + 加密 echostr),
|
||||
* 验签通过必须原样返回 echostr,否则一直提示「服务器地址验证失败」。
|
||||
*/
|
||||
@Public()
|
||||
@Get('notify')
|
||||
verify(@Query() query: Record<string, string>, @Res() res: Response): void {
|
||||
const token = this.cfg.WX_MSG_TOKEN;
|
||||
const aesKey = this.cfg.WX_MSG_AES_KEY;
|
||||
const timestamp = query.timestamp ?? '';
|
||||
const nonce = query.nonce ?? '';
|
||||
const echostr = query.echostr ?? '';
|
||||
|
||||
if (token && query.msg_signature && aesKey) {
|
||||
if (sameHex(wechatSignature(token, timestamp, nonce, echostr), query.msg_signature)) {
|
||||
res.type('text/plain').send(decryptWechatMessage(aesKey, echostr));
|
||||
return;
|
||||
}
|
||||
} else if (token && sameHex(wechatSignature(token, timestamp, nonce), query.signature ?? '')) {
|
||||
res.type('text/plain').send(echostr);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.warn('消息推送地址校验失败:签名不匹配,或 .env 未配置 WX_MSG_TOKEN');
|
||||
res.status(401).send('');
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('notify')
|
||||
async notify(@Req() req: Request, @Res() res: Response): Promise<void> {
|
||||
const xml = await this.payments.handleDeliverNotify(rawBodyOf(req));
|
||||
res.type('application/xml').send(xml);
|
||||
const body = this.decode(req);
|
||||
if (body.includes('xpay_goods_deliver_notify')) {
|
||||
res.type('application/xml').send(await this.payments.handleDeliverNotify(body));
|
||||
return;
|
||||
}
|
||||
// 退款推送走的是同一个消息推送地址,漏了这段退款就永远不会被记上
|
||||
if (body.includes('xpay_refund_notify')) {
|
||||
res.type('application/xml').send(await this.payments.handleRefundNotify(body));
|
||||
return;
|
||||
}
|
||||
// 地址会收到全部事件(投诉、风控、代币等),其余统一回 success 表示已收到
|
||||
res.type('text/plain').send('success');
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('refund-notify')
|
||||
async refundNotify(@Req() req: Request, @Res() res: Response): Promise<void> {
|
||||
const xml = await this.payments.handleRefundNotify(rawBodyOf(req));
|
||||
res.type('application/xml').send(xml);
|
||||
res.type('application/xml').send(await this.payments.handleRefundNotify(this.decode(req)));
|
||||
}
|
||||
|
||||
/** 取原始报文;安全模式下包体是 <Encrypt> 密文,先解出来。 */
|
||||
private decode(req: Request): string {
|
||||
const raw = rawBodyOf(req);
|
||||
const aesKey = this.cfg.WX_MSG_AES_KEY;
|
||||
if (!aesKey || !raw.includes('<Encrypt>')) return raw;
|
||||
try {
|
||||
return decryptWechatMessage(aesKey, xmlField(raw, 'Encrypt'));
|
||||
} catch (err) {
|
||||
this.logger.error(`消息推送解密失败,请核对 WX_MSG_AES_KEY:${err instanceof Error ? err.message : err}`);
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user