import { DataSource } from 'typeorm'; import WxPay from 'wechatpay-node-v3'; import { Buffer } from 'buffer'; import { PaymentMtEntity } from '../entities/payment.mt.entity.js'; import { PaymentStatus } from '../entities/payment.types.js'; import { PaymentCreateResponse } from '../entities/payment.types.js'; import { GenericCrudService } from '@d8d/shared-crud'; /** * 微信支付服务 - 多租户版本 * 使用微信支付v3 SDK,支持小程序支付,支持租户数据隔离 */ export class PaymentMtService extends GenericCrudService { private readonly wxPay: WxPay; private readonly merchantId: string; private readonly appId: string; private readonly v3Key: string; private readonly notifyUrl: string; constructor( dataSource: DataSource ) { super(dataSource, PaymentMtEntity, { tenantOptions: { enabled: true, tenantIdField: 'tenantId' } }); // 从环境变量获取支付配置 this.merchantId = process.env.WECHAT_MERCHANT_ID || ''; this.appId = process.env.WX_MINI_APP_ID || ''; this.v3Key = process.env.WECHAT_V3_KEY || ''; this.notifyUrl = process.env.WECHAT_PAY_NOTIFY_URL || ''; const certSerialNo = process.env.WECHAT_MERCHANT_CERT_SERIAL_NO || ''; if (!this.merchantId || !this.appId || !this.v3Key || !certSerialNo) { throw new Error('微信支付配置不完整,请检查环境变量'); } // 处理证书字符串,将 \n 转换为实际换行符 const publicKey = (process.env.WECHAT_PUBLIC_KEY || '').replace(/\\n/g, '\n'); const privateKey = (process.env.WECHAT_PRIVATE_KEY || '').replace(/\\n/g, '\n'); // 初始化微信支付SDK this.wxPay = new WxPay({ appid: this.appId, mchid: this.merchantId, publicKey: Buffer.from(publicKey), privateKey: Buffer.from(privateKey), key: this.v3Key, serial_no: certSerialNo }); } /** * 创建微信支付订单 * @param externalOrderId 外部订单ID * @param userId 用户ID * @param totalAmount 支付金额(分) * @param description 支付描述 * @param openid 用户OpenID * @param tenantId 租户ID */ async createPayment( externalOrderId: number, userId: number, totalAmount: number, description: string, openid: string, tenantId: number ): Promise { // 检查是否已存在相同外部订单ID的支付记录 const paymentRepository = this.dataSource.getRepository(PaymentMtEntity); const existingPayment = await paymentRepository.findOne({ where: { externalOrderId, tenantId } }); if (existingPayment) { if (existingPayment.paymentStatus !== PaymentStatus.PENDING) { throw new Error('该订单已存在支付记录且状态不正确'); } // 如果存在待支付的记录,可以更新或重新创建,这里选择重新创建 await paymentRepository.remove(existingPayment); } if (!openid) { throw new Error('用户OpenID不能为空'); } try { // 创建商户订单号 const outTradeNo = `PAYMENT_${externalOrderId}_${Date.now()}`; // 使用微信支付SDK创建JSAPI支付 const result = await this.wxPay.transactions_jsapi({ appid: this.appId, mchid: this.merchantId, description, out_trade_no: outTradeNo, notify_url: this.notifyUrl, amount: { total: totalAmount, }, payer: { openid } }); console.debug('微信支付SDK返回结果:', result); // 从 package 字段中提取 prepay_id const prepayId = result.package ? result.package.replace('prepay_id=', '') : undefined; // 创建支付记录 const payment = new PaymentMtEntity(); payment.externalOrderId = externalOrderId; payment.userId = userId; payment.totalAmount = totalAmount; payment.description = description; payment.paymentStatus = PaymentStatus.PROCESSING; payment.outTradeNo = outTradeNo; payment.openid = openid; payment.tenantId = tenantId; await paymentRepository.save(payment); // 直接返回微信支付SDK生成的参数 return { paymentId: prepayId, timeStamp: result.timeStamp, nonceStr: result.nonceStr, package: result.package, signType: result.signType, paySign: result.paySign, totalAmount: totalAmount // 添加金额字段用于前端验证 }; } catch (error) { const errorMessage = error instanceof Error ? error.message : '未知错误'; throw new Error(`微信支付创建失败: ${errorMessage}`); } } /** * 处理支付回调 */ async handlePaymentCallback( callbackData: any, headers: any, rawBody: string // 添加原始请求体参数 ): Promise { console.debug('收到支付回调请求:', { headers, callbackData, rawBody }); // 验证回调签名 const isValid = await this.wxPay.verifySign({ timestamp: headers['wechatpay-timestamp'], nonce: headers['wechatpay-nonce'], body: rawBody, // 优先使用原始请求体 serial: headers['wechatpay-serial'], signature: headers['wechatpay-signature'] }); console.debug('回调签名验证结果:', isValid); if (!isValid) { throw new Error('回调签名验证失败'); } // 解密回调数据 const decryptedData = this.wxPay.decipher_gcm( callbackData.resource.ciphertext, callbackData.resource.associated_data || '', callbackData.resource.nonce ); console.log('解密回调数据', decryptedData) console.log('解密回调数据类型:', typeof decryptedData) // 处理解密后的数据,可能是字符串或对象 let parsedData; if (typeof decryptedData === 'string') { parsedData = JSON.parse(decryptedData); } else { parsedData = decryptedData; } const paymentRepository = this.dataSource.getRepository(PaymentMtEntity); const outTradeNo = parsedData.out_trade_no; const payment = await paymentRepository.findOne({ where: { outTradeNo } }); if (!payment) { throw new Error('支付记录不存在'); } // 根据回调结果更新支付状态 if (parsedData.trade_state === 'SUCCESS') { payment.paymentStatus = PaymentStatus.PAID; payment.wechatTransactionId = parsedData.transaction_id; } else if (parsedData.trade_state === 'FAIL') { payment.paymentStatus = PaymentStatus.FAILED; } else if (parsedData.trade_state === 'REFUND') { payment.paymentStatus = PaymentStatus.REFUNDED; } await paymentRepository.save(payment); // TODO: 更新订单 } /** * 查询支付状态 */ async getPaymentStatus(externalOrderId: number, tenantId: number): Promise { const paymentRepository = this.dataSource.getRepository(PaymentMtEntity); const payment = await paymentRepository.findOne({ where: { externalOrderId, tenantId } }); if (!payment) { throw new Error('支付记录不存在'); } return payment.paymentStatus; } /** * 生成随机字符串 */ 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; } /** * 生成回调签名(用于测试) */ generateCallbackSignature( timestamp: string, nonce: string, callbackData: any ): string { return this.wxPay.getSignature( 'POST', nonce, timestamp, '/v3/pay/transactions/jsapi', callbackData ); } /** * 获取微信支付平台证书(用于测试) */ async getPlatformCertificates(): Promise { try { console.debug('开始获取微信支付平台证书...'); const certificates = await this.wxPay.get_certificates(this.v3Key); console.debug('获取平台证书成功:', certificates); return certificates; } catch (error) { console.debug('获取平台证书失败:', error); throw error; } } }