refactor(merchant): 回调记录商户端字段裁剪, notifyInfo 与冗余字段不下发, 异常状态错误信息受控化

This commit is contained in:
DaxPay Dev
2026-07-25 15:34:27 +08:00
parent 0d287ccce7
commit 68bd29237f
8 changed files with 50 additions and 9 deletions

View File

@@ -10,6 +10,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/// # 通道入站回调记录管理器
///
@Repository
@@ -22,4 +24,14 @@ public class PayCallbackRecordManager extends BaseManager<PayCallbackRecordMappe
wrapper.lambda().orderByDesc(PayCallbackRecord::getId);
return this.page(mpPage, wrapper);
}
/// 按主键+商户号查询(商户端显式数据隔离校验)
///
/// 与 [#page] 的 forceMchNo 风格一致, 不依赖 TenantLine 兜底
public Optional<PayCallbackRecord> findByIdAndMchNo(Long id, String mchNo) {
return lambdaQuery()
.eq(PayCallbackRecord::getId, id)
.eq(PayCallbackRecord::getMchNo, mchNo)
.oneOpt();
}
}

View File

@@ -6,7 +6,9 @@ import cn.daxpay.open.payment.trade.record.dao.PayCallbackRecordManager;
import cn.daxpay.open.payment.trade.record.entity.PayCallbackRecord;
import cn.daxpay.open.payment.trade.record.param.PayCallbackRecordQuery;
import cn.daxpay.open.payment.trade.record.result.PayCallbackRecordResult;
import cn.daxpay.open.platform.common.i18n.util.I18nUtil;
import cn.daxpay.open.platform.core.code.CommonCode;
import cn.daxpay.open.platform.core.enums.pay.notice.CallbackStatusEnum;
import cn.daxpay.open.platform.core.exception.BizInfoException;
import cn.daxpay.open.platform.core.exception.DataNotExistException;
import cn.daxpay.open.platform.core.rest.param.PageParam;
@@ -19,6 +21,7 @@ import org.springframework.stereotype.Service;
/// # 通道入站回调记录(商户端)
///
/// 强制按 [PaymentContext#getMchNo] 过滤;行级隔离另由 TenantLine 兜底。
/// 原始回调报文(notifyInfo)/冗余字段不下发, 异常状态错误信息受控化, 详见 [#sanitizeForMerchant]。
@Slf4j
@Service
@RequiredArgsConstructor
@@ -31,7 +34,11 @@ public class MchPayCallbackRecordService {
public PageResult<PayCallbackRecordResult> page(PageParam pageParam, PayCallbackRecordQuery query) {
forceMchNo(query);
Page<PayCallbackRecord> page = callbackRecordManager.page(pageParam, query);
var records = page.getRecords().stream().map(PayCallbackRecordConvert.CONVERT::toResult).toList();
// 原始报文/冗余字段裁剪, 异常状态错误信息受控化
var records = page.getRecords().stream()
.map(PayCallbackRecordConvert.CONVERT::toResult)
.map(this::sanitizeForMerchant)
.toList();
return new PageResult<PayCallbackRecordResult>()
.setRecords(records)
.setTotal(page.getTotal())
@@ -41,9 +48,25 @@ public class MchPayCallbackRecordService {
/// 详情
public PayCallbackRecordResult findById(Long id) {
PayCallbackRecord record = callbackRecordManager.findById(id)
String mchNo = requireMchNo();
PayCallbackRecord record = callbackRecordManager.findByIdAndMchNo(id, mchNo)
.orElseThrow(() -> new DataNotExistException("pay.error.order.callbackRecordNotExist"));
return PayCallbackRecordConvert.CONVERT.toResult(record);
return sanitizeForMerchant(PayCallbackRecordConvert.CONVERT.toResult(record));
}
/// 商户端字段裁剪: 原始报文/冗余字段不下发, 异常状态错误信息受控化
private PayCallbackRecordResult sanitizeForMerchant(PayCallbackRecordResult result) {
// 原始回调报文含买家PII/通道签名/请求头等, 不下发
result.setNotifyInfo(null);
// 冗余字段不下发(最小披露原则); mchNo 在父类单独赋值, 避免链式返回类型陷阱
result.setMchName(null);
result.setAppId(null);
result.setMchNo(null);
// 异常状态错误信息可能含内部堆栈/路径, 替换为受控文案
if (CallbackStatusEnum.EXCEPTION.getCode().equals(result.getStatus())) {
result.setErrorMsg(I18nUtil.get("pay.error.callback.abnormal"));
}
return result;
}
private void forceMchNo(PayCallbackRecordQuery query) {

View File

@@ -97,7 +97,8 @@
"refundNotFound": "Refund order not found, recording callback",
"refundAlreadyProcessed": "Refund order already processed, recording callback",
"transferNotFound": "Transfer order not found, recording callback",
"transferAlreadyProcessed": "Transfer order already processed, recording callback"
"transferAlreadyProcessed": "Transfer order already processed, recording callback",
"abnormal": "Callback processing exception, please contact platform support"
},
"assist": {
"mchPublicKeyEmpty": "Merchant public key is empty",

View File

@@ -97,7 +97,8 @@
"refundNotFound": "払い戻し注文が見つかりません。コールバックを記録しています",
"refundAlreadyProcessed": "払い戻し注文はすでに処理されており、コールバックを記録しています",
"transferNotFound": "転送オーダーが見つかりません。コールバックを記録しています",
"transferAlreadyProcessed": "転送オーダーはすでに処理されており、コールバックを記録しています"
"transferAlreadyProcessed": "転送オーダーはすでに処理されており、コールバックを記録しています",
"abnormal": "コールバック処理で例外が発生しました。プラットフォームサポートにお問い合わせください"
},
"assist": {
"mchPublicKeyEmpty": "販売者の公開キーが空です",

View File

@@ -97,7 +97,8 @@
"refundNotFound": "환불 주문을 찾을 수 없습니다. 콜백을 녹음 중입니다.",
"refundAlreadyProcessed": "환불 주문이 이미 처리되었으며 콜백을 녹음 중입니다.",
"transferNotFound": "이체 주문을 찾을 수 없습니다. 콜백을 녹음 중입니다.",
"transferAlreadyProcessed": "이전 주문이 이미 처리되었으며 콜백을 녹음하고 있습니다."
"transferAlreadyProcessed": "이전 주문이 이미 처리되었으며 콜백을 녹음하고 있습니다.",
"abnormal": "콜백 처리 중 예외가 발생했습니다. 플랫폼 지원에 문의하세요"
},
"assist": {
"mchPublicKeyEmpty": "판매자 공개 키가 비어 있습니다.",

View File

@@ -97,7 +97,8 @@
"refundNotFound": "退款单不存在,记录回调记录",
"refundAlreadyProcessed": "退款单状态已处理,记录回调记录",
"transferNotFound": "转账单不存在,记录回调记录",
"transferAlreadyProcessed": "转账单状态已处理,记录回调记录"
"transferAlreadyProcessed": "转账单状态已处理,记录回调记录",
"abnormal": "回调处理异常,请联系平台客服"
},
"assist": {
"mchPublicKeyEmpty": "商户公钥为空",

View File

@@ -97,7 +97,8 @@
"refundNotFound": "退款單不存在,記錄回調記錄",
"refundAlreadyProcessed": "退款單狀態已處理,記錄回調記錄",
"transferNotFound": "轉賬單不存在,記錄回調記錄",
"transferAlreadyProcessed": "轉賬單狀態已處理,記錄回調記錄"
"transferAlreadyProcessed": "轉賬單狀態已處理,記錄回調記錄",
"abnormal": "回調處理異常,請聯絡平台客服"
},
"assist": {
"mchPublicKeyEmpty": "商户公鑰為空",

View File

@@ -97,7 +97,8 @@
"refundNotFound": "退款單不存在,記錄回撥記錄",
"refundAlreadyProcessed": "退款單狀態已處理,記錄回撥記錄",
"transferNotFound": "轉賬單不存在,記錄回撥記錄",
"transferAlreadyProcessed": "轉賬單狀態已處理,記錄回撥記錄"
"transferAlreadyProcessed": "轉賬單狀態已處理,記錄回撥記錄",
"abnormal": "回撥處理異常,請聯繫平台客服"
},
"assist": {
"mchPublicKeyEmpty": "商戶公鑰為空",