diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferStartService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferStartService.java index 640c0627d..71e980536 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferStartService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferStartService.java @@ -12,11 +12,13 @@ import cn.daxpay.open.payment.trade.transfer.entity.TransferTrade; import cn.daxpay.open.payment.trade.transfer.param.TransferParam; import cn.daxpay.open.payment.trade.transfer.runtime.mq.TransferSyncMessage; import cn.daxpay.open.platform.common.artemis.service.ArtemisTemplateService; +import cn.daxpay.open.platform.common.i18n.util.I18nUtil; import cn.daxpay.open.platform.common.json.util.JacksonUtil; import cn.daxpay.open.platform.common.redis.lock.LockExecutor; import cn.daxpay.open.platform.core.code.CommonCode; import cn.daxpay.open.platform.core.code.CommonErrorCode; import cn.daxpay.open.platform.core.code.DaxPayErrorCode; +import cn.daxpay.open.platform.core.exception.BizException; import cn.daxpay.open.platform.core.exception.BizInfoException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,6 +26,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -131,11 +134,35 @@ public class TransferStartService { } catch (Exception e) { // 发起异常: 置 FAIL 并记录错误, 然后向上抛出, 避免接口返回"假成功"误导调用方 log.error("转账发起失败: tradeNo={}, channel={}", trade.getTradeNo(), channel, e); - assistService.fail(channel, trade, e.getMessage()); - throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "pay.error.transfer.createFailed", e.getMessage()); + // BizException.getMessage() 返回 messageKey(未解析), 需按固定中文解析为本地化文案再落库, + // 避免订单 errorMsg 存 key 字符串 + String errorMsg = resolveErrorMsg(e); + assistService.fail(channel, trade, errorMsg); + // BizException 直接透传, 保留通道原始 messageKey(由 RestExceptionHandler 按请求 locale 解析), + // 避免外层"转账发起失败"与通道前缀叠加成双重前缀; 非 BizException(如网络异常)无 messageKey, 兜底包装 + if (e instanceof BizException biz) { + throw biz; + } + throw new BizInfoException(DaxPayErrorCode.TRADE_FAIL, "pay.error.transfer.createFailed", errorMsg); } } + /// 解析异常为本地化错误消息 + /// + /// [BizException] 的 getMessage() 返回 i18n messageKey(未经 I18nUtil 解析), 直接记录会导致订单 errorMsg + /// 存 key 字符串(如 "error.channel.alipay.transferFailed")。本方法按固定中文(Locale.CHINA)解析, + /// 与 [cn.daxpay.open.platform.system.handler.exception.RestExceptionHandler] 的日志行为一致, + /// 保证落库文案不随请求语言变化。非 BizException 的 getMessage() 已是真实文案, 直接使用。 + private String resolveErrorMsg(Throwable e) { + if (e instanceof BizException biz) { + String key = biz.resolveMessageKey(); + if (key != null) { + return I18nUtil.get(key, Locale.CHINA, biz.getArgs()); + } + } + return e.getMessage(); + } + /// 注册 2 分钟延迟同步(发送失败由定时同步任务兜底) private void registerDelaySync(String tradeNo) { try { diff --git a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferSyncService.java b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferSyncService.java index 142c0c4be..79edff203 100644 --- a/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferSyncService.java +++ b/daxpay-payment/daxpay-payment-core/src/main/java/cn/daxpay/open/payment/trade/transfer/runtime/service/TransferSyncService.java @@ -9,14 +9,17 @@ import cn.daxpay.open.payment.trade.record.entity.PaySyncRecord; import cn.daxpay.open.payment.trade.transfer.bo.TransferResultBo; import cn.daxpay.open.payment.trade.transfer.dao.TransferTradeManager; import cn.daxpay.open.payment.trade.transfer.entity.TransferTrade; +import cn.daxpay.open.platform.common.i18n.util.I18nUtil; import cn.daxpay.open.platform.common.redis.lock.LockExecutor; import cn.daxpay.open.platform.core.code.CommonCode; import cn.daxpay.open.platform.core.enums.pay.trade.TradeTypeEnum; +import cn.daxpay.open.platform.core.exception.BizException; import cn.daxpay.open.platform.core.exception.BizInfoException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Locale; import java.util.Objects; /// # 转账同步服务 @@ -76,7 +79,7 @@ public class TransferSyncService { } catch (Exception e) { // 通道查单失败: 保持处理中, 落同步记录, 由定时任务兜底重试 log.warn("转账同步查单失败: tradeNo={}, 保持处理中", latestTrade.getTradeNo(), e); - this.saveSyncRecord(channel, latestTrade, null, false, e.getMessage()); + this.saveSyncRecord(channel, latestTrade, null, false, resolveErrorMsg(e)); return; } // 按通道结果调整状态 @@ -110,4 +113,19 @@ public class TransferSyncService { record.setMchNo(trade.getMchNo()); paySyncRecordManager.save(record); } + + /// 解析异常为本地化错误消息 + /// + /// [BizException] 的 getMessage() 返回 i18n messageKey(未经 I18nUtil 解析), 直接记录会导致同步记录 + /// errorMsg 存 key 字符串。本方法按固定中文(Locale.CHINA)解析, 保证落库文案不随请求语言变化。 + /// 非 BizException 的 getMessage() 已是真实文案, 直接使用。 + private String resolveErrorMsg(Throwable e) { + if (e instanceof BizException biz) { + String key = biz.resolveMessageKey(); + if (key != null) { + return I18nUtil.get(key, Locale.CHINA, biz.getArgs()); + } + } + return e.getMessage(); + } }