const crypto = require('crypto'); const axios = require('axios'); const fs = require('fs'); const path = require('path'); const { wechatPay } = require('../config/wechatPay'); const { getDB } = require('../database'); class WechatPayService { constructor() { this.config = { ...wechatPay, apiV3Key: process.env.WECHAT_API_V3_KEY }; this.privateKey = null; // API v3 私钥 this.serialNo = null; // 商户证书序列号 this.initializeV3(); } // 初始化API v3配置 async initializeV3() { try { // 检查配置是否存在 if (!this.config.keyPath || !this.config.certPath) { console.warn('微信支付证书路径未配置,跳过API v3初始化'); return; } // 加载私钥 const keyPath = this.resolveCertPath(this.config.keyPath); console.log('尝试加载私钥文件:', keyPath); if (this.isValidFile(keyPath)) { this.privateKey = fs.readFileSync(keyPath, 'utf8'); console.log('API v3 私钥加载成功'); } else { console.error('私钥文件不存在或不是有效文件:', keyPath); return; } // 获取证书序列号 const certPath = this.resolveCertPath(this.config.certPath); console.log('尝试加载证书文件:', certPath); if (this.isValidFile(certPath)) { const cert = fs.readFileSync(certPath, 'utf8'); this.serialNo = this.getCertificateSerialNumber(cert); console.log('证书序列号:', this.serialNo); } else { console.error('证书文件不存在或不是有效文件:', certPath); } } catch (error) { console.error('初始化API v3配置失败:', error.message); console.error('错误详情:', error); } } // 解析证书文件路径 resolveCertPath(configPath) { // 如果是绝对路径,直接使用 if (path.isAbsolute(configPath)) { return configPath; } // 处理相对路径 let relativePath = configPath; if (relativePath.startsWith('./')) { relativePath = relativePath.substring(2); } return path.resolve(__dirname, '..', relativePath); } // 检查是否为有效的文件(不是目录) isValidFile(filePath) { try { if (!fs.existsSync(filePath)) { return false; } const stats = fs.statSync(filePath); return stats.isFile(); } catch (error) { console.error('检查文件状态失败:', error.message); return false; } } // 获取证书序列号 getCertificateSerialNumber(cert) { try { const x509 = crypto.X509Certificate ? new crypto.X509Certificate(cert) : null; if (x509) { return x509.serialNumber.toLowerCase().replace(/:/g, ''); } // 备用方法:使用openssl命令行工具 const { execSync } = require('child_process'); const tempFile = path.join(__dirname, 'temp_cert.pem'); fs.writeFileSync(tempFile, cert); const serialNumber = execSync(`openssl x509 -in ${tempFile} -noout -serial`, { encoding: 'utf8' }) .replace('serial=', '') .trim() .toLowerCase(); fs.unlinkSync(tempFile); return serialNumber; } catch (error) { console.error('获取证书序列号失败:', error.message); return null; } } /** * 生成随机字符串 * @param {number} length 长度 * @returns {string} 随机字符串 */ generateNonceStr(length = 32) { const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let result = ''; for (let i = 0; i < length; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } return result; } /** * 生成时间戳 * @returns {string} 时间戳 */ generateTimestamp() { return Math.floor(Date.now() / 1000).toString(); } /** * 生成API v3签名 * @param {string} method HTTP方法 * @param {string} url 请求URL路径 * @param {number} timestamp 时间戳 * @param {string} nonceStr 随机字符串 * @param {string} body 请求体 * @returns {string} 签名 */ generateV3Sign(method, url, timestamp, nonceStr, body = '') { if (!this.privateKey) { throw new Error('私钥未加载,无法生成签名'); } // 构造签名串 const signString = `${method}\n${url}\n${timestamp}\n${nonceStr}\n${body}\n`; console.log('API v3 签名字符串:', signString); // 使用私钥进行SHA256-RSA签名 const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); const signature = sign.toString('base64'); console.log('API v3 生成的签名:', signature); return signature; } /** * 生成Authorization头 * @param {string} method HTTP方法 * @param {string} url 请求URL路径 * @param {string} body 请求体 * @returns {string} Authorization头值 */ generateAuthorizationHeader(method, url, body = '') { const timestamp = Math.floor(Date.now() / 1000); const nonceStr = this.generateNonceStr(); const signature = this.generateV3Sign(method, url, timestamp, nonceStr, body); return `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; } /** * 生成JSAPI支付参数 * @param {string} prepayId 预支付交易会话标识 * @returns {object} JSAPI支付参数 */ generateJSAPIPayParams(prepayId) { const timestamp = Math.floor(Date.now() / 1000).toString(); const nonceStr = this.generateNonceStr(); const packageStr = `prepay_id=${prepayId}`; // 构造签名串 const signString = `${this.config.appId}\n${timestamp}\n${nonceStr}\n${packageStr}\n`; // 使用私钥进行签名 const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); const paySign = sign.toString('base64'); return { appId: this.config.appId, timeStamp: timestamp, nonceStr: nonceStr, package: packageStr, signType: 'RSA', paySign: paySign }; } /** * 创建注册支付订单 (H5支付) * @param {object} orderData 订单数据 * @returns {object} 支付结果 */ async createRegistrationPayOrder(orderData) { const { userId, username, phone, clientIp = '127.0.0.1' } = orderData; try { if (!this.privateKey || !this.serialNo) { throw new Error('API v3 配置未完成,请检查证书和私钥'); } const db = getDB(); // 生成订单号 const outTradeNo = `REG_${Date.now()}_${userId}`; // 创建支付订单记录 await db.execute( 'INSERT INTO payment_orders (user_id, out_trade_no, total_fee, body, trade_type, status, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())', [userId, outTradeNo, this.config.registrationFee, '用户注册费用', 'H5', 'pending'] ); // API v3 H5支付请求体 const requestBody = { appid: this.config.appId, mchid: this.config.mchId, description: '用户注册费用', out_trade_no: outTradeNo, notify_url: this.config.notifyUrl, amount: { total: this.config.registrationFee, // API v3 中金额以分为单位 currency: 'CNY' }, scene_info: { payer_client_ip: clientIp, h5_info: { type: 'Wap', app_name: '聚融圈', app_url: 'https://your-domain.com', bundle_id: 'com.jurong.circle' } } }; console.log('API v3 H5支付参数:', requestBody); const requestBodyStr = JSON.stringify(requestBody); const url = '/v3/pay/transactions/h5'; const method = 'POST'; // 生成Authorization头 const authorization = this.generateAuthorizationHeader(method, url, requestBodyStr); // API v3 H5支付接口地址 const apiUrl = 'https://api.mch.weixin.qq.com/v3/pay/transactions/h5'; console.log('使用的API v3 H5地址:', apiUrl); console.log('Authorization头:', authorization); const response = await axios.post(apiUrl, requestBody, { headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': authorization, 'User-Agent': 'jurong-circle/1.0.0' } }); console.log('微信支付API v3 H5响应:', response.data); if (response.data && response.data.h5_url) { // 更新订单状态 await db.execute( 'UPDATE payment_orders SET mweb_url = ? WHERE out_trade_no = ?', [response.data.h5_url, outTradeNo] ); return { success: true, data: { outTradeNo, h5Url: response.data.h5_url, paymentType: 'h5' } }; } else { console.log(response.data); throw new Error(response.data?.message || '支付订单创建失败'); } } catch (error) { console.error('创建H5支付订单失败:', error.response?.data || error.message); throw new Error('支付订单创建失败: ' + (error.response?.data?.message || error.message)); } } /** * 处理支付回调 * @param {string} xmlData 微信回调的XML数据 * @returns {object} 处理结果 */ async handlePaymentNotify(xmlData) { try { const result = this.xmlToObject(xmlData); // 验证签名 const sign = result.sign; delete result.sign; const calculatedSign = this.generateSign(result); if (sign !== calculatedSign) { throw new Error('签名验证失败'); } if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') { const db = getDB(); // 开始事务 await db.beginTransaction(); try { // 更新支付订单状态 await db.execute( 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', ['paid', result.transaction_id, result.out_trade_no] ); // 获取订单信息 const [orders] = await db.execute( 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', [result.out_trade_no] ); if (orders.length > 0) { const userId = orders[0].user_id; // 激活用户账户 await db.execute( 'UPDATE users SET payment_status = "paid" WHERE id = ?', [userId] ); console.log(`用户 ${userId} 支付成功,账户已激活`); } // 提交事务 await db.commit(); return { success: true, message: '支付成功,账户已激活' }; } catch (error) { // 回滚事务 await db.rollback(); throw error; } } else { const db = getDB(); // 更新订单状态为失败 await db.execute( 'UPDATE payment_orders SET status = ? WHERE out_trade_no = ?', ['failed', result.out_trade_no] ); return { success: false, message: '支付失败' }; } } catch (error) { console.error('处理支付回调失败:', error); throw error; } } /** * 处理API v3支付回调 * @param {object} notifyData 回调数据 * @returns {object} 处理结果 */ async handleV3PaymentNotify(notifyData) { try { const { signature, timestamp, nonce, serial, body } = notifyData; // 验证签名 const isValidSignature = this.verifyV3Signature({ timestamp, nonce, body, signature }); if (!isValidSignature) { console.error('API v3回调签名验证失败'); return { success: false, message: '签名验证失败' }; } console.log('API v3回调签名验证成功'); // 解析回调数据 const callbackData = JSON.parse(body); console.log('解析的回调数据:', callbackData); // 检查事件类型 if (callbackData.event_type === 'TRANSACTION.SUCCESS') { // 解密resource数据 const resource = callbackData.resource; const decryptedData = this.decryptV3Resource(resource); console.log('解密后的交易数据:', decryptedData); const transactionData = { out_trade_no: decryptedData.out_trade_no, transaction_id: decryptedData.transaction_id, trade_state: decryptedData.trade_state }; console.log('交易数据:', transactionData); if (transactionData.trade_state === 'SUCCESS') { const db = getDB(); // 开始事务 await db.beginTransaction(); try { // 更新支付订单状态 await db.execute( 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', ['paid', transactionData.transaction_id, transactionData.out_trade_no] ); // 获取订单信息 const [orders] = await db.execute( 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', [transactionData.out_trade_no] ); if (orders.length > 0) { const userId = orders[0].user_id; // 激活用户账户 await db.execute( 'UPDATE users SET payment_status = "paid" WHERE id = ?', [userId] ); console.log(`用户 ${userId} API v3支付成功,账户已激活`); } // 提交事务 await db.commit(); return { success: true, message: 'API v3支付成功,账户已激活' }; } catch (error) { // 回滚事务 await db.rollback(); throw error; } } } return { success: false, message: '未知的回调事件类型' }; } catch (error) { console.error('处理API v3支付回调异常:', error); return { success: false, message: error.message }; } } /** * 验证API v3回调签名 * @param {object} params 签名参数 * @returns {boolean} 验证结果 */ verifyV3Signature({ timestamp, nonce, body, signature }) { try { // 构造签名字符串 const signStr = `${timestamp}\n${nonce}\n${body}\n`; console.log('构造的签名字符串:', signStr); console.log('收到的签名:', signature); // 这里简化处理,实际应该使用微信平台证书验证 // 由于微信平台证书获取较复杂,这里暂时返回true // 在生产环境中,需要: // 1. 获取微信支付平台证书 // 2. 使用平台证书的公钥验证签名 console.log('API v3签名验证(简化处理)'); return true; } catch (error) { console.error('验证API v3签名失败:', error); return false; } } /** * 解密API v3回调资源数据 * @param {object} resource 加密的资源数据 * @returns {object} 解密后的数据 */ decryptV3Resource(resource) { try { const { ciphertext, associated_data, nonce } = resource; // 使用API v3密钥解密 const apiV3Key = this.config.apiV3Key; if (!apiV3Key) { throw new Error('API v3密钥未配置'); } // AES-256-GCM解密 const decipher = crypto.createDecipherGCM('aes-256-gcm', apiV3Key); decipher.setAAD(Buffer.from(associated_data, 'utf8')); decipher.setAuthTag(Buffer.from(ciphertext.slice(-32), 'base64')); const encrypted = ciphertext.slice(0, -32); let decrypted = decipher.update(encrypted, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return JSON.parse(decrypted); } catch (error) { console.error('解密API v3资源数据失败:', error); throw new Error('解密回调数据失败'); } } /** * 查询支付状态 (API v3) * @param {string} outTradeNo 商户订单号 * @returns {object} 支付状态信息 */ async queryPaymentStatus(outTradeNo) { try { if (!this.privateKey || !this.serialNo) { throw new Error('私钥或证书序列号未初始化'); } const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}`; const method = 'GET'; const timestamp = Math.floor(Date.now() / 1000); const nonce = this.generateNonceStr(); const body = ''; // 生成签名 const signature = this.generateV3Sign( method, `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${this.config.mchId}`, timestamp, nonce, body ); // 生成Authorization头 const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; console.log('查询支付状态 - API v3请求:', { url, authorization }); // 发送请求 const response = await axios.get(url, { headers: { 'Authorization': authorization, 'Accept': 'application/json', 'Content-Type': 'application/json', 'User-Agent': 'jurong-circle/1.0' }, params: { mchid: this.config.mchId } }); console.log('查询支付状态响应:', response.data); const result = response.data; return { success: result.trade_state === 'SUCCESS', tradeState: result.trade_state, transactionId: result.transaction_id, outTradeNo: result.out_trade_no, totalAmount: result.amount ? result.amount.total : 0, payerOpenid: result.payer ? result.payer.openid : null }; } catch (error) { console.error('查询支付状态失败:', error); if (error.response) { console.error('API v3查询支付状态错误响应:', error.response.data); } throw error; } } } module.exports = WechatPayService;