fix(payment): 解决微信支付异常处理和SQL Runner配置问题

- 在application.yml中启用enable-sql-runner配置以解决SqlRunner删除操作报错
- 在application-ysb.yml中补充完整的mybatis-plus配置包括SQL Runner支持
- 修改ShopDealerWithdrawController中openid获取逻辑,统一使用小程序openid避免微信400错误
- 更新ShopDealerWithdrawMapper.xml中字段别名映射确保数据正确显示
- 在WxTransferService中增强ServiceException处理,提供更友好的错误信息
- 添加详细的异常转换方法toPaymentException解析微信API错误详情
- 补充必要的Gson依赖导入处理JSON响应数据
This commit is contained in:
2026-02-24 19:30:18 +08:00
parent fe893c71f6
commit 34554cbaac
7 changed files with 330 additions and 7 deletions

View File

@@ -16,6 +16,8 @@ import com.wechat.pay.java.core.http.JsonRequestBody;
import com.wechat.pay.java.core.http.MediaType;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.util.GsonUtil;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
@@ -214,7 +216,8 @@ public class WxTransferService {
"未传入完整且对应的转账场景报备信息:请在配置中设置 wechatpay.transfer.scene-report-infos-json需与 transfer_scene_id="
+ transferSceneId + " 的报备信息一致)");
} else {
throw se;
// 透传更友好的错误信息(避免前端看到 SDK 的 Wrong HttpStatusCode…
throw toPaymentException(se);
}
}
TransferBillsResponse response = httpResponse.getServiceResponse();
@@ -226,6 +229,9 @@ public class WxTransferService {
} catch (PaymentException e) {
// 业务/参数错误保持原样抛出,避免被包装成 systemError
throw e;
} catch (ServiceException se) {
// 兜底:上面的分支未覆盖到的 ServiceException 统一转换为业务可读信息
throw toPaymentException(se);
} catch (Exception e) {
log.error("微信商家转账失败(升级版): tenantId={}, outBillNo={}, openid={}, amountFen={}, err={}",
tenantId, outBillNo, openid, amountFen, e.getMessage(), e);
@@ -233,6 +239,62 @@ public class WxTransferService {
}
}
private static PaymentException toPaymentException(ServiceException se) {
String code = se.getErrorCode();
String msg = se.getErrorMessage();
String detail = extractDetail(se.getResponseBody());
// 微信侧 400 通常是请求参数/业务规则不匹配(如 openid 与 appid 不匹配、单号重复、报备信息不一致等)
if (se.getHttpStatusCode() == 400) {
StringBuilder sb = new StringBuilder();
sb.append("微信商家转账请求无效");
if (StrUtil.isNotBlank(msg)) {
sb.append("").append(msg);
}
if (StrUtil.isNotBlank(detail)) {
sb.append("").append(detail).append("");
}
if (StrUtil.isNotBlank(code)) {
sb.append(" [").append(code).append("]");
}
return PaymentException.paramError(sb.toString());
}
// 其他状态码按系统错误处理,保留 code/msg 便于排查
StringBuilder sb = new StringBuilder("微信商家转账失败");
if (StrUtil.isNotBlank(msg)) {
sb.append("").append(msg);
}
if (StrUtil.isNotBlank(detail)) {
sb.append("").append(detail).append("");
}
if (StrUtil.isNotBlank(code)) {
sb.append(" [").append(code).append("]");
}
return PaymentException.systemError(sb.toString(), se);
}
private static String extractDetail(String responseBody) {
if (StrUtil.isBlank(responseBody)) {
return null;
}
try {
JsonObject obj = GsonUtil.getGson().fromJson(responseBody, JsonObject.class);
if (obj == null) {
return null;
}
JsonElement detailEl = obj.get("detail");
if (detailEl != null && !detailEl.isJsonNull()) {
// 常见为字符串;若为对象/数组toString() 也能提供排错信息
String detail = detailEl.isJsonPrimitive() ? detailEl.getAsString() : detailEl.toString();
return StrUtil.isBlank(detail) ? null : detail;
}
} catch (Exception ignore) {
// ignore
}
return null;
}
private static String limitLen(String s, int maxLen) {
if (s == null) {
return null;

View File

@@ -238,16 +238,17 @@ public class ShopDealerWithdrawController extends BaseController {
return fail("tenantId为空无法发起微信转账");
}
String openid = StrUtil.isNotBlank(db.getOpenId()) ? db.getOpenId() : db.getOfficeOpenid();
// 小程序“收款确认页”只能使用小程序openid公众号openid传入会导致微信侧 400(INVALID_REQUEST)。
String openid = db.getOpenId();
if (StrUtil.isBlank(openid)) {
// 兜底从分销商信息关联获取openid
// 兜底从分销商信息关联获取openid同样应为小程序openid
ShopDealerUser dealerUser = shopDealerUserService.getByUserIdRel(db.getUserId());
if (dealerUser != null && StrUtil.isNotBlank(dealerUser.getOpenid())) {
openid = dealerUser.getOpenid();
}
}
if (StrUtil.isBlank(openid)) {
return fail("用户openid为空无法拉起微信收款确认页");
return fail("用户小程序openid为空无法拉起微信收款确认页");
}
// 使用提现记录ID构造单号保持幂等微信要求 5-32 且仅字母/数字

View File

@@ -4,7 +4,13 @@
<!-- 关联查询sql -->
<sql id="selectSql">
SELECT a.*, b.nickname, b.phone AS phone, b.avatar,b.openid,b.office_openid, c.real_name as realName
SELECT a.*,
b.nickname,
b.phone AS phone,
b.avatar,
b.openid AS openId,
b.office_openid AS officeOpenid,
c.real_name AS realName
FROM shop_dealer_withdraw a
LEFT JOIN gxwebsoft_core.sys_user b ON a.user_id = b.user_id
LEFT JOIN gxwebsoft_core.sys_user_verify c ON a.user_id = c.user_id AND c.status = 1

View File

@@ -44,7 +44,21 @@ mqtt:
connection-timeout: 10
keep-alive-interval: 20
auto-reconnect: true
# Mybatis-plus配置
mybatis-plus:
mapper-locations: classpath*:com/gxwebsoft/**/*Mapper.xml
configuration:
map-underscore-to-camel-case: true
cache-enabled: true
global-config:
banner: false
# SqlRunner.db().xxx 需要开启该开关,否则会报:
# Mapped Statements collection does not contain value for com.baomidou.mybatisplus.core.mapper.SqlRunner.Delete
enable-sql-runner: true
db-config:
id-type: auto
logic-delete-value: 1
logic-not-delete-value: 0
# 框架配置
config:
# 文件服务器

View File

@@ -82,7 +82,10 @@ mybatis-plus:
map-underscore-to-camel-case: true
cache-enabled: true
global-config:
:banner: false
banner: false
# SqlRunner.db().xxx 需要开启该开关,否则会报:
# Mapped Statements collection does not contain value for com.baomidou.mybatisplus.core.mapper.SqlRunner.Delete
enable-sql-runner: true
db-config:
id-type: auto
logic-delete-value: 1