nestJs中接入微信支付

3,042 阅读4分钟

nestJs中接入微信支付

要在 Nest 中接入微信支付,我们需要完成以下几个步骤:

  1. 申请微信支付商户号和 API 密钥
  2. 安装 xml2jsrequest 这两个依赖库
  3. 配置微信支付相关参数
  4. 实现统一下单接口和支付结果通知接口的控制器逻辑

下面我来具体介绍每一步。

1. 申请微信支付商户号和 API 密钥

在使用微信支付之前,我们需要先注册一个微信支付商户号,然后在商户平台上创建一个 API 密钥。具体步骤可以参考微信支付官方文档

2. 安装依赖库

在使用 Nest 接入微信支付时,我们需要用到两个依赖库:xml2jsrequestxml2js 是用于解析微信支付接口返回的 XML 格式数据,而 request 则是用于发送 HTTP 请求。可以使用 npm 进行安装:

npm install xml2js request --save
3. 配置微信支付相关参数

在开始使用微信支付之前,我们需要设置一些必要的支付参数,例如 appid、商户号、API 密钥等。可以将这些参数放在配置文件中,然后在代码中进行读取和使用。以下是一个示例配置文件 wechatpay.config.ts

export const wechatpayConfig = {
  appid: 'xxxxxxxx', // 公众号 AppID
  mch_id: 'xxxxxxxx', // 商户号
  notify_url: 'https://example.com/pay/notify', // 支付结果通知网址
  key: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // API 密钥
};
4. 实现统一下单接口和支付结果通知接口的控制器逻辑

在 Nest 中实现微信支付,需要编写两个控制器方法,一个用于发起统一下单请求,并返回预支付订单信息,另一个用于接收微信支付结果通知,并处理支付成功的业务逻辑。

首先是统一下单接口控制器代码:

import { Controller, Post, Body } from '@nestjs/common';
import * as request from 'request';
import { parseString } from 'xml2js';
import { wechatpayConfig } from './wechatpay.config';

@Controller('pay')
export class PayController {
  @Post('unifiedorder')
  async unifiedorder(@Body() body: any) {
    const timestamp = Math.floor(Date.now() / 1000); // 当前时间戳

    // 构造统一下单请求参数
    const params = {
      appid: wechatpayConfig.appid,
      mch_id: wechatpayConfig.mch_id,
      nonce_str: Math.random().toString(36).substr(2, 15),
      body: body.productName, // 商品描述
      out_trade_no: body.orderNo, // 商户订单号
      total_fee: body.totalFee, // 总金额,单位:分
      spbill_create_ip: body.ipAddress, // 终端 IP
      notify_url: wechatpayConfig.notify_url,
      trade_type: 'JSAPI',
      openid: body.openid, // 用户 openid
      sign_type: 'MD5',
    };
    params['sign'] = this.generateSign(params); // 生成签名

    // 发送统一下单请求
    const responseXml = await new Promise<string>((resolve, reject) => {
      request.post({
        url: 'https://api.mch.weixin.qq.com/pay/unifiedorder',
        body: this.buildXmlRequest(params),
      }, (error, response, body) => {
        if (error) {
          reject(error);
        } else {
          resolve(body);
        }
      });
    });

    // 解析响应结果
    const result = await new Promise<any>((resolve, reject) => {
      parseString(responseXml, { explicitArray: false }, (err, res) => {
        if (err) {
          reject(err);
        } else {
          const data = res.xml;
          if (data.return_code === 'SUCCESS' && data.result_code === 'SUCCESS') {
            resolve({
              appId: wechatpayConfig.appid,
              timeStamp: timestamp.toString(),
              nonceStr: Math.random().toString(36).substr(2, 15),
              package: `prepay_id=${data.prepay_id}`,
              signType: 'MD5',
              paySign: this.generateSign({
                appId: wechatpayConfig.appid,
                timeStamp: timestamp.toString(),
                nonceStr: Math.random().toString(36).substr(2, 15),
                package: `prepay_id=${data.prepay_id}`,
                signType: 'MD5',
              })
            });
          } else {
            reject(data.err_code_des || '统一下单失败');
          }
        }
      });
    });

    return result;
  }

  // 生成签名
  private generateSign(params) {
    const keys = Object.keys(params).sort();
    const string = keys.map(k => `${k}=${params[k]}`).join('&') + `&key=${wechatpayConfig.key}`;
    return require('crypto').createHash('md5')
      .update(string, 'utf8')
      .digest('hex').toUpperCase();
  }

  // 构造 XML 格式请求数据
  private buildXmlRequest(params) {
    const builder = new require('xml2js').Builder({ rootName: 'xml', headless: true });
    return builder.buildObject(params);
  }
}

在上述代码中,我们通过 @Post 装饰器定义了一个 unifiedorder() 方法,用于发起统一下单请求,并返回预支付订单信息。在该方法中,我们首先构造了统一下单请求参数,并发送 HTTP POST 请求到微信支付服务器,得到预支付订单的信息。然后,我们对响应结果进行解析,并通过加密算法生成签名串和支付签名,最后将数据返回给客户端。

接着是支付结果通知控制器代码:

import { Controller, Post, Body, Req } from '@nestjs/common';
import { parseString } from 'xml2js';

@Controller('pay')
export class PayController {
  @Post('notify')
  async notify(@Req() request, @Body() body: any) {
    const notifyXml = request.rawBody; // 获取原始的请求体内容
    const result = await new Promise<any>((resolve, reject) => {
      parseString(notifyXml, { explicitArray: false }, (err, res) => {
        if (err) {
          reject(err);
        } else {
          const data = res.xml;
          if (data.return_code === 'SUCCESS' && data.result_code === 'SUCCESS') {
            // TODO: 处理支付成功的业务逻辑,例如更新订单状态等
            resolve('<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>');
          } else {
            reject(data.err_code_des || '支付失败');
          }
        }
      });
    });

    return result;
  }
}

在上述代码中,我们通过 @Post 装饰器定义了一个 notify() 方法,用于接收微信支付结果通知,并处理支付成功的业务逻辑。在该方法中,我们首先获取原始的请求体内容,并解析成 JSON 格式数据。然后,判断支付是否成功,如果成功,则执行后续的业务逻辑,例如更新订单状态等。最后,我们需要返回一个 XML 格式的响应数据,告知微信支付服务器接收到通知。

以上是在 Nest 中接入微信支付的基本流程和关键代码实现,您可以根据自己的需求进行相应的修改和优化。