306 lines
8.4 KiB
JavaScript
306 lines
8.4 KiB
JavaScript
|
|
const { AlipaySdk } = require('alipay-sdk');
|
|||
|
|
const { getDB } = require('../database');
|
|||
|
|
const crypto = require('crypto');
|
|||
|
|
const path = require('path');
|
|||
|
|
const fs = require('fs');
|
|||
|
|
|
|||
|
|
class AlipayService {
|
|||
|
|
constructor() {
|
|||
|
|
this.privateKey = null;
|
|||
|
|
this.alipayPublicKey = null;
|
|||
|
|
this.alipaySdk = null;
|
|||
|
|
this.isInitialized = false;
|
|||
|
|
|
|||
|
|
this.initializeAlipay();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化支付宝服务
|
|||
|
|
*/
|
|||
|
|
initializeAlipay() {
|
|||
|
|
try {
|
|||
|
|
// 读取密钥文件
|
|||
|
|
const privateKeyPath = this.resolveCertPath('../certs/alipay-private-key.pem');
|
|||
|
|
const publicKeyPath = this.resolveCertPath('../certs/alipay-public-key.pem');
|
|||
|
|
|
|||
|
|
console.log('支付宝私钥路径:', privateKeyPath);
|
|||
|
|
console.log('支付宝公钥路径:', publicKeyPath);
|
|||
|
|
this.privateKey = fs.readFileSync(privateKeyPath, 'utf8');
|
|||
|
|
this.alipayPublicKey = fs.readFileSync(publicKeyPath, 'utf8');
|
|||
|
|
this.initializeSDK();
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('支付宝服务初始化失败:', error.message);
|
|||
|
|
console.error('支付宝功能将不可用');
|
|||
|
|
// 不抛出错误,允许服务继续运行
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 初始化支付宝SDK
|
|||
|
|
*/
|
|||
|
|
initializeSDK() {
|
|||
|
|
if (!this.privateKey || !this.alipayPublicKey) {
|
|||
|
|
console.warn('支付宝密钥未加载,跳过SDK初始化');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 支付宝配置
|
|||
|
|
this.config = {
|
|||
|
|
appId: process.env.ALIPAY_APP_ID || '2021001161683774', // 替换为实际的应用ID
|
|||
|
|
privateKey: this.privateKey, // 从文件读取的应用私钥
|
|||
|
|
alipayPublicKey: this.alipayPublicKey, // 从文件读取的支付宝公钥
|
|||
|
|
gateway: 'https://openapi.alipay.com/gateway.do', // 支付宝网关地址
|
|||
|
|
signType: 'RSA2',
|
|||
|
|
charset: 'utf-8',
|
|||
|
|
version: '1.0',
|
|||
|
|
timeout: 5000
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 初始化支付宝SDK
|
|||
|
|
this.alipaySdk = new AlipaySdk({
|
|||
|
|
appId: this.config.appId,
|
|||
|
|
privateKey: this.config.privateKey,
|
|||
|
|
alipayPublicKey: this.config.alipayPublicKey,
|
|||
|
|
gateway: this.config.gateway,
|
|||
|
|
signType: this.config.signType,
|
|||
|
|
timeout: this.config.timeout
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
this.isInitialized = true;
|
|||
|
|
console.log('支付宝SDK初始化成功');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 解析证书文件路径
|
|||
|
|
* @param {string} relativePath - 相对路径
|
|||
|
|
* @returns {string} 绝对路径
|
|||
|
|
*/
|
|||
|
|
resolveCertPath(relativePath) {
|
|||
|
|
return path.resolve(__dirname, relativePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证文件是否有效
|
|||
|
|
* @param {string} filePath - 文件路径
|
|||
|
|
* @returns {boolean} 是否为有效文件
|
|||
|
|
*/
|
|||
|
|
isValidFile(filePath) {
|
|||
|
|
try {
|
|||
|
|
const stats = fs.statSync(filePath);
|
|||
|
|
return stats.isFile();
|
|||
|
|
} catch (error) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查支付宝服务是否已初始化
|
|||
|
|
* @returns {boolean} 是否已初始化
|
|||
|
|
*/
|
|||
|
|
isServiceAvailable() {
|
|||
|
|
return this.isInitialized && this.alipaySdk !== null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建注册支付订单
|
|||
|
|
* @param {Object} params - 支付参数
|
|||
|
|
* @param {string} params.userId - 用户ID
|
|||
|
|
* @param {string} params.username - 用户名
|
|||
|
|
* @param {string} params.phone - 手机号
|
|||
|
|
* @param {string} params.clientIp - 客户端IP
|
|||
|
|
* @returns {Promise<Object>} 支付结果
|
|||
|
|
*/
|
|||
|
|
async createRegistrationPayOrder({ userId, username, phone, clientIp }) {
|
|||
|
|
// 检查服务是否可用
|
|||
|
|
if (!this.isServiceAvailable()) {
|
|||
|
|
throw new Error('支付宝服务未初始化或不可用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
|
|||
|
|
// 生成订单号
|
|||
|
|
const outTradeNo = this.generateOrderNo();
|
|||
|
|
const totalFee = 39900; // 399元,单位:分
|
|||
|
|
const subject = '用户注册激活费用';
|
|||
|
|
const body = `用户${username}(${phone})注册激活费用`;
|
|||
|
|
|
|||
|
|
// 业务参数
|
|||
|
|
const bizContent = {
|
|||
|
|
out_trade_no: outTradeNo,
|
|||
|
|
total_amount: (totalFee / 100).toFixed(2), // 转换为元
|
|||
|
|
subject: subject,
|
|||
|
|
body: body,
|
|||
|
|
product_code: 'QUICK_WAP_WAY',
|
|||
|
|
quit_url: process.env.ALIPAY_QUIT_URL
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 使用新版SDK的pageExecute方法生成支付URL
|
|||
|
|
const payUrl = this.alipaySdk.pageExecute('alipay.trade.wap.pay', 'GET', {
|
|||
|
|
bizContent: bizContent,
|
|||
|
|
notifyUrl: process.env.ALIPAY_NOTIFY_URL,
|
|||
|
|
returnUrl: process.env.ALIPAY_RETURN_URL
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 保存订单到数据库
|
|||
|
|
await db.execute(
|
|||
|
|
`INSERT INTO payment_orders
|
|||
|
|
(user_id, out_trade_no, total_fee, body, trade_type, status, created_at)
|
|||
|
|
VALUES (?, ?, ?, ?, ?, ?, NOW())`,
|
|||
|
|
[userId, outTradeNo, totalFee, body, 'ALIPAY_WAP', 'pending']
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
console.log('支付宝支付订单创建成功:', {
|
|||
|
|
userId,
|
|||
|
|
outTradeNo,
|
|||
|
|
totalFee,
|
|||
|
|
payUrl
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
outTradeNo,
|
|||
|
|
payUrl,
|
|||
|
|
paymentType: 'alipay_wap',
|
|||
|
|
totalFee
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('创建支付宝支付订单失败:', error);
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: error.message || '创建支付订单失败'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 查询支付状态
|
|||
|
|
* @param {string} outTradeNo - 商户订单号
|
|||
|
|
* @returns {Promise<Object>} 查询结果
|
|||
|
|
*/
|
|||
|
|
async queryPaymentStatus(outTradeNo) {
|
|||
|
|
// 检查服务是否可用
|
|||
|
|
if (!this.isServiceAvailable()) {
|
|||
|
|
throw new Error('支付宝服务未初始化或不可用');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const result = await this.alipaySdk.exec('alipay.trade.query', {
|
|||
|
|
bizContent: {
|
|||
|
|
out_trade_no: outTradeNo
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (result.code === '10000') {
|
|||
|
|
// 查询成功
|
|||
|
|
const tradeStatus = result.tradeStatus;
|
|||
|
|
|
|||
|
|
// 如果支付成功,更新数据库
|
|||
|
|
if (tradeStatus === 'TRADE_SUCCESS') {
|
|||
|
|
await this.updatePaymentStatus(outTradeNo, {
|
|||
|
|
status: 'paid',
|
|||
|
|
transactionId: result.tradeNo,
|
|||
|
|
paidAt: new Date()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
data: {
|
|||
|
|
trade_status: tradeStatus,
|
|||
|
|
trade_no: result.tradeNo,
|
|||
|
|
total_amount: result.totalAmount,
|
|||
|
|
buyer_pay_amount: result.buyerPayAmount,
|
|||
|
|
gmt_payment: result.gmtPayment
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
} else {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: result.msg || '查询支付状态失败'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('查询支付宝支付状态失败:', error);
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
message: error.message || '查询支付状态失败'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新支付状态
|
|||
|
|
* @param {string} outTradeNo - 商户订单号
|
|||
|
|
* @param {Object} updateData - 更新数据
|
|||
|
|
*/
|
|||
|
|
async updatePaymentStatus(outTradeNo, updateData) {
|
|||
|
|
try {
|
|||
|
|
const db = getDB();
|
|||
|
|
|
|||
|
|
// 更新订单状态
|
|||
|
|
await db.execute(
|
|||
|
|
`UPDATE payment_orders
|
|||
|
|
SET status = ?, transaction_id = ?, paid_at = ?
|
|||
|
|
WHERE out_trade_no = ?`,
|
|||
|
|
[updateData.status, updateData.transactionId, updateData.paidAt, outTradeNo]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 如果支付成功,更新用户支付状态
|
|||
|
|
if (updateData.status === 'paid') {
|
|||
|
|
const [orders] = await db.execute(
|
|||
|
|
'SELECT user_id FROM payment_orders WHERE out_trade_no = ?',
|
|||
|
|
[outTradeNo]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
if (orders.length > 0) {
|
|||
|
|
const userId = orders[0].user_id;
|
|||
|
|
await db.execute(
|
|||
|
|
'UPDATE users SET payment_status = ? WHERE id = ?',
|
|||
|
|
['paid', userId]
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
console.log('用户支付状态更新成功:', { userId, outTradeNo });
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('更新支付状态失败:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证支付宝回调签名
|
|||
|
|
* @param {Object} params - 回调参数
|
|||
|
|
* @returns {boolean} 验证结果
|
|||
|
|
*/
|
|||
|
|
verifyNotifySign(params) {
|
|||
|
|
// 检查服务是否可用
|
|||
|
|
if (!this.isServiceAvailable()) {
|
|||
|
|
console.error('支付宝服务未初始化,无法验证签名');
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
return this.alipaySdk.checkNotifySign(params);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('验证支付宝回调签名失败:', error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成订单号
|
|||
|
|
* @returns {string} 订单号
|
|||
|
|
*/
|
|||
|
|
generateOrderNo() {
|
|||
|
|
const timestamp = Date.now();
|
|||
|
|
const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0');
|
|||
|
|
return `ALI${timestamp}${random}`;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
module.exports = AlipayService;
|