| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- 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<PaymentMtEntity> {
- 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<PaymentCreateResponse> {
- // 检查是否已存在相同外部订单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<void> {
- 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<PaymentStatus> {
- 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<any> {
- try {
- console.debug('开始获取微信支付平台证书...');
- const certificates = await this.wxPay.get_certificates(this.v3Key);
- console.debug('获取平台证书成功:', certificates);
- return certificates;
- } catch (error) {
- console.debug('获取平台证书失败:', error);
- throw error;
- }
- }
- }
|