Files
jurong_circle_black/services/wechatPayService.js
2025-09-10 18:10:40 +08:00

609 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;