4-1 nestjs支付模块:创建JSAPI下单服务&通知接口
创建 Pay 模块
在 NestJS 项目中创建支付相关的模块:
# 创建模块
nest g mo common/pay
# 创建 Service
nest g s common/pay --no-spec
# 创建 Controller
nest g co common/pay --no-spec
bash
定义 TypeScript 接口
根据微信 JSAPI 下单接口文档,定义参数类型:
// pay.interface.ts
export interface WechatPayOrder {
appid: string;
mchid: string;
description: string;
out_trade_no: string;
time_expire?: string;
attach?: string;
notify_url: string;
goods_tag?: string;
support_fapiao?: boolean;
amount: {
total: number;
currency?: string;
};
payer: {
openid: string;
};
detail?: {
cost_price?: number;
invoice_id?: string;
goods_detail?: GoodsDetail[];
};
scene_info?: {
payer_client_id?: string;
device_id?: string;
};
}
export interface GoodsDetail {
merchant_goods_id: string;
wechatpay_goods_id?: string;
goods_name: string;
quantity: number;
unit_price: number;
}
// 用户传入的参数(排除服务端自动生成的字段)
export type WxPayParams = Omit<WechatPayOrder,
'appid' | 'mchid' | 'notify_url' | 'out_trade_no'
>;
typescript
.env 环境变量配置
# 微信支付配置
WECHAT_APPID=wx1234567890
WECHAT_MCHID=1234567890
bash
PayService 实现
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import dayjs from 'dayjs';
@Injectable()
export class PayService {
private readonly logger = new Logger(PayService.name);
private readonly axiosInstance;
constructor(private configService: ConfigService) {
this.axiosInstance = axios.create({
timeout: 10000,
});
}
async wechatJsPay(params: WxPayParams) {
try {
const wxParams: WechatPayOrder = {
...params,
appid: this.configService.get<string>('WECHAT_APPID'),
mchid: this.configService.get<string>('WECHAT_MCHID'),
notify_url: 'https://wechat-dev.example.com/api/v1/pay/notify',
out_trade_no: this.getTradeNumber(),
time_expire: dayjs().add(30, 'minute').format(),
};
const result = await this.axiosInstance.post(
'https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi',
wxParams,
);
return result.data;
} catch (error) {
this.logger.error('微信JSAPI下单失败', error.message);
throw error;
}
}
// 生成商户订单号:时间戳 + 分类ID + 随机数
private getTradeNumber(): string {
const timestamp = dayjs().format('YYYYMMDDHHmmssSSS');
const categoryId = '01'; // 01=小程序, 02=APP, 03=Web
const random = Math.floor(Math.random() * 1e10)
.toString()
.padStart(10, '0');
return `${timestamp}${categoryId}${random}`;
}
}
typescript
PayController 实现
import { Controller, Post, Body } from '@nestjs/common';
import { PayService } from './pay.service';
@Controller('pay')
export class PayController {
constructor(private readonly payService: PayService) {}
@Post('notify')
notify(@Body() body: any) {
// 微信支付结果通知回调
console.log('支付通知:', body);
return 'OK';
}
}
typescript
notify 路径配置
通知地址必须与 .env 中配置的域名一致:
https://wechat-dev.domain.com/api/v1/pay/notify
text
Controller 路由对应关系:@Controller('pay') + @Post('notify') = /pay/notify,加上全局前缀 /api/v1 即为 /api/v1/pay/notify。
本地测试
使用 Bruno 或 Postman 测试 notify 接口:
POST http://localhost:3000/api/v1/pay/notify
text
返回 OK 即接口可用。
参考资源
- 微信支付 JSAPI 下单 - 接口文档
- NestJS Modules - 模块系统
- axios - HTTP 客户端
↑