Просмотр исходного кода

✨ feat(wechat-pay): 重构微信支付服务,提取客户端工具类

- 新增WechatPayClientService类,封装微信支付V3 API基础能力
- 实现签名生成、随机字符串、请求发送等核心方法
- 支持创建代金券批次、激活批次、发放代金券等操作
- 重构WechatPayService,通过客户端工具类简化业务逻辑
- 移除重复代码,优化配置管理和API调用流程
yourname 6 месяцев назад
Родитель
Сommit
b1bf5f27fe

+ 151 - 0
src/server/modules/wechat-pay/wechat-pay-client.service.ts

@@ -0,0 +1,151 @@
+import crypto from 'crypto';
+import axios from 'axios';
+
+export interface WechatPayConfigData {
+  merchantId: string;
+  appId: string;
+  privateKey: string;
+  certificateSerialNo: string;
+  apiV3Key: string;
+}
+
+interface CreateStockData {
+  stockName: string;
+  stockCreatorMchid: string;
+  couponType: string;
+  couponUseRule: any;
+  stockSendRule: any;
+  couponAmount: number;
+  couponQuantity: number;
+  startTime: Date;
+  endTime: Date;
+}
+
+interface SendCouponData {
+  openid: string;
+  stockId: string;
+  outRequestNo: string;
+  stockCreatorMchid: string;
+}
+
+export class WechatPayClientService {
+  constructor(private config: WechatPayConfigData) {}
+
+  // 生成随机字符串
+  private generateNonceStr(length: number = 32): string {
+    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+    let result = '';
+    for (let i = 0; i < length; i++) {
+      result += chars.charAt(Math.floor(Math.random() * chars.length));
+    }
+    return result;
+  }
+
+  // 生成V3签名
+  private generateV3Signature(
+    httpMethod: string,
+    url: string,
+    body: string
+  ): string {
+    const timestamp = Math.floor(Date.now() / 1000);
+    const nonceStr = this.generateNonceStr(32);
+    
+    const urlParts = new URL(url);
+    const canonicalUrl = urlParts.pathname + (urlParts.search || '');
+    
+    const message = `${httpMethod}\n${canonicalUrl}\n${timestamp}\n${nonceStr}\n${body}\n`;
+    
+    const sign = crypto.createSign('RSA-SHA256');
+    sign.write(message);
+    sign.end();
+    
+    const privateKey = `-----BEGIN PRIVATE KEY-----\n${this.config.privateKey}\n-----END PRIVATE KEY-----`;
+    const signature = sign.sign(privateKey, 'base64');
+    
+    const token = `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.merchantId}",nonce_str="${nonceStr}",timestamp="${timestamp}",serial_no="${this.config.certificateSerialNo}",signature="${signature}"`;
+    
+    return token;
+  }
+
+  // 发起微信支付API请求
+  async request(method: string, endpoint: string, data?: any): Promise<any> {
+    const url = `https://api.mch.weixin.qq.com${endpoint}`;
+    const body = data ? JSON.stringify(data) : '';
+    const signature = this.generateV3Signature(method, url, body);
+
+    const response = await axios({
+      method,
+      url,
+      data,
+      headers: {
+        'Authorization': signature,
+        'Content-Type': 'application/json',
+        'Accept': 'application/json',
+        'User-Agent': 'WeChatPay/1.0'
+      }
+    });
+
+    return response.data;
+  }
+
+  // 创建代金券批次
+  async createStock(stockData: CreateStockData): Promise<any> {
+    const data = {
+      stock_name: stockData.stockName,
+      stock_creator_mchid: stockData.stockCreatorMchid,
+      coupon_use_rule: stockData.couponUseRule,
+      stock_send_rule: stockData.stockSendRule,
+      coupon_type: stockData.couponType,
+      coupon_amount: stockData.couponAmount,
+      coupon_quantity: stockData.couponQuantity,
+      available_begin_time: stockData.startTime.toISOString(),
+      available_end_time: stockData.endTime.toISOString()
+    };
+
+    return this.request('POST', '/v3/marketing/favor/coupon-stocks', data);
+  }
+
+  // 激活代金券批次
+  async activateStock(stockId: string, stockCreatorMchid: string): Promise<any> {
+    return this.request('POST', `/v3/marketing/favor/stocks/${stockId}/start`, {
+      stock_creator_mchid: stockCreatorMchid
+    });
+  }
+
+  // 发放代金券
+  async sendCoupon(couponData: SendCouponData): Promise<any> {
+    const data = {
+      stock_id: couponData.stockId,
+      out_request_no: couponData.outRequestNo,
+      appid: this.config.appId,
+      stock_creator_mchid: couponData.stockCreatorMchid
+    };
+
+    return this.request('POST', `/v3/marketing/favor/users/${couponData.openid}/coupons`, data);
+  }
+
+  // 查询代金券详情
+  async getCouponDetail(couponId: string, openid: string): Promise<any> {
+    return this.request('GET', `/v3/marketing/favor/users/${openid}/coupons/${couponId}?appid=${this.config.appId}`);
+  }
+
+  // 查询代金券批次列表
+  async getStockList(
+    offset: number,
+    limit: number,
+    stockCreatorMchid: string,
+    status?: string
+  ): Promise<any> {
+    let url = `/v3/marketing/favor/stocks?offset=${offset}&limit=${limit}&stock_creator_mchid=${stockCreatorMchid}`;
+    if (status) {
+      url += `&status=${status}`;
+    }
+
+    return this.request('GET', url);
+  }
+
+  // 查询代金券批次详情
+  async getStockDetail(stockId: string, stockCreatorMchid: string): Promise<any> {
+    return this.request('GET', `/v3/marketing/favor/stocks/${stockId}?stock_creator_mchid=${stockCreatorMchid}`);
+  }
+}

+ 30 - 136
src/server/modules/wechat-pay/wechat-pay.service.ts

@@ -2,16 +2,7 @@ import { DataSource, Repository } from 'typeorm';
 import { WechatPayConfig } from './wechat-pay-config.entity';
 import { WechatCouponStock } from './wechat-coupon-stock.entity';
 import { WechatCoupon } from './wechat-coupon.entity';
-import crypto from 'crypto';
-import axios from 'axios';
-
-interface WechatPayConfigData {
-  merchantId: string;
-  appId: string;
-  privateKey: string;
-  certificateSerialNo: string;
-  apiV3Key: string;
-}
+import { WechatPayClientService } from './wechat-pay-client.service';
 
 interface CreateStockData {
   stockName: string;
@@ -52,99 +43,26 @@ export class WechatPayService {
     return config;
   }
 
-  // 生成V3签名
-  private generateV3Signature(
-    httpMethod: string,
-    url: string,
-    body: string,
-    config: WechatPayConfigData
-  ): string {
-    const timestamp = Math.floor(Date.now() / 1000);
-    const nonceStr = this.generateNonceStr(32);
-    
-    const urlParts = new URL(url);
-    const canonicalUrl = urlParts.pathname + (urlParts.search || '');
-    
-    const message = `${httpMethod}\n${canonicalUrl}\n${timestamp}\n${nonceStr}\n${body}\n`;
-    
-    const sign = crypto.createSign('RSA-SHA256');
-    sign.write(message);
-    sign.end();
-    
-    const privateKey = `-----BEGIN PRIVATE KEY-----\n${config.privateKey}\n-----END PRIVATE KEY-----`;
-    const signature = sign.sign(privateKey, 'base64');
-    
-    const token = `WECHATPAY2-SHA256-RSA2048 mchid="${config.merchantId}",nonce_str="${nonceStr}",timestamp="${timestamp}",serial_no="${config.certificateSerialNo}",signature="${signature}"`;
-    
-    return token;
-  }
-
-  // 生成随机字符串
-  private generateNonceStr(length: number = 32): string {
-    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-    let result = '';
-    for (let i = 0; i < length; i++) {
-      result += chars.charAt(Math.floor(Math.random() * chars.length));
+  // 获取微信支付客户端实例
+  private async getWechatPayClient(configId: number): Promise<WechatPayClientService> {
+    const config = await this.configRepository.findOne({ where: { id: configId } });
+    if (!config) {
+      throw new Error('微信支付配置不存在');
     }
-    return result;
-  }
 
-  // 发起微信支付API请求
-  private async wechatPayRequest(
-    method: string,
-    endpoint: string,
-    data?: any,
-    config?: WechatPayConfig
-  ): Promise<any> {
-    const activeConfig = config || await this.getActiveConfig();
-    
-    const configData: WechatPayConfigData = {
-      merchantId: activeConfig.merchantId,
-      appId: activeConfig.appId,
-      privateKey: activeConfig.privateKey,
-      certificateSerialNo: activeConfig.certificateSerialNo,
-      apiV3Key: activeConfig.apiV3Key
-    };
-
-    const url = `https://api.mch.weixin.qq.com${endpoint}`;
-    const body = data ? JSON.stringify(data) : '';
-    const signature = this.generateV3Signature(method, url, body, configData);
-
-    const response = await axios({
-      method,
-      url,
-      data,
-      headers: {
-        'Authorization': signature,
-        'Content-Type': 'application/json',
-        'Accept': 'application/json',
-        'User-Agent': 'WeChatPay/1.0'
-      }
+    return new WechatPayClientService({
+      merchantId: config.merchantId,
+      appId: config.appId,
+      privateKey: config.privateKey,
+      certificateSerialNo: config.certificateSerialNo,
+      apiV3Key: config.apiV3Key
     });
-
-    return response.data;
   }
 
   // 创建代金券批次
   async createStock(data: CreateStockData, configId: number): Promise<WechatCouponStock> {
-    const config = await this.configRepository.findOne({ where: { id: configId } });
-    if (!config) {
-      throw new Error('微信支付配置不存在');
-    }
-
-    const stockData = {
-      stock_name: data.stockName,
-      stock_creator_mchid: data.stockCreatorMchid,
-      coupon_use_rule: data.couponUseRule,
-      stock_send_rule: data.stockSendRule,
-      coupon_type: data.couponType,
-      coupon_amount: data.couponAmount,
-      coupon_quantity: data.couponQuantity,
-      available_begin_time: data.startTime.toISOString(),
-      available_end_time: data.endTime.toISOString()
-    };
-
-    const response = await this.wechatPayRequest('POST', '/v3/marketing/favor/coupon-stocks', stockData, config);
+    const client = await this.getWechatPayClient(configId);
+    const response = await client.createStock(data);
 
     const stock = this.stockRepository.create({
       stockId: response.stock_id,
@@ -173,14 +91,8 @@ export class WechatPayService {
       throw new Error('代金券批次不存在');
     }
 
-    const config = await this.configRepository.findOne({ where: { id: stock.configId } });
-    if (!config) {
-      throw new Error('微信支付配置不存在');
-    }
-
-    await this.wechatPayRequest('POST', `/v3/marketing/favor/stocks/${stockId}/start`, {
-      stock_creator_mchid: stock.stockCreatorMchid
-    }, config);
+    const client = await this.getWechatPayClient(stock.configId);
+    await client.activateStock(stockId, stock.stockCreatorMchid);
 
     stock.status = 'RUNNING';
     await this.stockRepository.save(stock);
@@ -199,19 +111,8 @@ export class WechatPayService {
       throw new Error('代金券库存不足');
     }
 
-    const config = await this.configRepository.findOne({ where: { id: configId } });
-    if (!config) {
-      throw new Error('微信支付配置不存在');
-    }
-
-    const couponData = {
-      stock_id: data.stockId,
-      out_request_no: data.outRequestNo,
-      appid: config.appId,
-      stock_creator_mchid: data.stockCreatorMchid
-    };
-
-    const response = await this.wechatPayRequest('POST', `/v3/marketing/favor/users/${data.openid}/coupons`, couponData, config);
+    const client = await this.getWechatPayClient(configId);
+    const response = await client.sendCoupon(data);
 
     const coupon = this.couponRepository.create({
       couponId: response.coupon_id,
@@ -245,12 +146,8 @@ export class WechatPayService {
       throw new Error('代金券不存在');
     }
 
-    const response = await this.wechatPayRequest(
-      'GET', 
-      `/v3/marketing/favor/users/${openid}/coupons/${couponId}?appid=${coupon.stock.config.appId}`
-    );
-
-    return response;
+    const client = await this.getWechatPayClient(coupon.stock.configId);
+    return client.getCouponDetail(couponId, openid);
   }
 
   // 查询代金券批次列表
@@ -260,24 +157,21 @@ export class WechatPayService {
     stockCreatorMchid: string,
     status?: string
   ): Promise<any> {
-    const config = await this.getActiveConfig();
+    const activeConfig = await this.getActiveConfig();
+    const client = await this.getWechatPayClient(activeConfig.id);
     
-    let url = `/v3/marketing/favor/stocks?offset=${offset}&limit=${limit}&stock_creator_mchid=${stockCreatorMchid}`;
-    if (status) {
-      url += `&status=${status}`;
-    }
-
-    const response = await this.wechatPayRequest('GET', url, undefined, config);
-    return response;
+    return client.getStockList(offset, limit, stockCreatorMchid, status);
   }
 
   // 查询代金券批次详情
   async getStockDetail(stockId: string, stockCreatorMchid: string): Promise<any> {
-    const response = await this.wechatPayRequest(
-      'GET', 
-      `/v3/marketing/favor/stocks/${stockId}?stock_creator_mchid=${stockCreatorMchid}`
-    );
-    return response;
+    const stock = await this.stockRepository.findOne({ where: { stockId } });
+    if (!stock) {
+      throw new Error('代金券批次不存在');
+    }
+
+    const client = await this.getWechatPayClient(stock.configId);
+    return client.getStockDetail(stockId, stockCreatorMchid);
   }
 
   // 查询本地代金券批次列表