Compare commits

..

2 Commits

Author SHA1 Message Date
6be4421ed9 feat(payment): 添加微信支付商家转账场景报备信息配置
- 在 application-cms.yml、application-dev.yml、application-prod.yml 和 application-yd.yml 中
  添加 wechatpay.transfer.scene-id 和 scene-report-infos-json 配置项
- 重构 CmsNavigation 实体类,将 modelName 字段位置调整到正确位置
- 修改 CmsNavigationMapper.xml 添加模型名称关联查询
- 更新 JSONUtil 工具类,注册 JavaTimeModule 支持 LocalDateTime 等 Java8 时间类型
- 扩展 ShopDealerUser 实体类,添加 dealerName 和 community 字段
- 在 ShopDealerUserController 中添加手机号排重逻辑
- 修改 ShopDealerUserMapper.xml 增加关键词搜索字段
- 移除 ShopDealerWithdrawController 中多余的操作日志注解
- 扩展 ShopGoods 实体类,添加 categoryName 字段并修改关联查询
- 更新 WxLoginController 构造函数注入 ObjectMapper
- 增强 WxTransferService 添加转账场景报备信息验证和日志记录
2026-01-29 20:49:18 +08:00
4c290ea4fe ```
feat(payment): 升级微信商家转账接口为新版API

- 将批量转账接口替换为商家转账(升级版)接口 /v3/fund-app/mch-transfer/transfer-bills
- 新增 transfer_scene_id 和场景报备信息配置支持
- 参数从 outBatchNo/outDetailNo 统一为 outBillNo 单号
- 添加商户单号长度限制校验(5-32字符)
- 支持接口路径fallback机制,兼容不同环境差异
- 实现转账场景报备信息的JSON配置解析功能
- 更新日志记录格式以匹配新接口响应结构
```
2026-01-29 02:39:24 +08:00
15 changed files with 265 additions and 76 deletions

View File

@@ -43,6 +43,10 @@ public class CmsNavigation implements Serializable {
@Schema(description = "模型") @Schema(description = "模型")
private String model; private String model;
@Schema(description = "模型名称")
@TableField(exist = false)
private String modelName;
@Schema(description = "标识") @Schema(description = "标识")
private String code; private String code;
@@ -114,9 +118,6 @@ public class CmsNavigation implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private Integer parentPosition; private Integer parentPosition;
@Schema(description = "模型名称")
private String modelName;
@Schema(description = "绑定的页面(已废弃)") @Schema(description = "绑定的页面(已废弃)")
private Integer pageId; private Integer pageId;

View File

@@ -4,9 +4,10 @@
<!-- 关联查询sql --> <!-- 关联查询sql -->
<sql id="selectSql"> <sql id="selectSql">
SELECT a.*, b.title as parentName, b.position as parentPosition SELECT a.*, b.title as parentName, b.position as parentPosition, c.name as modelName
FROM cms_navigation a FROM cms_navigation a
LEFT JOIN cms_navigation b ON a.parent_id = b.navigation_id LEFT JOIN cms_navigation b ON a.parent_id = b.navigation_id
LEFT JOIN cms_model c ON a.model = c.model
<where> <where>
<if test="param.navigationId != null"> <if test="param.navigationId != null">
AND a.navigation_id = #{param.navigationId} AND a.navigation_id = #{param.navigationId}

View File

@@ -1,8 +1,13 @@
package com.gxwebsoft.common.core.utils; package com.gxwebsoft.common.core.utils;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter; import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.util.TimeZone;
/** /**
* JSON解析工具类 * JSON解析工具类
@@ -11,8 +16,21 @@ import com.fasterxml.jackson.databind.ObjectWriter;
* @since 2017-06-10 10:10:39 * @since 2017-06-10 10:10:39
*/ */
public class JSONUtil { public class JSONUtil {
private static final ObjectMapper objectMapper = new ObjectMapper(); /**
private static final ObjectWriter objectWriter = objectMapper.writerWithDefaultPrettyPrinter(); * 注意:不要直接 new ObjectMapper() 否则不支持 Java8 时间类型LocalDateTime 等)。
* 这里做最小可用配置,避免在 Redis/日志/签名等场景序列化失败。
*/
private static final ObjectMapper objectMapper;
private static final ObjectWriter objectWriter;
static {
objectMapper = new ObjectMapper();
objectMapper.registerModule(new JavaTimeModule());
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
objectWriter = objectMapper.writerWithDefaultPrettyPrinter();
}
/** /**
* 对象转json字符串 * 对象转json字符串

View File

@@ -54,7 +54,7 @@ import static com.gxwebsoft.common.core.constants.RedisConstants.MP_WX_KEY;
public class WxLoginController extends BaseController { public class WxLoginController extends BaseController {
private final StringRedisTemplate redisTemplate; private final StringRedisTemplate redisTemplate;
private final OkHttpClient http = new OkHttpClient(); private final OkHttpClient http = new OkHttpClient();
private final ObjectMapper om = new ObjectMapper(); private final ObjectMapper om;
private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒 private volatile long tokenExpireEpoch = 0L; // 过期的 epoch 秒
@Resource @Resource
private SettingService settingService; private SettingService settingService;
@@ -80,8 +80,9 @@ public class WxLoginController extends BaseController {
private CmsWebsiteService cmsWebsiteService; private CmsWebsiteService cmsWebsiteService;
public WxLoginController(StringRedisTemplate redisTemplate) { public WxLoginController(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
this.redisTemplate = redisTemplate; this.redisTemplate = redisTemplate;
this.om = objectMapper;
} }
@Operation(summary = "获取微信AccessToken") @Operation(summary = "获取微信AccessToken")

View File

@@ -4,47 +4,81 @@ import cn.hutool.core.util.StrUtil;
import com.gxwebsoft.common.system.entity.Payment; import com.gxwebsoft.common.system.entity.Payment;
import com.gxwebsoft.payment.exception.PaymentException; import com.gxwebsoft.payment.exception.PaymentException;
import com.wechat.pay.java.core.Config; import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.service.transferbatch.TransferBatchService; import com.wechat.pay.java.core.cipher.PrivacyEncryptor;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferRequest; import com.wechat.pay.java.core.http.Constant;
import com.wechat.pay.java.service.transferbatch.model.InitiateBatchTransferResponse; import com.wechat.pay.java.core.http.DefaultHttpClientBuilder;
import com.wechat.pay.java.service.transferbatch.model.TransferDetailInput; import com.wechat.pay.java.core.http.HttpClient;
import com.wechat.pay.java.core.http.HttpHeaders;
import com.wechat.pay.java.core.http.HttpMethod;
import com.wechat.pay.java.core.http.HttpRequest;
import com.wechat.pay.java.core.http.HttpResponse;
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.annotations.SerializedName;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.Collections; import java.lang.reflect.Type;
import java.util.List;
/** /**
* 微信支付-商家转账到零钱(批量转账)封装 * 微信支付-商家转账到零钱封装
* 使用 wechatpay-java (APIv3) SDK 发起转账。 *
* 注意:部分商户号开通了“商家转账(升级版)”后,会被微信侧限制使用旧版“批量转账到零钱”接口(/v3/transfer/batches
* 需改用升级版接口(/v3/fund-app/mch-transfer/transfer-bills
*/ */
@Slf4j @Slf4j
@Service @Service
public class WxTransferService { public class WxTransferService {
// 商家转账(升级版)接口在 fund-app 域下
private static final String TRANSFER_BILLS_API =
"https://api.mch.weixin.qq.com/v3/fund-app/mch-transfer/transfer-bills";
// 兼容少数文档/环境差异:如 fund-app 路径不可用时,尝试该路径(仅在 404 时重试)
private static final String TRANSFER_BILLS_API_FALLBACK =
"https://api.mch.weixin.qq.com/v3/transfer/bills";
@Resource @Resource
private WxPayConfigService wxPayConfigService; private WxPayConfigService wxPayConfigService;
/** /**
* 发起单笔“商家转账到零钱”(使用批量转账接口,明细数=1 * 商家转账升级版场景ID。直连模式下在“产品中心-商家转账”配置后获得
*/
@Value("${wechatpay.transfer.scene-id:1005}")
private String transferSceneId;
/**
* 转账场景报备信息(升级版必填项之一,内容需与商户平台该 transfer_scene_id 的报备信息对应)。
*
* 配置示例YAML
* wechatpay:
* transfer:
* scene-report-infos-json: '[{"info_type":"...","info_content":"..."}]'
*/
@Value("${wechatpay.transfer.scene-report-infos-json:}")
private String transferSceneReportInfosJson;
/**
* 发起单笔“商家转账到零钱”(升级版接口 /v3/fund-app/mch-transfer/transfer-bills
* *
* @param tenantId 租户ID用于获取微信支付配置 * @param tenantId 租户ID用于获取微信支付配置
* @param openid 收款用户openid必须是该appid下的openid * @param openid 收款用户openid必须是该appid下的openid
* @param amountYuan 转账金额(单位:元) * @param amountYuan 转账金额(单位:元)
* @param outBatchNo 商家批次单号(字母/数字,商户内唯一) * @param outBillNo 商家单号(字母/数字,商户内唯一)
* @param outDetailNo 商家明细单号(字母/数字,批次内唯一 * @param remark 备注(<=32字符
* @param batchName 批次名称(<=32字符
* @param remark 备注(<=32字符既用于批次备注也用于单条明细备注
* @param userName 收款用户姓名(可选;金额>=2000元时强制要求 * @param userName 收款用户姓名(可选;金额>=2000元时强制要求
*/ */
public InitiateBatchTransferResponse initiateSingleTransfer(Integer tenantId, public TransferBillsResponse initiateSingleTransfer(Integer tenantId,
String openid, String openid,
BigDecimal amountYuan, BigDecimal amountYuan,
String outBatchNo, String outBillNo,
String outDetailNo,
String batchName,
String remark, String remark,
String userName) throws PaymentException { String userName) throws PaymentException {
@@ -57,18 +91,15 @@ public class WxTransferService {
if (amountYuan == null || amountYuan.compareTo(BigDecimal.ZERO) <= 0) { if (amountYuan == null || amountYuan.compareTo(BigDecimal.ZERO) <= 0) {
throw PaymentException.amountError("转账金额必须大于0"); throw PaymentException.amountError("转账金额必须大于0");
} }
if (StrUtil.isBlank(outBatchNo) || !outBatchNo.matches("^[0-9A-Za-z]+$")) { if (StrUtil.isBlank(transferSceneId)) {
throw PaymentException.paramError("outBatchNo不合法仅允许数字/大小写字母"); throw PaymentException.paramError("transfer_scene_id未配置无法发起商家转账升级版");
} }
// 微信接口限制:商家批次单号长度需在区间内(当前实测最小 5 if (StrUtil.isBlank(outBillNo) || !outBillNo.matches("^[0-9A-Za-z]+$")) {
if (outBatchNo.length() < 5 || outBatchNo.length() > 32) { throw PaymentException.paramError("outBillNo不合法仅允许数字/大小写字母)");
throw PaymentException.paramError("outBatchNo长度不合法要求 5-32");
} }
if (StrUtil.isBlank(outDetailNo) || !outDetailNo.matches("^[0-9A-Za-z]+$")) { // 保守校验:多数微信单号字段限制在 5-32你此前 out_batch_no 也是 5-32
throw PaymentException.paramError("outDetailNo不合法仅允许数字/大小写字母)"); if (outBillNo.length() < 5 || outBillNo.length() > 32) {
} throw PaymentException.paramError("outBillNo长度不合法要求 5-32");
if (outDetailNo.length() > 32) {
throw PaymentException.paramError("outDetailNo长度不合法最大 32");
} }
// 微信要求金额单位为“分”,必须为整数 // 微信要求金额单位为“分”,必须为整数
@@ -91,41 +122,86 @@ public class WxTransferService {
userName = null; userName = null;
} }
// 升级版接口必填transfer_scene_report_infos且必须与 transfer_scene_id 的报备信息一致)
List<TransferSceneReportInfo> sceneReportInfos = parseTransferSceneReportInfos();
if (sceneReportInfos == null
|| sceneReportInfos.isEmpty()
|| sceneReportInfos.stream().anyMatch(i -> i == null
|| StrUtil.isBlank(i.getInfoType())
|| StrUtil.isBlank(i.getInfoContent()))) {
throw PaymentException.paramError(
"未传入完整且对应的转账场景报备信息:请在配置中设置 wechatpay.transfer.scene-report-infos-json需与 transfer_scene_id="
+ transferSceneId + " 的报备信息一致)");
}
Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId); Payment paymentConfig = wxPayConfigService.getPaymentConfigForStrategy(tenantId);
Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId); Config wxPayConfig = wxPayConfigService.getWxPayConfig(tenantId);
InitiateBatchTransferRequest request = new InitiateBatchTransferRequest(); try {
request.setAppid(paymentConfig.getAppId()); HttpClient httpClient = new DefaultHttpClientBuilder().config(wxPayConfig).build();
request.setOutBatchNo(outBatchNo); PrivacyEncryptor encryptor = wxPayConfig.createEncryptor();
request.setBatchName(limitLen(batchName, 32));
request.setBatchRemark(limitLen(remark, 32));
request.setTotalAmount(amountFen);
request.setTotalNum(1);
// 可选:复用支付通知地址(如配置为 https用于接收转账结果通知 TransferBillsRequest request = new TransferBillsRequest();
request.setAppid(paymentConfig.getAppId());
request.setOutBillNo(outBillNo);
request.setTransferSceneId(transferSceneId);
request.setOpenid(openid);
request.setTransferAmount(amountFen);
request.setTransferRemark(limitLen(remark, 32));
request.setTransferSceneReportInfos(sceneReportInfos);
log.debug("微信商家转账(升级版)请求参数: tenantId={}, outBillNo={}, transferSceneId={}, sceneReportInfosCount={}",
tenantId, outBillNo, transferSceneId, sceneReportInfos.size());
// 可选:转账结果通知地址(必须 https
if (StrUtil.isNotBlank(paymentConfig.getNotifyUrl()) && paymentConfig.getNotifyUrl().startsWith("https://")) { if (StrUtil.isNotBlank(paymentConfig.getNotifyUrl()) && paymentConfig.getNotifyUrl().startsWith("https://")) {
request.setNotifyUrl(paymentConfig.getNotifyUrl()); request.setNotifyUrl(paymentConfig.getNotifyUrl());
} }
TransferDetailInput detail = new TransferDetailInput(); // 需要姓名时按平台证书加密,并带上 Wechatpay-Serial
detail.setOutDetailNo(outDetailNo);
detail.setTransferAmount(amountFen);
detail.setTransferRemark(limitLen(remark, 32));
detail.setOpenid(openid);
if (StrUtil.isNotBlank(userName)) { if (StrUtil.isNotBlank(userName)) {
detail.setUserName(userName); request.setUserName(encryptor.encrypt(userName));
} }
request.setTransferDetailList(Collections.singletonList(detail));
HttpHeaders headers = new HttpHeaders();
headers.addHeader(Constant.ACCEPT, MediaType.APPLICATION_JSON.getValue());
headers.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue());
headers.addHeader(Constant.WECHAT_PAY_SERIAL, encryptor.getWechatpaySerial());
HttpRequest httpRequest = buildTransferBillsHttpRequest(TRANSFER_BILLS_API, headers, request);
HttpResponse<TransferBillsResponse> httpResponse;
try { try {
TransferBatchService service = new TransferBatchService.Builder().config(wxPayConfig).build(); httpResponse = httpClient.execute(httpRequest, TransferBillsResponse.class);
InitiateBatchTransferResponse response = service.initiateBatchTransfer(request); } catch (ServiceException se) {
log.info("微信商家转账已受理: tenantId={}, outBatchNo={}, batchId={}, batchStatus={}", // 404 且无 body 通常意味着接口路径不正确;做一次兜底重试。
tenantId, response.getOutBatchNo(), response.getBatchId(), response.getBatchStatus()); if (se.getHttpStatusCode() == 404) {
log.warn("商家转账接口返回404尝试fallback路径: tenantId={}, outBillNo={}, url={}",
tenantId, outBillNo, TRANSFER_BILLS_API_FALLBACK);
HttpRequest fallbackReq = buildTransferBillsHttpRequest(TRANSFER_BILLS_API_FALLBACK, headers, request);
httpResponse = httpClient.execute(fallbackReq, TransferBillsResponse.class);
} else if (se.getHttpStatusCode() == 400
&& "PARAM_ERROR".equals(se.getErrorCode())
&& se.getErrorMessage() != null
&& se.getErrorMessage().contains("转账场景报备信息")) {
// 常见:升级版商家转账需带 transfer_scene_report_infos且内容必须与商户平台场景报备一致
throw PaymentException.paramError(
"未传入完整且对应的转账场景报备信息:请在配置中设置 wechatpay.transfer.scene-report-infos-json需与 transfer_scene_id="
+ transferSceneId + " 的报备信息一致)");
} else {
throw se;
}
}
TransferBillsResponse response = httpResponse.getServiceResponse();
log.info("微信商家转账已受理(升级版): tenantId={}, outBillNo={}, transferBillNo={}, state={}",
tenantId, outBillNo,
response != null ? response.getTransferBillNo() : null,
response != null ? response.getState() : null);
return response; return response;
} catch (PaymentException e) {
// 业务/参数错误保持原样抛出,避免被包装成 systemError
throw e;
} catch (Exception e) { } catch (Exception e) {
log.error("微信商家转账失败: tenantId={}, outBatchNo={}, openid={}, amountFen={}, err={}", log.error("微信商家转账失败(升级版): tenantId={}, outBillNo={}, openid={}, amountFen={}, err={}",
tenantId, outBatchNo, openid, amountFen, e.getMessage(), e); tenantId, outBillNo, openid, amountFen, e.getMessage(), e);
throw PaymentException.systemError("微信商家转账失败: " + e.getMessage(), e); throw PaymentException.systemError("微信商家转账失败: " + e.getMessage(), e);
} }
} }
@@ -139,4 +215,61 @@ public class WxTransferService {
} }
return s.substring(0, maxLen); return s.substring(0, maxLen);
} }
private static HttpRequest buildTransferBillsHttpRequest(String url, HttpHeaders headers, TransferBillsRequest request) {
return new HttpRequest.Builder()
.httpMethod(HttpMethod.POST)
.url(url)
.headers(headers)
.body(new JsonRequestBody.Builder().body(GsonUtil.toJson(request)).build())
.build();
}
private List<TransferSceneReportInfo> parseTransferSceneReportInfos() throws PaymentException {
if (StrUtil.isBlank(transferSceneReportInfosJson)) {
return null;
}
try {
Type t = new TypeToken<List<TransferSceneReportInfo>>() {}.getType();
return GsonUtil.getGson().fromJson(transferSceneReportInfosJson, t);
} catch (Exception e) {
throw PaymentException.paramError("转账场景报备信息配置解析失败wechatpay.transfer.scene-report-infos-json");
}
}
/**
* 商家转账升级版请求体POST /v3/fund-app/mch-transfer/transfer-bills
* 字段命名使用 GsonUtil 的 lower_case_with_underscores 策略自动转换。
*/
@lombok.Data
private static class TransferBillsRequest {
private String appid;
private String outBillNo;
private String transferSceneId;
private String openid;
private Long transferAmount;
private String transferRemark;
private String userName;
private String notifyUrl;
private List<TransferSceneReportInfo> transferSceneReportInfos;
}
@lombok.Data
private static class TransferSceneReportInfo {
@SerializedName(value = "info_type", alternate = {"infoType"})
private String infoType;
@SerializedName(value = "info_content", alternate = {"infoContent"})
private String infoContent;
}
/**
* 商家转账(升级版)响应体(按需字段)
*/
@lombok.Data
public static class TransferBillsResponse {
private String outBillNo;
private String transferBillNo;
private String createTime;
private String state;
}
} }

View File

@@ -3,6 +3,7 @@ package com.gxwebsoft.shop.controller;
import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.common.core.utils.JSONUtil; import com.gxwebsoft.common.core.utils.JSONUtil;
import com.gxwebsoft.common.core.web.BaseController; import com.gxwebsoft.common.core.web.BaseController;
import com.gxwebsoft.shop.service.ShopDealerUserService; import com.gxwebsoft.shop.service.ShopDealerUserService;
@@ -67,6 +68,10 @@ public class ShopDealerUserController extends BaseController {
if (loginUser != null) { if (loginUser != null) {
shopDealerUser.setUserId(loginUser.getUserId()); shopDealerUser.setUserId(loginUser.getUserId());
} }
// 排重
if (shopDealerUserService.count(new LambdaQueryWrapper<ShopDealerUser>().eq(ShopDealerUser::getMobile, shopDealerUser.getMobile())) > 0) {
return fail("添加失败,手机号码已存在!");
}
if (shopDealerUserService.save(shopDealerUser)) { if (shopDealerUserService.save(shopDealerUser)) {
return success("添加成功", shopDealerUser); return success("添加成功", shopDealerUser);
} }

View File

@@ -66,7 +66,6 @@ public class ShopDealerWithdrawController extends BaseController {
} }
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:save')") @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:save')")
@OperationLog
@Transactional(rollbackFor = {Exception.class}) @Transactional(rollbackFor = {Exception.class})
@Operation(summary = "添加分销商提现明细表") @Operation(summary = "添加分销商提现明细表")
@PostMapping() @PostMapping()
@@ -87,7 +86,6 @@ public class ShopDealerWithdrawController extends BaseController {
} }
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')") @PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')")
@OperationLog
@Transactional(rollbackFor = {Exception.class}) @Transactional(rollbackFor = {Exception.class})
@Operation(summary = "修改分销商提现明细表") @Operation(summary = "修改分销商提现明细表")
@PutMapping() @PutMapping()
@@ -138,10 +136,9 @@ public class ShopDealerWithdrawController extends BaseController {
return fail("用户openid为空无法发起微信转账"); return fail("用户openid为空无法发起微信转账");
} }
// 微信支付批量转账接口对 out_batch_no 有最小长度限制(当前为 >= 5 // 微信支付商家转账接口对商户单号通常有最小长度限制(当前实测为 >= 5
// 使用 0 填充,避免如 "WD59" 这种过短导致 PARAM_ERROR。 // 使用 0 填充,避免如 "WD59" 这种过短导致 PARAM_ERROR。
String outBatchNo = String.format("WD%03d", db.getId()); String outBillNo = String.format("WD%03d", db.getId());
String outDetailNo = outBatchNo + "D1";
String remark = "分销商提现"; String remark = "分销商提现";
String userName = db.getRealName(); String userName = db.getRealName();
@@ -150,9 +147,7 @@ public class ShopDealerWithdrawController extends BaseController {
tenantId, tenantId,
openid, openid,
db.getMoney(), db.getMoney(),
outBatchNo, outBillNo,
outDetailNo,
"分销商提现",
remark, remark,
userName userName
); );

View File

@@ -37,6 +37,12 @@ public class ShopDealerUser implements Serializable {
@TableField(exist = false) @TableField(exist = false)
private String openid; private String openid;
@Schema(description = "店铺名称")
private String dealerName;
@Schema(description = "小区名称")
private String community;
@Schema(description = "头像") @Schema(description = "头像")
@TableField(exist = false) @TableField(exist = false)
private String avatar; private String avatar;

View File

@@ -2,6 +2,7 @@ package com.gxwebsoft.shop.entity;
import java.math.BigDecimal; import java.math.BigDecimal;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
@@ -44,6 +45,10 @@ public class ShopGoods implements Serializable {
@Schema(description = "产品分类ID") @Schema(description = "产品分类ID")
private Integer categoryId; private Integer categoryId;
@Schema(description = "分类名称")
@TableField(exist = false)
private String categoryName;
@Schema(description = "路由地址") @Schema(description = "路由地址")
private String path; private String path;

View File

@@ -64,6 +64,7 @@
</if> </if>
<if test="param.keywords != null"> <if test="param.keywords != null">
AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%') AND (a.comments LIKE CONCAT('%', #{param.keywords}, '%')
OR a.user_id = #{param.keywords} OR a.dealer_name LIKE CONCAT('%', #{param.keywords}, '%') OR a.real_name LIKE CONCAT('%', #{param.keywords}, '%') OR a.mobile LIKE CONCAT('%', #{param.keywords}, '%')
) )
</if> </if>
</where> </where>

View File

@@ -4,8 +4,9 @@
<!-- 关联查询sql --> <!-- 关联查询sql -->
<sql id="selectSql"> <sql id="selectSql">
SELECT a.* SELECT a.*, b.title AS categoryName
FROM shop_goods a FROM shop_goods a
LEFT JOIN cms_navigation b ON a.category_id = b.navigation_id
<where> <where>
<if test="param.goodsId != null"> <if test="param.goodsId != null">
AND a.goods_id = #{param.goodsId} AND a.goods_id = #{param.goodsId}

View File

@@ -75,3 +75,9 @@ payment:
key-prefix: "Payment:1" key-prefix: "Payment:1"
# 缓存过期时间(小时) # 缓存过期时间(小时)
expire-hours: 24 expire-hours: 24
# 微信支付-商家转账(升级版)转账场景报备信息(必须与商户平台 transfer_scene_id=1005 的报备信息一致)
wechatpay:
transfer:
scene-id: 1005
scene-report-infos-json: '[{"info_type":"岗位类型","info_content":"业务员"},{"info_type":"报酬说明","info_content":"配送费"}]'

View File

@@ -63,3 +63,9 @@ aliyun:
access-key-id: LTAI5tEsyhW4GCKbds1qsopg access-key-id: LTAI5tEsyhW4GCKbds1qsopg
access-key-secret: zltFlQrYVAoq2KMFDWgLa3GhkMNeyO access-key-secret: zltFlQrYVAoq2KMFDWgLa3GhkMNeyO
endpoint: mt.cn-hangzhou.aliyuncs.com endpoint: mt.cn-hangzhou.aliyuncs.com
# 微信支付-商家转账(升级版)转账场景报备信息(必须与商户平台 transfer_scene_id=1005 的报备信息一致)
wechatpay:
transfer:
scene-id: 1005
scene-report-infos-json: '[{"info_type":"岗位类型","info_content":"业务员"},{"info_type":"报酬说明","info_content":"配送费"}]'

View File

@@ -77,3 +77,7 @@ aliyun:
access-key-id: LTAI5tEsyhW4GCKbds1qsopg access-key-id: LTAI5tEsyhW4GCKbds1qsopg
access-key-secret: zltFlQrYVAoq2KMFDWgLa3GhkMNeyO access-key-secret: zltFlQrYVAoq2KMFDWgLa3GhkMNeyO
endpoint: mt.cn-hangzhou.aliyuncs.com endpoint: mt.cn-hangzhou.aliyuncs.com
wechatpay:
transfer:
scene-id: 1005
scene-report-infos-json: '[{"info_type":"岗位类型","info_content":"业务员"},{"info_type":"报酬说明","info_content":"配送费"}]'

View File

@@ -75,3 +75,9 @@ payment:
key-prefix: "Payment:1" key-prefix: "Payment:1"
# 缓存过期时间(小时) # 缓存过期时间(小时)
expire-hours: 24 expire-hours: 24
# 微信支付-商家转账(升级版)转账场景报备信息(必须与商户平台 transfer_scene_id=1005 的报备信息一致)
wechatpay:
transfer:
scene-id: 1005
scene-report-infos-json: '[{"info_type":"岗位类型","info_content":"业务员"},{"info_type":"报酬说明","info_content":"配送费"}]'