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; |