feat:【MALL 商城】佣金提现,优化字段,以及支持更多 API 自动打款

This commit is contained in:
YunaiV 2025-05-10 10:07:11 +08:00
parent d2b668a676
commit 423c0b7ea7
17 changed files with 285 additions and 111 deletions

View File

@ -101,5 +101,11 @@ public interface ErrorCodeConstants {
ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中"); ErrorCode BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING = new ErrorCode(1_011_008_001, "佣金提现记录状态不是审核中");
ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元"); ErrorCode BROKERAGE_WITHDRAW_MIN_PRICE = new ErrorCode(1_011_008_002, "提现金额不能低于 {} 元");
ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元"); ErrorCode BROKERAGE_WITHDRAW_USER_BALANCE_NOT_ENOUGH = new ErrorCode(1_011_008_003, "您当前最多可提现 {} 元");
ErrorCode BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND = new ErrorCode(1_011_008_004, "提现失败,原因:用户未绑定微信");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR = new ErrorCode(1_011_008_005, "提现单更新转账状态失败,转账单不匹配");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED = new ErrorCode(1_011_008_006, "提现单更新转账状态失败,转账单状态不是成功或关闭状态");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(1_011_008_007, "提现单更新转账状态失败,转账单金额不匹配");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS = new ErrorCode(1_011_008_008, "提现单更新转账状态失败,转账单的商户订单不匹配");
ErrorCode BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH = new ErrorCode(1_011_008_009, "提现单更新转账状态失败,转账渠道不匹配");
} }

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.trade.enums.brokerage; package cn.iocoder.yudao.module.trade.enums.brokerage;
import cn.iocoder.yudao.framework.common.core.ArrayValuable; import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -17,9 +18,10 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
WALLET(1, "钱包"), WALLET(1, "钱包"),
BANK(2, "银行卡"), BANK(2, "银行卡"),
WECHAT(3, "微信"), // 手动打款 WECHAT_QR(3, "微信收款码"), // 手动打款
ALIPAY(4, "支付宝"), ALIPAY_QR(4, "支付宝收款码"), // 手动打款
WECHAT_API(5, "微信零钱"), // 自动打款通过微信转账 API WECHAT_API(5, "微信零钱"), // 自动打款通过微信转账 API
ALIPAY_API(6, "支付宝余额"), // 自动打款通过支付宝转账 API
; ;
public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new); public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new);
@ -45,7 +47,7 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable<Integer> {
* @return 是否 * @return 是否
*/ */
public static boolean isApi(Integer type) { public static boolean isApi(Integer type) {
return WECHAT_API.getType().equals(type); return ObjectUtils.equalsAny(type, WALLET.getType(), ALIPAY_API.getType(), WECHAT_API.getType());
} }
} }

View File

@ -82,10 +82,9 @@ public class BrokerageWithdrawController {
return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap)); return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
} }
// TODO @luchiupdate-transferredurl 改成这个 update-paid update-refunded 保持一致 @PostMapping("/update-transferred")
@PostMapping("/update-transfer") @Operation(summary = "更新佣金提现的转账结果") // pay-module 支付服务进行回调可见 PayNotifyJob
@Operation(summary = "更新转账订单为转账成功") // pay-module 支付服务进行回调可见 PayNotifyJob @PermitAll // 无需登录安全由 BrokerageWithdrawService 内部校验实现
@PermitAll // 无需登录安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO); log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
brokerageWithdrawService.updateBrokerageWithdrawTransferred( brokerageWithdrawService.updateBrokerageWithdrawTransferred(

View File

@ -37,10 +37,10 @@ public class BrokerageWithdrawBaseVO {
private Integer type; private Integer type;
@Schema(description = "真实姓名", example = "赵六") @Schema(description = "真实姓名", example = "赵六")
private String name; private String userName;
@Schema(description = "账号", example = "88677912132") @Schema(description = "收款账号", example = "88677912132")
private String accountNo; private String userAccount;
@Schema(description = "银行名称", example = "1") @Schema(description = "银行名称", example = "1")
private String bankName; private String bankName;
@ -49,7 +49,7 @@ public class BrokerageWithdrawBaseVO {
private String bankAddress; private String bankAddress;
@Schema(description = "收款码", example = "https://www.iocoder.cn") @Schema(description = "收款码", example = "https://www.iocoder.cn")
private String accountQrCodeUrl; private String qrCodeUrl;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空") @NotNull(message = "状态不能为空")

View File

@ -28,10 +28,10 @@ public class BrokerageWithdrawPageReqVO extends PageParam {
private Integer type; private Integer type;
@Schema(description = "真实姓名", example = "赵六") @Schema(description = "真实姓名", example = "赵六")
private String name; private String userName;
@Schema(description = "账号", example = "886779132") @Schema(description = "账号", example = "886779132")
private String accountNo; private String userAccount;
@Schema(description = "银行名称", example = "1") @Schema(description = "银行名称", example = "1")
private String bankName; private String bankName;

View File

@ -25,23 +25,18 @@ public class AppBrokerageWithdrawCreateReqVO {
@NotNull(message = "提现金额不能为空") @NotNull(message = "提现金额不能为空")
private Integer price; private Integer price;
// ========== 银行卡微信支付宝 提现相关字段 ==========
@Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789") @Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789")
@NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class}) @NotBlank(message = "提现账号不能为空", groups = {Bank.class, WechatApi.class, AlipayApi.class})
private String accountNo; private String userAccount;
// ========== 微信支付宝 提现相关字段 ========== @Schema(description = "提现姓名", example = "张三")
@NotBlank(message = "提现姓名不能为空", groups = {Bank.class, WechatApi.class, AlipayApi.class})
private String userName;
@Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png") @Schema(description = "收款码的图片", example = "https://www.iocoder.cn/1.png")
@URL(message = "收款码的图片,必须是一个 URL") @URL(message = "收款码的图片,必须是一个 URL", groups = {WechatQR.class, AlipayQR.class})
private String accountQrCodeUrl; private String qrCodeUrl;
// ========== 银行卡 提现相关字段 ==========
@Schema(description = "持卡人姓名", example = "张三")
@NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class})
private String name;
@Schema(description = "提现银行", example = "1") @Schema(description = "提现银行", example = "1")
@NotNull(message = "提现银行不能为空", groups = {Bank.class}) @NotNull(message = "提现银行不能为空", groups = {Bank.class})
private String bankName; private String bankName;
@ -54,10 +49,16 @@ public class AppBrokerageWithdrawCreateReqVO {
public interface Bank { public interface Bank {
} }
public interface Wechat { public interface WechatQR {
} }
public interface Alipay { public interface WechatApi {
}
public interface AlipayQR {
}
public interface AlipayApi {
} }
public void validate(Validator validator) { public void validate(Validator validator) {
@ -65,10 +66,14 @@ public class AppBrokerageWithdrawCreateReqVO {
ValidationUtils.validate(validator, this, Wallet.class); ValidationUtils.validate(validator, this, Wallet.class);
} else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) { } else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) {
ValidationUtils.validate(validator, this, Bank.class); ValidationUtils.validate(validator, this, Bank.class);
} else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) { } else if (BrokerageWithdrawTypeEnum.WECHAT_QR.getType().equals(type)) {
ValidationUtils.validate(validator, this, Wechat.class); ValidationUtils.validate(validator, this, WechatQR.class);
} else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) { } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(type)) {
ValidationUtils.validate(validator, this, Alipay.class); ValidationUtils.validate(validator, this, WechatApi.class);
} else if (BrokerageWithdrawTypeEnum.ALIPAY_QR.getType().equals(type)) {
ValidationUtils.validate(validator, this, AlipayQR.class);
} else if (BrokerageWithdrawTypeEnum.ALIPAY_API.getType().equals(type)) {
ValidationUtils.validate(validator, this, AlipayApi.class);
} }
} }

View File

@ -57,13 +57,25 @@ public class BrokerageWithdrawDO extends BaseDO {
private Integer type; private Integer type;
/** /**
* 真实姓名 * 提现姓名
* 1. {@link BrokerageWithdrawTypeEnum#BANK}银行开户人
* 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}微信真名
* 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}支付宝真名
*/ */
private String name; private String userName;
/** /**
* 账号 * 提现账号
* 1. {@link BrokerageWithdrawTypeEnum#BANK}银行账号
* 2. {@link BrokerageWithdrawTypeEnum#WECHAT_API}微信 openid
* 3. {@link BrokerageWithdrawTypeEnum#ALIPAY_API}支付宝账号
*/ */
private String accountNo; private String userAccount;
/**
* 收款码
*/
private String qrCodeUrl;
/** /**
* 银行名称 * 银行名称
*/ */
@ -72,10 +84,7 @@ public class BrokerageWithdrawDO extends BaseDO {
* 开户地址 * 开户地址
*/ */
private String bankAddress; private String bankAddress;
/**
* 收款码
*/
private String accountQrCodeUrl;
/** /**
* 状态 * 状态
* <p> * <p>
@ -95,4 +104,27 @@ public class BrokerageWithdrawDO extends BaseDO {
*/ */
private String remark; private String remark;
// ========== 转账相关字段 ==========
/**
* 转账单编号
*
* 关联 {@link cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO#getId()}
*/
private Long payTransferId;
/**
* 转账渠道
*
* 枚举 {@link cn.iocoder.yudao.module.pay.enums.PayChannelEnum}
*/
private String transferChannelCode;
/**
* 转账成功时间
*/
private LocalDateTime transferTime;
/**
* 转账错误提示
*/
private String transferErrorMsg;
} }

View File

@ -27,8 +27,8 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageWithdrawDO>() return selectPage(reqVO, new LambdaQueryWrapperX<BrokerageWithdrawDO>()
.eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId()) .eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId())
.eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType()) .eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType())
.likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName()) .likeIfPresent(BrokerageWithdrawDO::getUserName, reqVO.getUserName())
.eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo()) .eqIfPresent(BrokerageWithdrawDO::getUserAccount, reqVO.getUserAccount())
.likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName()) .likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName())
.eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus()) .eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus())
.betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime()) .betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime())

View File

@ -1,19 +1,20 @@
package cn.iocoder.yudao.module.trade.service.brokerage; package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi; import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO;
import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi; import cn.iocoder.yudao.module.pay.api.wallet.PayWalletApi;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
import cn.iocoder.yudao.module.pay.enums.PayChannelEnum;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
//import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferTypeEnum;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
import cn.iocoder.yudao.module.system.api.social.SocialUserApi; import cn.iocoder.yudao.module.system.api.social.SocialUserApi;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum; import cn.iocoder.yudao.module.system.enums.social.SocialTypeEnum;
@ -29,6 +30,7 @@ import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum;
import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties; import cn.iocoder.yudao.module.trade.framework.order.config.TradeOrderProperties;
import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO; import cn.iocoder.yudao.module.trade.service.brokerage.bo.BrokerageWithdrawSummaryRespBO;
import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService;
import com.google.common.base.Objects;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Validator; import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -40,10 +42,12 @@ import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_NOT_FOUND;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/** /**
@ -64,8 +68,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Resource @Resource
private TradeConfigService tradeConfigService; private TradeConfigService tradeConfigService;
@Resource
private NotifyMessageSendApi notifyMessageSendApi;
@Resource @Resource
private PayTransferApi payTransferApi; private PayTransferApi payTransferApi;
@Resource @Resource
@ -109,44 +111,53 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
} }
private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) { private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) {
// 1.1 钱包 // 情况一通过 API 转账
if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) { if (BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO() createPayTransfer(withdraw);
.setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) return;
.setBizType(PayWalletBizTypeEnum.TRANSFER.getType()).setBizId(withdraw.getId().toString())
.setPrice(withdraw.getPrice()));
// 1.2 微信 API
} else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) {
// TODO @luchi这里要加个转账单号的记录另外调用 API 转账是立马成功还是有延迟的哈
Long payTransferId = createPayTransfer(withdraw);
// 1.3 剩余类型都是手动打款所以不处理
} else {
// TODO 可优化未来可以考虑接入支付宝银联等 API 转账实现自动打款
log.info("[auditBrokerageWithdrawSuccess][withdraw({}) 类型({}) 手动打款,无需处理]", withdraw.getId(), withdraw.getType());
} }
// 2. 非支付 API则直接体现成功 // 情况二 API 转账手动打款
if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) { brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(),
brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(), new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()));
}
} }
private Long createPayTransfer(BrokerageWithdrawDO withdraw) { private void createPayTransfer(BrokerageWithdrawDO withdraw) {
// 1.1 获取微信 openid // 1.1 获取基础信息
SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId( String userAccount = withdraw.getUserAccount();
UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType()); String userName = withdraw.getUserName();
// TODO @luchi这里需要校验非空如果空的话要有业务异常哈 String channelCode = null;
Map<String, String> channelExtras = null;
if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.ALIPAY_API.getType())) {
channelCode = PayChannelEnum.ALIPAY_PC.getCode();
} else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType())) {
SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(
UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType());
if (socialUser == null) {
throw exception(BROKERAGE_WITHDRAW_TRANSFER_FAIL_WECHAT_NOT_BIND);
}
channelCode = PayChannelEnum.WX_LITE.getCode();
userAccount = socialUser.getOpenid();
// 特殊微信需要有报备信息
channelExtras = PayTransferCreateReqDTO.buildWeiXinChannelExtra1000("佣金提现", "佣金提现");
} else if (Objects.equal(withdraw.getType(), BrokerageWithdrawTypeEnum.WALLET.getType())) {
PayWalletRespDTO wallet = payWalletApi.getOrCreateWallet(withdraw.getUserId(), UserTypeEnum.MEMBER.getValue());
Assert.notNull(wallet, "钱包不存在");
channelCode = PayChannelEnum.WALLET.getCode();
userAccount = wallet.getId().toString();
}
// 1.2 构建请求 // 1.2 构建请求
PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO() PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO()
.setAppKey(tradeOrderProperties.getPayAppKey()) .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode)
.setChannelCode("wx_lite") // TODO @芋艿转账这里要处理下 .setMerchantOrderId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice())
.setMerchantOrderId(withdraw.getId().toString()) .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP())
.setPrice(withdraw.getPrice()) .setChannelExtras(channelExtras);
.setSubject("佣金提现") // 1.3 发起请求
.setUserAccount(socialUser.getOpenid()).setUserIp(getClientIP()); Long payTransferId = payTransferApi.createTransfer(transferReqDTO);
// 2. 发起请求
return payTransferApi.createTransfer(payTransferCreateReqDTO); // 2. 更新提现记录
brokerageWithdrawMapper.updateById(new BrokerageWithdrawDO().setId(withdraw.getId())
.setPayTransferId(payTransferId).setTransferChannelCode(channelCode));
} }
private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) { private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
@ -220,22 +231,71 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) { public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) {
BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id); // 1.1 校验提现单是否存在
PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId); BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
// TODO @luchi建议参考支付那即使成功的情况下也要各种校验金额是否匹配转账单号是否匹配是否重复调用 if (withdraw == null) {
if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { log.error("[updateBrokerageWithdrawTransferred][withdraw({}) payTransfer({}) 不存在提现单,请进行处理!]", id, payTransferId);
withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()); throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
// TODO @luchi发送站内信
} else if (PayTransferStatusEnum.isWaitingOrProcessing(transfer.getStatus())) {
// TODO @luchi这里是不是不用更新哈
withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus());
} else {
withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus());
// 3.2 驳回时需要退还用户佣金
brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
} }
brokerageWithdrawMapper.updateById(withdraw); // 1.2 校验提现单已经结束成功或失败
if (ObjectUtils.equalsAny(withdraw.getStatus(), BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus(),
BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus())) {
// 特殊转账单编号相同直接返回说明重复回调
if (ObjectUtil.equal(withdraw.getPayTransferId(), payTransferId)) {
log.warn("[updateBrokerageWithdrawTransferred][withdraw({}) 已结束,且转账单编号相同({}),直接返回]", withdraw, payTransferId);
return;
}
// 异常转账单编号不同说明转账单编号错误
log.error("[updateBrokerageWithdrawTransferred][withdraw({}) 转账单不匹配({}),请进行处理!]", withdraw, payTransferId);
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_ID_ERROR);
}
// 2. 校验转账单的合法性
PayTransferRespDTO payTransfer = validateBrokerageTransferStatusCanUpdate(withdraw, payTransferId);
// 3. 更新提现单状态
Integer newStatus = PayTransferStatusEnum.isSuccess(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus() :
PayTransferStatusEnum.isClosed(payTransfer.getStatus()) ? BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus() : null;
Assert.notNull(newStatus, "转账单状态({}) 不合法", payTransfer.getStatus());
brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), withdraw.getStatus(),
new BrokerageWithdrawDO().setStatus(newStatus)
.setTransferTime(payTransfer.getSuccessTime())
.setTransferErrorMsg(payTransfer.getChannelErrorMsg()));
}
private PayTransferRespDTO validateBrokerageTransferStatusCanUpdate(BrokerageWithdrawDO withdraw, Long payTransferId) {
// 1. 校验转账单是否存在
PayTransferRespDTO payTransfer = payTransferApi.getTransfer(payTransferId);
if (payTransfer == null) {
log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 不存在,请进行处理!]", withdraw.getId(), payTransferId);
throw exception(PAY_TRANSFER_NOT_FOUND);
}
// 2.1 校验转账单已成功或关闭
if (!PayTransferStatusEnum.isSuccessOrClosed(payTransfer.getStatus())) {
log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 未结束请进行处理payTransfer 数据是:{}]",
withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_TRANSFER_STATUS_NOT_SUCCESS_OR_CLOSED);
}
// 2.2 校验转账金额一致
if (ObjectUtil.notEqual(payTransfer.getPrice(), withdraw.getPrice())) {
log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账金额不匹配请进行处理withdraw 数据是:{}payTransfer 数据是:{}]",
withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_PRICE_NOT_MATCH);
}
// 2.3 校验转账订单匹配
if (ObjectUtil.notEqual(payTransfer.getMerchantOrderId(), withdraw.getId().toString())) {
log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) 转账单不匹配({})请进行处理payTransfer 数据是:{}]",
withdraw.getId(), payTransferId, JsonUtils.toJsonString(payTransfer));
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_MERCHANT_EXISTS);
}
// 2.4 校验转账渠道一致
if (ObjectUtil.notEqual(payTransfer.getChannelCode(), withdraw.getTransferChannelCode())) {
log.error("[validateBrokerageTransferStatusCanUpdate][withdraw({}) payTransfer({}) 转账渠道不匹配请进行处理withdraw 数据是:{}payTransfer 数据是:{}]",
withdraw.getId(), payTransferId, JsonUtils.toJsonString(withdraw), JsonUtils.toJsonString(payTransfer));
throw exception(BROKERAGE_WITHDRAW_UPDATE_STATUS_FAIL_PAY_CHANNEL_NOT_MATCH);
}
return payTransfer;
} }
@Override @Override

View File

@ -56,8 +56,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到 BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到
o.setUserId(null); o.setUserId(null);
o.setType(null); o.setType(null);
o.setName(null); o.setUserName(null);
o.setAccountNo(null); o.setUserAccount(null);
o.setBankName(null); o.setBankName(null);
o.setStatus(null); o.setStatus(null);
o.setCreateTime(null); o.setCreateTime(null);
@ -68,9 +68,9 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
// 测试 type 不匹配 // 测试 type 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null))); brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null)));
// 测试 name 不匹配 // 测试 name 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null))); brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserName(null)));
// 测试 accountNo 不匹配 // 测试 accountNo 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null))); brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserAccount(null)));
// 测试 bankName 不匹配 // 测试 bankName 不匹配
brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null))); brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null)));
// 测试 status 不匹配 // 测试 status 不匹配
@ -87,8 +87,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest {
BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO(); BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO();
reqVO.setUserId(null); reqVO.setUserId(null);
reqVO.setType(null); reqVO.setType(null);
reqVO.setName(null); reqVO.setUserName(null);
reqVO.setAccountNo(null); reqVO.setUserAccount(null);
reqVO.setBankName(null); reqVO.setBankName(null);
reqVO.setStatus(null); reqVO.setStatus(null);
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.pay.api.wallet; package cn.iocoder.yudao.module.pay.api.wallet;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
/** /**
* 钱包 API 接口 * 钱包 API 接口
@ -16,4 +17,13 @@ public interface PayWalletApi {
*/ */
void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO); void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO);
/**
* 获取钱包信息
*
* @param userId 用户编号
* @param userType 用户类型
* @return 钱包信息
*/
PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType);
} }

View File

@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.pay.api.wallet.dto;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import lombok.Data;
/**
* 钱包 Response DTO
*
* @author jason
*/
@Data
public class PayWalletRespDTO {
/**
* 编号
*/
private Long id;
/**
* 用户 id
*
* 关联 MemberUserDO id 编号
* 关联 AdminUserDO id 编号
*/
private Long userId;
/**
* 用户类型, 预留 多商户转帐可能需要用到
*
* 关联 {@link UserTypeEnum}
*/
private Integer userType;
/**
* 余额单位分
*/
private Integer balance;
/**
* 冻结金额单位分
*/
private Integer freezePrice;
/**
* 累计支出单位分
*/
private Integer totalExpense;
/**
* 累计充值单位分
*/
private Integer totalRecharge;
}

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.pay.api.wallet; package cn.iocoder.yudao.module.pay.api.wallet;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO; import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletRespDTO;
import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO; import cn.iocoder.yudao.module.pay.dal.dataobject.wallet.PayWalletDO;
import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum; import cn.iocoder.yudao.module.pay.enums.wallet.PayWalletBizTypeEnum;
import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService; import cn.iocoder.yudao.module.pay.service.wallet.PayWalletService;
@ -30,4 +32,10 @@ public class PayWalletApiImpl implements PayWalletApi {
payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice()); payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice());
} }
@Override
public PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType) {
PayWalletDO wallet = payWalletService.getOrCreateWallet(userId, userType);
return BeanUtils.toBean(wallet, PayWalletRespDTO.class);
}
} }

View File

@ -1,4 +1,4 @@
### 请求 /pay/demo-withdraw/create 接口(支付宝) => 成功 ### 请求 /pay/demo-withdraw/create 接口(支付宝)
POST {{baseUrl}}/pay/demo-withdraw/create POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
Content-Type: application/json Content-Type: application/json
@ -12,7 +12,7 @@ tenant-id: {{adminTenantId}}
"userName": "oespxk7368" "userName": "oespxk7368"
} }
### 请求 /pay/demo-withdraw/create 接口(微信余额) => 成功 ### 请求 /pay/demo-withdraw/create 接口(微信余额)
POST {{baseUrl}}/pay/demo-withdraw/create POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
Content-Type: application/json Content-Type: application/json
@ -26,7 +26,7 @@ tenant-id: {{adminTenantId}}
"userName": "芋艿" "userName": "芋艿"
} }
### 请求 /pay/demo-withdraw/create 接口(钱包余额) => 成功 ### 请求 /pay/demo-withdraw/create 接口(钱包余额)
POST {{baseUrl}}/pay/demo-withdraw/create POST {{baseUrl}}/pay/demo-withdraw/create
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
Content-Type: application/json Content-Type: application/json
@ -39,12 +39,12 @@ tenant-id: {{adminTenantId}}
"userAccount": "1" "userAccount": "1"
} }
### 请求 /pay/demo-withdraw/transfer 接口(发起转账) => 成功 ### 请求 /pay/demo-withdraw/transfer 接口(发起转账)
POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1 POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}} tenant-id: {{adminTenantId}}
### 请求 /pay/demo-withdraw/page 接口(查询分页) => 成功 ### 请求 /pay/demo-withdraw/page 接口(查询分页)
GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10 GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
tenant-id: {{adminTenantId}} tenant-id: {{adminTenantId}}

View File

@ -51,11 +51,11 @@ public class PayDemoWithdrawController {
return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class)); return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class));
} }
@PostMapping("/update-status") @PostMapping("/update-transferred")
@Operation(summary = "更新示例提现单的转账状态") // pay-module 转账服务进行回调 @Operation(summary = "更新示例提现单的转账状态") // pay-module 转账服务进行回调
@PermitAll // 无需登录安全由 PayDemoTransferService 内部校验实现 @PermitAll // 无需登录安全由 PayDemoTransferService 内部校验实现
public CommonResult<Boolean> updateDemoWithdrawStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { public CommonResult<Boolean> updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
demoWithdrawService.updateDemoWithdrawStatus(Long.valueOf(notifyReqDTO.getMerchantOrderId()), demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
notifyReqDTO.getPayTransferId()); notifyReqDTO.getPayTransferId());
return success(true); return success(true);
} }

View File

@ -124,7 +124,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService {
} }
@Override @Override
public void updateDemoWithdrawStatus(Long id, Long payTransferId) { public void updateDemoWithdrawTransferred(Long id, Long payTransferId) {
// 1.1 校验转账单是否存在 // 1.1 校验转账单是否存在
PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id);
if (withdraw == null) { if (withdraw == null) {

View File

@ -43,6 +43,6 @@ public interface PayDemoWithdrawService {
* @param id 编号 * @param id 编号
* @param payTransferId 转账单编号 * @param payTransferId 转账单编号
*/ */
void updateDemoWithdrawStatus(Long id, Long payTransferId); void updateDemoWithdrawTransferred(Long id, Long payTransferId);
} }