feat(withdraw): 优化分销商提现流程并支持微信收款确认
- 添加提现方式必填校验,确保 payType 不为空 - 调整资金安全机制,申请后统一进入待审核状态(10),审核通过后用户主动领取 - 移除旧的微信提现逻辑,简化基础提现功能 - 增加防御性代码,防止前端未传字段时被更新为 NULL - 修改审核通过逻辑,仅标记为 20 状态,等待用户主动领取 - 阻止后台直接设置微信提现已打款状态(40),需用户领取后自动完成 - 添加非微信转账场景的打款凭证上传要求 - 新增 receive 接口供用户领取提现,返回微信收款确认页 package_info - 新增 receive-success 回调接口将状态置为已打款(40)
This commit is contained in:
@@ -93,6 +93,13 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
shopDealerWithdraw.setUserId(loginUser.getUserId());
|
||||
|
||||
Integer payType = shopDealerWithdraw.getPayType();
|
||||
if (payType == null) {
|
||||
return fail("提现方式不能为空");
|
||||
}
|
||||
|
||||
// 资金安全:用户申请后统一进入“待审核(10)”,审核通过后再由用户主动领取
|
||||
shopDealerWithdraw.setApplyStatus(10);
|
||||
shopDealerWithdraw.setAuditTime(null);
|
||||
|
||||
final ShopDealerUser dealerUser = shopDealerUserService.getByUserIdRel(loginUser.getUserId());
|
||||
if (dealerUser == null) {
|
||||
@@ -102,20 +109,6 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
return fail("可提现佣金不足");
|
||||
}
|
||||
|
||||
// 微信提现:需要收款人 openid,否则后续无法返回 package_info 拉起确认页
|
||||
String wechatOpenid = null;
|
||||
if (Integer.valueOf(10).equals(payType)) {
|
||||
wechatOpenid = StrUtil.isNotBlank(shopDealerWithdraw.getOpenId())
|
||||
? shopDealerWithdraw.getOpenId()
|
||||
: shopDealerWithdraw.getOfficeOpenid();
|
||||
if (StrUtil.isBlank(wechatOpenid) && StrUtil.isNotBlank(dealerUser.getOpenid())) {
|
||||
wechatOpenid = dealerUser.getOpenid();
|
||||
}
|
||||
if (StrUtil.isBlank(wechatOpenid)) {
|
||||
return fail("用户openid为空,无法拉起微信收款确认页");
|
||||
}
|
||||
}
|
||||
|
||||
if (!shopDealerWithdrawService.save(shopDealerWithdraw)) {
|
||||
return fail("添加失败");
|
||||
}
|
||||
@@ -126,31 +119,6 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
throw PaymentException.systemError("扣减可提现佣金失败", null);
|
||||
}
|
||||
|
||||
// 微信提现:后端先创建“商家转账单(需用户确认)”,返回 package_info 给小程序调用 wx.requestMerchantTransfer
|
||||
if (Integer.valueOf(10).equals(payType)) {
|
||||
// 微信支付商家转账接口对商户单号通常有最小长度限制(当前实测为 >= 5)。
|
||||
String outBillNo = String.format("WD%03d", shopDealerWithdraw.getId());
|
||||
String remark = "分销商提现";
|
||||
String userName = shopDealerWithdraw.getRealName();
|
||||
|
||||
WxTransferService.TransferBillsResponse resp = wxTransferService.initiateSingleTransferWithUserConfirm(
|
||||
tenantId,
|
||||
wechatOpenid,
|
||||
shopDealerWithdraw.getMoney(),
|
||||
outBillNo,
|
||||
remark,
|
||||
userName
|
||||
);
|
||||
|
||||
if (resp == null || StrUtil.isBlank(resp.getPackageInfo())) {
|
||||
throw PaymentException.systemError("后台未返回 package_info,无法调起微信收款确认页", null);
|
||||
}
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("package_info", resp.getPackageInfo());
|
||||
return success("添加成功", data);
|
||||
}
|
||||
|
||||
return success("添加成功");
|
||||
} catch (PaymentException e) {
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
@@ -176,9 +144,18 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
return fail("提现记录不存在");
|
||||
}
|
||||
|
||||
// 防御:前端未传字段时,避免被 updateById 更新为 NULL
|
||||
if (shopDealerWithdraw.getApplyStatus() == null) {
|
||||
shopDealerWithdraw.setApplyStatus(db.getApplyStatus());
|
||||
}
|
||||
if (shopDealerWithdraw.getPayType() == null) {
|
||||
shopDealerWithdraw.setPayType(db.getPayType());
|
||||
}
|
||||
|
||||
Integer newStatus = shopDealerWithdraw.getApplyStatus() != null ? shopDealerWithdraw.getApplyStatus() : db.getApplyStatus();
|
||||
Integer oldStatus = db.getApplyStatus();
|
||||
Integer payType = shopDealerWithdraw.getPayType() != null ? shopDealerWithdraw.getPayType() : db.getPayType();
|
||||
Integer incomingStatus = shopDealerWithdraw.getApplyStatus();
|
||||
|
||||
// 驳回操作(状态迁移到30时),退回金额,避免重复退回
|
||||
if (Integer.valueOf(30).equals(newStatus) && !Integer.valueOf(30).equals(oldStatus)) {
|
||||
@@ -189,11 +166,57 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
// 微信收款且审核通过(迁移到20时),自动发起商家转账到零钱,并将状态置为已打款(40)
|
||||
// 审核通过:仅标记为 20,等待用户在提现记录中主动领取(微信等在线转账在领取环节执行)
|
||||
if (Integer.valueOf(20).equals(incomingStatus) && !Integer.valueOf(20).equals(oldStatus)) {
|
||||
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
|
||||
}
|
||||
|
||||
// 微信提现:已打款(40)由用户“领取成功”后置为 40,后台不允许直接改为 40
|
||||
if (Integer.valueOf(10).equals(payType)
|
||||
&& Integer.valueOf(20).equals(newStatus)
|
||||
&& !Integer.valueOf(20).equals(oldStatus)
|
||||
&& Integer.valueOf(40).equals(incomingStatus)
|
||||
&& !Integer.valueOf(40).equals(oldStatus)) {
|
||||
return fail("微信提现请用户在提现记录中领取后自动完成,后台不可直接置为已打款");
|
||||
}
|
||||
|
||||
// 已打款:非微信自动转账的场景,要求上传凭证
|
||||
if (Integer.valueOf(40).equals(incomingStatus) && !Integer.valueOf(40).equals(oldStatus)) {
|
||||
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
|
||||
if (!Integer.valueOf(10).equals(payType) && StrUtil.isBlankIfStr(shopDealerWithdraw.getImage())) {
|
||||
return fail("请上传打款凭证");
|
||||
}
|
||||
}
|
||||
if (shopDealerWithdrawService.updateById(shopDealerWithdraw)) {
|
||||
return success("修改成功");
|
||||
}
|
||||
return fail("修改失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:update')")
|
||||
@Operation(summary = "用户领取提现(审核通过后,返回微信收款确认页 package_info)")
|
||||
@PostMapping("/receive/{id}")
|
||||
public ApiResult<?> receive(@PathVariable("id") Integer id) {
|
||||
try {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("未登录或登录已失效");
|
||||
}
|
||||
if (id == null) {
|
||||
return fail("id不能为空");
|
||||
}
|
||||
|
||||
ShopDealerWithdraw db = shopDealerWithdrawService.getByIdRel(id);
|
||||
if (db == null) {
|
||||
return fail("提现记录不存在");
|
||||
}
|
||||
if (!loginUser.getUserId().equals(db.getUserId())) {
|
||||
return fail("无权领取该提现记录");
|
||||
}
|
||||
if (!Integer.valueOf(20).equals(db.getApplyStatus())) {
|
||||
return fail("当前状态不可领取");
|
||||
}
|
||||
if (!Integer.valueOf(10).equals(db.getPayType())) {
|
||||
return fail("仅微信提现支持在线领取");
|
||||
}
|
||||
|
||||
Integer tenantId = db.getTenantId() != null ? db.getTenantId() : getTenantId();
|
||||
if (tenantId == null) {
|
||||
@@ -209,43 +232,72 @@ public class ShopDealerWithdrawController extends BaseController {
|
||||
}
|
||||
}
|
||||
if (StrUtil.isBlank(openid)) {
|
||||
return fail("用户openid为空,无法发起微信转账");
|
||||
return fail("用户openid为空,无法拉起微信收款确认页");
|
||||
}
|
||||
|
||||
// 微信支付商家转账接口对商户单号通常有最小长度限制(当前实测为 >= 5)。
|
||||
// 使用 0 填充,避免如 "WD59" 这种过短导致 PARAM_ERROR。
|
||||
// 使用提现记录ID构造单号,保持幂等;微信要求 5-32 且仅字母/数字
|
||||
String outBillNo = String.format("WD%03d", db.getId());
|
||||
String remark = "分销商提现";
|
||||
String userName = db.getRealName();
|
||||
|
||||
try {
|
||||
wxTransferService.initiateSingleTransfer(
|
||||
tenantId,
|
||||
openid,
|
||||
db.getMoney(),
|
||||
outBillNo,
|
||||
remark,
|
||||
userName
|
||||
);
|
||||
} catch (PaymentException e) {
|
||||
return fail(e.getMessage());
|
||||
WxTransferService.TransferBillsResponse resp = wxTransferService.initiateSingleTransferWithUserConfirm(
|
||||
tenantId,
|
||||
openid,
|
||||
db.getMoney(),
|
||||
outBillNo,
|
||||
remark,
|
||||
userName
|
||||
);
|
||||
if (resp == null || StrUtil.isBlank(resp.getPackageInfo())) {
|
||||
return fail("后台未返回 package_info,无法调起微信收款确认页");
|
||||
}
|
||||
|
||||
shopDealerWithdraw.setApplyStatus(40);
|
||||
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("package_info", resp.getPackageInfo());
|
||||
return success("领取发起成功", data);
|
||||
} catch (PaymentException e) {
|
||||
return fail(e.getMessage());
|
||||
} catch (Exception e) {
|
||||
return fail("领取失败");
|
||||
}
|
||||
}
|
||||
|
||||
@Operation(summary = "用户领取成功回调(将状态置为已打款/已领取40)")
|
||||
@PostMapping("/receive-success/{id}")
|
||||
public ApiResult<?> receiveSuccess(@PathVariable("id") Integer id) {
|
||||
User loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return fail("未登录或登录已失效");
|
||||
}
|
||||
if (id == null) {
|
||||
return fail("id不能为空");
|
||||
}
|
||||
|
||||
// 已打款:非微信自动转账的场景,要求上传凭证
|
||||
if (Integer.valueOf(40).equals(newStatus)) {
|
||||
shopDealerWithdraw.setAuditTime(LocalDateTime.now());
|
||||
if (!Integer.valueOf(10).equals(payType) && StrUtil.isBlankIfStr(shopDealerWithdraw.getImage())) {
|
||||
return fail("请上传打款凭证");
|
||||
}
|
||||
ShopDealerWithdraw db = shopDealerWithdrawService.getByIdRel(id);
|
||||
if (db == null) {
|
||||
return fail("提现记录不存在");
|
||||
}
|
||||
if (shopDealerWithdrawService.updateById(shopDealerWithdraw)) {
|
||||
return success("修改成功");
|
||||
if (!loginUser.getUserId().equals(db.getUserId())) {
|
||||
return fail("无权操作该提现记录");
|
||||
}
|
||||
return fail("修改失败");
|
||||
if (!Integer.valueOf(10).equals(db.getPayType())) {
|
||||
return fail("仅微信提现支持在线领取");
|
||||
}
|
||||
if (Integer.valueOf(40).equals(db.getApplyStatus())) {
|
||||
return success("已领取");
|
||||
}
|
||||
if (!Integer.valueOf(20).equals(db.getApplyStatus())) {
|
||||
return fail("当前状态不可确认领取");
|
||||
}
|
||||
|
||||
ShopDealerWithdraw upd = new ShopDealerWithdraw();
|
||||
upd.setId(db.getId());
|
||||
upd.setApplyStatus(40);
|
||||
upd.setAuditTime(LocalDateTime.now());
|
||||
if (shopDealerWithdrawService.updateById(upd)) {
|
||||
return success("领取成功");
|
||||
}
|
||||
return fail("领取失败");
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('shop:shopDealerWithdraw:remove')")
|
||||
|
||||
Reference in New Issue
Block a user