| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780 |
- import { DataSource } from 'typeorm';
- import WxPay from 'wechatpay-node-v3';
- import { Buffer } from 'buffer';
- import { PaymentMtEntity } from '../entities/payment.mt.entity.js';
- import { PaymentStatus, RefundRequest, RefundResponse } from '../entities/payment.types.js';
- import { PaymentCreateResponse } from '../entities/payment.types.js';
- import { GenericCrudService } from '@d8d/shared-crud';
- import { SystemConfigServiceMt } from '@d8d/core-module-mt/system-config-module-mt';
- import { OrderMt } from '@d8d/orders-module-mt';
- import { PayStatus, PayType } from '@d8d/orders-module-mt';
- /**
- * 微信支付服务 - 多租户版本
- * 使用微信支付v3 SDK,支持小程序支付,支持租户数据隔离
- */
- export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
- private wxPay: WxPay;
- private readonly systemConfigService: SystemConfigServiceMt;
- constructor(
- dataSource: DataSource
- ) {
- super(dataSource, PaymentMtEntity, {
- tenantOptions: { enabled: true, tenantIdField: 'tenantId' }
- });
- this.systemConfigService = new SystemConfigServiceMt(dataSource);
- // 微信支付SDK将在需要时动态初始化
- this.wxPay = null as any;
- }
- /**
- * 初始化微信支付SDK实例
- */
- private async initializeWxPay(tenantId: number): Promise<void> {
- const config = await this.getPaymentConfig(tenantId);
- // 初始化微信支付SDK
- this.wxPay = new WxPay({
- appid: config.appId,
- mchid: config.merchantId,
- publicKey: Buffer.from(config.publicKey),
- privateKey: Buffer.from(config.privateKey),
- key: config.v3Key,
- serial_no: config.certSerialNo
- });
- }
- /**
- * 获取支付配置(优先从系统配置获取,回退到环境变量)
- */
- private async getPaymentConfig(tenantId: number): Promise<{
- merchantId: string;
- appId: string;
- v3Key: string;
- notifyUrl: string;
- certSerialNo: string;
- publicKey: string;
- privateKey: string;
- }> {
- // 配置键定义
- const configKeys = [
- 'wx.payment.merchant.id',
- 'wx.mini.app.id',
- 'wx.payment.v3.key',
- 'wx.payment.notify.url',
- 'wx.payment.cert.serial.no',
- 'wx.payment.public.key',
- 'wx.payment.private.key'
- ];
- // 优先从系统配置获取
- const configs = await this.systemConfigService.getConfigsByKeys(configKeys, tenantId);
- // 获取配置值,如果系统配置中没有则回退到环境变量
- const merchantId = configs['wx.payment.merchant.id'] || process.env.WECHAT_MERCHANT_ID || '';
- const appId = configs['wx.mini.app.id'] || process.env.WX_MINI_APP_ID || '';
- const v3Key = configs['wx.payment.v3.key'] || process.env.WECHAT_V3_KEY || '';
- const notifyUrl = configs['wx.payment.notify.url'] || process.env.WECHAT_PAY_NOTIFY_URL || '';
- const certSerialNo = configs['wx.payment.cert.serial.no'] || process.env.WECHAT_MERCHANT_CERT_SERIAL_NO || '';
- let publicKey = configs['wx.payment.public.key'] || process.env.WECHAT_PUBLIC_KEY || '';
- let privateKey = configs['wx.payment.private.key'] || process.env.WECHAT_PRIVATE_KEY || '';
- // 处理证书字符串,将 \n 转换为实际换行符
- publicKey = publicKey.replace(/\\n/g, '\n');
- privateKey = privateKey.replace(/\\n/g, '\n');
- // 验证配置完整性
- if (!merchantId || !appId || !v3Key || !certSerialNo || !publicKey || !privateKey) {
- throw new Error('微信支付配置不完整,请检查系统配置或环境变量');
- }
- return {
- merchantId,
- appId,
- v3Key,
- notifyUrl,
- certSerialNo,
- publicKey,
- privateKey
- };
- }
- /**
- * 创建微信支付订单
- * @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) {
- // 如果支付记录状态是PAID(已支付),不允许重新支付
- if (existingPayment.paymentStatus === PaymentStatus.PAID) {
- throw new Error('该订单已支付成功,请勿重复支付');
- }
- // 如果支付记录状态是REFUNDED(已退款),根据业务需求决定是否允许重新支付
- // 这里暂时允许重新支付,删除旧记录
- if (existingPayment.paymentStatus === PaymentStatus.REFUNDED) {
- console.log(`租户${tenantId}订单${externalOrderId}存在已退款的支付记录,允许重新支付`);
- }
- // 删除旧支付记录,创建新的支付记录
- await paymentRepository.remove(existingPayment);
- console.log(`删除租户${tenantId}订单${externalOrderId}的旧支付记录,状态: ${existingPayment.paymentStatus}`);
- }
- if (!openid) {
- throw new Error('用户OpenID不能为空');
- }
- try {
- // 重新初始化微信支付SDK
- await this.initializeWxPay(tenantId);
- // 获取支付配置
- const config = await this.getPaymentConfig(tenantId);
- // console.debug('微信支付配置:', config);
- // 创建商户订单号
- const outTradeNo = `PAYMENT_${externalOrderId}_${Date.now()}`;
- // 使用微信支付SDK创建JSAPI支付
- const result = await this.wxPay.transactions_jsapi({
- appid: config.appId,
- mchid: config.merchantId,
- description,
- out_trade_no: outTradeNo,
- notify_url: config.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}`);
- }
- }
- /**
- * 获取飞鹅API配置
- */
- private async getFeieApiConfig(tenantId: number): Promise<any> {
- try {
- console.debug(`[租户${tenantId}] 获取飞鹅API配置`);
- // 从 system_config_mt 表获取飞鹅API配置
- const configKeys = [
- 'feie.api.user', // 飞鹅API用户
- 'feie.api.ukey', // 飞鹅API密钥
- 'feie.api.base_url', // 飞鹅API基础URL
- 'feie.api.timeout', // API超时时间
- 'feie.api.max_retries' // 最大重试次数
- ];
- const configs = await this.systemConfigService.getConfigsByKeys(configKeys, tenantId);
- // 检查必要的配置项
- const user = configs['feie.api.user'];
- const ukey = configs['feie.api.ukey'];
- if (!user || !ukey) {
- console.warn(`[租户${tenantId}] 飞鹅API配置不完整,缺少user或ukey`);
- return null;
- }
- // 返回配置结构
- return {
- user,
- ukey,
- baseUrl: configs['feie.api.base_url'] || 'http://api.feieyun.cn/Api/Open/',
- timeout: parseInt(configs['feie.api.timeout'] || '10000', 10),
- maxRetries: parseInt(configs['feie.api.max_retries'] || '3', 10)
- };
- } catch (error) {
- console.warn(`[租户${tenantId}] 获取飞鹅API配置失败:`, error);
- return null;
- }
- }
- /**
- * 触发打印任务
- * 使用飞鹅打印模块创建延迟打印任务
- */
- private async triggerPrintTask(
- tenantId: number,
- orderId: number
- ): Promise<void> {
- try {
- // 动态导入飞鹅打印模块,避免循环依赖
- const { PrintTriggerService } = await import('@d8d/feie-printer-module-mt');
- // 获取飞鹅API配置
- const feieConfig = await this.getFeieApiConfig(tenantId);
- if (!feieConfig) {
- console.debug(`[租户${tenantId}] 飞鹅API配置未找到,跳过打印任务`);
- return;
- }
- // 创建打印触发服务并处理订单支付成功事件
- const printTriggerService = new PrintTriggerService(this.dataSource, feieConfig);
- await printTriggerService.handleOrderPaymentSuccess(tenantId, orderId);
- console.debug(`[租户${tenantId}] 打印任务触发成功,订单ID: ${orderId}`);
- } catch (error) {
- // 检查是否是模块未找到错误
- const errorMessage = error instanceof Error ? error.message : '未知错误';
- if (errorMessage.includes('Cannot find package') || errorMessage.includes('ERR_MODULE_NOT_FOUND')) {
- console.debug(`[租户${tenantId}] 飞鹅打印模块未安装,跳过打印任务,订单ID: ${orderId}`);
- } else {
- console.warn(`[租户${tenantId}] 触发打印任务失败,订单ID: ${orderId}:`, error);
- }
- // 不抛出错误,打印失败不影响支付流程
- }
- }
- /**
- * 取消打印任务
- * 使用飞鹅打印模块取消关联的打印任务
- */
- private async cancelPrintTasks(tenantId: number, orderId: number): Promise<void> {
- try {
- // 动态导入飞鹅打印模块,避免循环依赖
- const { PrintTriggerService } = await import('@d8d/feie-printer-module-mt');
- // 获取飞鹅API配置
- const feieConfig = await this.getFeieApiConfig(tenantId);
- if (!feieConfig) {
- console.debug(`[租户${tenantId}] 飞鹅API配置未找到,跳过取消打印任务`);
- return;
- }
- // 创建打印触发服务
- const printTriggerService = new PrintTriggerService(this.dataSource, feieConfig);
- // 处理订单退款事件
- await printTriggerService.handleOrderRefund(tenantId, orderId);
- console.debug(`[租户${tenantId}] 打印任务取消成功,订单ID: ${orderId}`);
- } catch (error) {
- // 检查是否是模块未找到错误
- const errorMessage = error instanceof Error ? error.message : '未知错误';
- if (errorMessage.includes('Cannot find package') || errorMessage.includes('ERR_MODULE_NOT_FOUND')) {
- console.debug(`[租户${tenantId}] 飞鹅打印模块未安装,跳过取消打印任务,订单ID: ${orderId}`);
- } else {
- console.warn(`[租户${tenantId}] 取消打印任务失败,订单ID: ${orderId}:`, error);
- }
- // 不抛出错误,打印取消失败不影响退款流程
- }
- }
- /**
- * 触发支付成功事件
- * 创建延迟打印任务
- * 注意:此方法主要用于测试,正常情况下支付回调会自动触发
- */
- async triggerPaymentSuccessEvent(tenantId: number, orderId: number): Promise<void> {
- try {
- console.debug(`[租户${tenantId}] 触发支付成功事件,订单ID: ${orderId}`);
- // 尝试触发打印(如果飞鹅打印模块可用)
- await this.triggerPrintTask(tenantId, orderId);
- } catch (error) {
- console.error(`[租户${tenantId}] 触发支付成功事件失败,订单ID: ${orderId}:`, error);
- // 为了调试,抛出错误以便在API响应中看到
- throw new Error(`触发支付成功事件失败: ${error instanceof Error ? error.message : '未知错误'}`);
- }
- }
- /**
- * 触发退款事件
- * 取消关联的打印任务
- */
- private async triggerRefundEvent(tenantId: number, orderId: number): Promise<void> {
- try {
- console.debug(`[租户${tenantId}] 触发退款事件,订单ID: ${orderId}`);
- // 尝试取消打印任务(如果飞鹅打印模块可用)
- await this.cancelPrintTasks(tenantId, orderId);
- } catch (error) {
- console.error(`[租户${tenantId}] 触发退款事件失败,订单ID: ${orderId}:`, error);
- // 不抛出错误,避免影响退款流程
- }
- }
- /**
- * 处理支付回调
- */
- async handlePaymentCallback(
- callbackData: any,
- headers: any,
- rawBody: string // 添加原始请求体参数
- ): Promise<void> {
- console.debug('收到支付回调请求:', {
- headers,
- callbackData,
- rawBody
- });
- // 重新初始化微信支付SDK(先初始化以进行签名验证和解密)
- console.debug(`开始初始化微信支付SDK`);
- await this.initializeWxPay(headers['X-Tenant-Id']); // 使用从域名代理中配置的header中的租户id
- console.debug(`微信支付SDK初始化完成`);
- // 验证回调签名
- console.debug(`开始验证回调签名`);
- 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) {
- console.debug(`回调签名验证失败,headers:`, headers);
- throw new Error('回调签名验证失败');
- }
- console.debug(`回调签名验证成功`);
- // 解密回调数据
- console.debug(`开始解密回调数据`);
- const decryptedData = this.wxPay.decipher_gcm(
- callbackData.resource.ciphertext,
- callbackData.resource.associated_data || '',
- callbackData.resource.nonce
- );
- console.debug(`解密回调数据:`, decryptedData);
- console.debug(`解密回调数据类型:`, typeof decryptedData);
- // 处理解密后的数据,可能是字符串或对象
- let parsedData;
- if (typeof decryptedData === 'string') {
- parsedData = JSON.parse(decryptedData);
- } else {
- parsedData = decryptedData;
- }
- console.debug(`解析后的回调数据:`, parsedData);
- // 从解密后的数据中获取商户订单号
- const outTradeNo = parsedData.out_trade_no;
- if (!outTradeNo) {
- console.debug('解密后的回调数据中缺少商户订单号,回调数据:', parsedData);
- throw new Error('回调数据中缺少商户订单号');
- }
- console.debug(`开始处理支付回调,商户订单号: ${outTradeNo}`);
- // 从数据库中查找支付记录以获取租户ID
- const paymentRepository = this.dataSource.getRepository(PaymentMtEntity);
- const payment = await paymentRepository.findOne({
- where: { outTradeNo }
- });
- if (!payment) {
- console.debug(`支付记录不存在,商户订单号: ${outTradeNo}`);
- throw new Error('支付记录不存在');
- }
- console.debug(`[租户${payment.tenantId}] 找到支付记录,外部订单ID: ${payment.externalOrderId}, 当前支付状态: ${payment.paymentStatus}`);
- // 重新使用正确的租户ID初始化微信支付SDK
- console.debug(`[租户${payment.tenantId}] 重新初始化微信支付SDK`);
- await this.initializeWxPay(payment.tenantId);
- console.debug(`[租户${payment.tenantId}] 微信支付SDK重新初始化完成`);
- // 根据回调结果更新支付状态
- const originalPaymentStatus = payment.paymentStatus;
- if (parsedData.trade_state === 'SUCCESS') {
- payment.paymentStatus = PaymentStatus.PAID;
- payment.wechatTransactionId = parsedData.transaction_id;
- console.debug(`[租户${payment.tenantId}] 支付成功,微信交易流水号: ${parsedData.transaction_id}`);
- } else if (parsedData.trade_state === 'FAIL') {
- payment.paymentStatus = PaymentStatus.FAILED;
- console.debug(`[租户${payment.tenantId}] 支付失败`);
- } else if (parsedData.trade_state === 'REFUND') {
- payment.paymentStatus = PaymentStatus.REFUNDED;
- console.debug(`[租户${payment.tenantId}] 支付退款`);
- }
- console.debug(`[租户${payment.tenantId}] 支付状态从 ${originalPaymentStatus} 更新为 ${payment.paymentStatus}`);
- console.debug(`[租户${payment.tenantId}] 开始保存支付记录`);
- await paymentRepository.save(payment);
- console.debug(`[租户${payment.tenantId}] 支付记录保存成功`);
- // 根据支付状态更新订单状态
- console.debug(`[租户${payment.tenantId}] 开始更新订单状态,外部订单ID: ${payment.externalOrderId}, 支付状态: ${payment.paymentStatus}`);
- try {
- if (payment.paymentStatus === PaymentStatus.PAID) {
- // 支付成功,更新订单状态为已支付 (2),支付类型为微信支付 (4)
- await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, PayStatus.SUCCESS, PayType.WECHAT);
- console.debug(`[租户${payment.tenantId}] 订单状态更新为已支付,订单ID: ${payment.externalOrderId}`);
-
- // 触发支付成功事件,创建延迟打印任务
- await this.triggerPaymentSuccessEvent(payment.tenantId, payment.externalOrderId);
- } else if (payment.paymentStatus === PaymentStatus.FAILED) {
- // 支付失败,更新订单状态为支付失败 (4),支付类型为微信支付 (4)
- await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, PayStatus.FAILED, PayType.WECHAT);
- console.debug(`[租户${payment.tenantId}] 订单状态更新为支付失败,订单ID: ${payment.externalOrderId}`);
- } else if (payment.paymentStatus === PaymentStatus.REFUNDED) {
- // 退款,更新订单状态为已退款 (3),支付类型为微信支付 (4)
- await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, PayStatus.REFUNDED, PayType.WECHAT);
- console.debug(`[租户${payment.tenantId}] 订单状态更新为已退款,订单ID: ${payment.externalOrderId}`);
- }
- } catch (error) {
- console.debug(`[租户${payment.tenantId}] 订单状态更新失败,订单ID: ${payment.externalOrderId}, 错误详情:`, {
- error: error instanceof Error ? error.message : '未知错误',
- stack: error instanceof Error ? error.stack : undefined
- });
- // 这里不抛出错误,因为支付记录已经保存,订单状态更新失败不影响支付回调的成功响应
- // 但记录详细的错误信息以便后续排查
- }
- }
- /**
- * 查询支付状态
- */
- 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;
- }
- /**
- * 生成回调签名(用于测试)
- */
- async generateCallbackSignature(
- timestamp: string,
- nonce: string,
- callbackData: any,
- tenantId: number
- ): Promise<string> {
- // 初始化微信支付SDK
- await this.initializeWxPay(tenantId);
- return this.wxPay.getSignature(
- 'POST',
- nonce,
- timestamp,
- '/v3/pay/transactions/jsapi',
- callbackData
- );
- }
- /**
- * 获取微信支付平台证书(用于测试)
- */
- async getPlatformCertificates(tenantId: number): Promise<any> {
- try {
- // 重新初始化微信支付SDK
- await this.initializeWxPay(tenantId);
- console.debug('开始获取微信支付平台证书...');
- const config = await this.getPaymentConfig(tenantId);
- const certificates = await this.wxPay.get_certificates(config.v3Key);
- console.debug('获取平台证书成功:', certificates);
- return certificates;
- } catch (error) {
- console.debug('获取平台证书失败:', error);
- throw error;
- }
- }
- /**
- * 更新订单支付状态
- * @param tenantId 租户ID
- * @param externalOrderId 外部订单ID
- * @param payState 支付状态 (0未支付、1支付中、2支付成功、3已退款、4支付失败、5订单关闭)
- * @param payType 支付类型 (1积分、2礼券、3额度支付、4微信支付),可选,默认为微信支付
- */
- async updateOrderPaymentStatus(tenantId: number, externalOrderId: number, payState: number, payType: number = PayType.WECHAT): Promise<void> {
- console.debug(`[租户${tenantId}] 开始更新订单支付状态,订单ID: ${externalOrderId}, 支付状态: ${payState}, 支付类型: ${payType}`);
- try {
- // 直接使用数据源更新订单支付状态
- const orderRepository = this.dataSource.getRepository(OrderMt);
- // 首先检查订单是否存在
- const order = await orderRepository.findOne({
- where: { id: externalOrderId, tenantId }
- });
- if (!order) {
- console.debug(`[租户${tenantId}] 订单不存在,订单ID: ${externalOrderId}`);
- throw new Error(`订单ID ${externalOrderId} 不存在`);
- }
- console.debug(`[租户${tenantId}] 找到订单,准备更新支付状态,当前支付状态: ${order.payState}`);
- const updateResult = await orderRepository.update(
- { id: externalOrderId, tenantId },
- {
- payState,
- payType,
- updatedAt: new Date()
- }
- );
- if (updateResult.affected === 0) {
- console.debug(`[租户${tenantId}] 订单更新失败,订单ID: ${externalOrderId}`);
- throw new Error(`订单ID ${externalOrderId} 更新失败`);
- }
- console.debug(`[租户${tenantId}] 订单支付状态更新成功,订单ID: ${externalOrderId}, 支付状态: ${payState}, 支付类型: ${payType}`);
- } catch (error) {
- console.debug(`[租户${tenantId}] 订单支付状态更新失败,订单ID: ${externalOrderId}, 错误详情:`, {
- error: error instanceof Error ? error.message : '未知错误',
- stack: error instanceof Error ? error.stack : undefined
- });
- throw error;
- }
- }
- /**
- * 执行微信支付退款
- * @param tenantId 租户ID
- * @param orderNo 订单号
- * @param refundAmount 退款金额(分)
- * @param refundReason 退款原因
- */
- async refund(
- tenantId: number,
- orderNo: string,
- refundAmount: number,
- refundReason?: string
- ): Promise<RefundResponse> {
- console.debug(`[租户${tenantId}] 开始处理退款,订单号: ${orderNo}, 退款金额: ${refundAmount}`);
- try {
- // 初始化微信支付SDK
- await this.initializeWxPay(tenantId);
- // 根据订单号查找支付记录
- const paymentRepository = this.dataSource.getRepository(PaymentMtEntity);
- // 首先通过订单号查找对应的订单ID
- const orderRepository = this.dataSource.getRepository(OrderMt);
- const order = await orderRepository.findOne({
- where: { orderNo, tenantId }
- });
- if (!order) {
- throw new Error(`订单不存在,订单号: ${orderNo}`);
- }
- // 通过订单ID查找支付记录
- const payment = await paymentRepository.findOne({
- where: { externalOrderId: order.id, tenantId }
- });
- if (!payment) {
- throw new Error(`支付记录不存在,订单号: ${orderNo}`);
- }
- // 验证支付状态
- if (payment.paymentStatus !== PaymentStatus.PAID) {
- throw new Error(`订单支付状态不正确,当前状态: ${payment.paymentStatus}`);
- }
- // 验证退款金额并转换为整数(分)
- const refundAmountInCents = Math.round(refundAmount * 100);
- if (refundAmountInCents <= 0 || refundAmountInCents > payment.totalAmount) {
- throw new Error(`退款金额无效,退款金额: ${refundAmount}, 支付金额: ${payment.totalAmount}`);
- }
- // 生成退款订单号
- const outRefundNo = `REFUND_${orderNo}_${Date.now()}`;
- // 验证outTradeNo存在
- if (!payment.outTradeNo) {
- throw new Error('支付记录缺少商户订单号');
- }
- // 调用微信支付退款API
- const result = await this.wxPay.refunds({
- out_trade_no: payment.outTradeNo,
- out_refund_no: outRefundNo,
- amount: {
- refund: refundAmountInCents,
- total: payment.totalAmount,
- currency: 'CNY'
- },
- reason: refundReason || '用户取消订单'
- });
- console.debug(`[租户${tenantId}] 微信支付退款API返回结果:`, result);
- // 更新支付记录的退款状态
- payment.refundStatus = PaymentStatus.REFUNDED;
- payment.refundTransactionId = outRefundNo; // 使用退款订单号作为临时退款流水号
- payment.refundAmount = refundAmountInCents;
- payment.refundTime = new Date();
- await paymentRepository.save(payment);
- console.debug(`[租户${tenantId}] 退款处理完成,退款订单号: ${outRefundNo}`);
- return {
- refundId: outRefundNo,
- outRefundNo: outRefundNo,
- refundStatus: 'SUCCESS',
- refundAmount: refundAmount,
- refundTime: new Date().toISOString()
- };
- } catch (error) {
- console.debug(`[租户${tenantId}] 退款处理失败,订单号: ${orderNo}, 错误:`, error);
- throw new Error(`退款处理失败: ${error instanceof Error ? error.message : '未知错误'}`);
- }
- }
- /**
- * 处理退款回调
- */
- async handleRefundCallback(
- callbackData: any,
- headers: any,
- rawBody: string
- ): Promise<void> {
- console.debug('收到退款回调请求:', {
- headers,
- callbackData,
- rawBody
- });
- // 重新初始化微信支付SDK(先初始化以进行签名验证和解密)
- await this.initializeWxPay(1); // 先使用默认租户ID进行初始化
- // 验证回调签名
- const isValid = await this.wxPay.verifySign({
- timestamp: headers['wechatpay-timestamp'],
- nonce: headers['wechatpay-nonce'],
- body: rawBody,
- serial: headers['wechatpay-serial'],
- signature: headers['wechatpay-signature']
- });
- if (!isValid) {
- console.debug('退款回调签名验证失败,headers:', headers);
- throw new Error('退款回调签名验证失败');
- }
- console.debug('退款回调签名验证成功');
- // 解密回调数据
- const decryptedData = this.wxPay.decipher_gcm(
- callbackData.resource.ciphertext,
- callbackData.resource.associated_data || '',
- callbackData.resource.nonce
- );
- console.debug('解密退款回调数据:', decryptedData);
- // 处理解密后的数据
- let parsedData;
- if (typeof decryptedData === 'string') {
- parsedData = JSON.parse(decryptedData);
- } else {
- parsedData = decryptedData;
- }
- console.debug('解析后的退款回调数据:', parsedData);
- // 从解密后的数据中获取退款订单号
- const outRefundNo = parsedData.out_refund_no;
- if (!outRefundNo) {
- console.debug('解密后的退款回调数据中缺少退款订单号,回调数据:', parsedData);
- throw new Error('退款回调数据中缺少退款订单号');
- }
- console.debug(`开始处理退款回调,退款订单号: ${outRefundNo}`);
- // 这里可以根据退款回调更新相关业务状态
- // 例如:更新退款记录状态、通知用户等
- console.debug(`退款回调处理完成,退款订单号: ${outRefundNo}, 退款状态: ${parsedData.refund_status}`);
- }
- }
|