diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java index 1e65aec629..05e1dc1f2f 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/ErrorCodeConstants.java @@ -101,5 +101,11 @@ public interface ErrorCodeConstants { 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_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, "提现单更新转账状态失败,转账渠道不匹配"); } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java index b374566d39..01aee8acde 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/brokerage/BrokerageWithdrawTypeEnum.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.trade.enums.brokerage; import cn.iocoder.yudao.framework.common.core.ArrayValuable; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; import lombok.AllArgsConstructor; import lombok.Getter; @@ -17,9 +18,10 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable { WALLET(1, "钱包"), BANK(2, "银行卡"), - WECHAT(3, "微信"), // 手动打款 - ALIPAY(4, "支付宝"), + WECHAT_QR(3, "微信收款码"), // 手动打款 + ALIPAY_QR(4, "支付宝收款码"), // 手动打款 WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API + ALIPAY_API(6, "支付宝余额"), // 自动打款,通过支付宝转账 API ; public static final Integer[] ARRAYS = Arrays.stream(values()).map(BrokerageWithdrawTypeEnum::getType).toArray(Integer[]::new); @@ -45,7 +47,7 @@ public enum BrokerageWithdrawTypeEnum implements ArrayValuable { * @return 是否 */ public static boolean isApi(Integer type) { - return WECHAT_API.getType().equals(type); + return ObjectUtils.equalsAny(type, WALLET.getType(), ALIPAY_API.getType(), WECHAT_API.getType()); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java index fd7e475fed..dc0ce5ca4a 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/BrokerageWithdrawController.java @@ -82,10 +82,9 @@ public class BrokerageWithdrawController { return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap)); } - // TODO @luchi:update-transferred,url 改成这个。和 update-paid 、update-refunded 保持一致 - @PostMapping("/update-transfer") - @Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob - @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现 + @PostMapping("/update-transferred") + @Operation(summary = "更新佣金提现的转账结果") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob + @PermitAll // 无需登录,安全由 BrokerageWithdrawService 内部校验实现 public CommonResult updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO); brokerageWithdrawService.updateBrokerageWithdrawTransferred( diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java index 69765a5287..46e63b25b2 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java @@ -37,10 +37,10 @@ public class BrokerageWithdrawBaseVO { private Integer type; @Schema(description = "真实姓名", example = "赵六") - private String name; + private String userName; - @Schema(description = "账号", example = "88677912132") - private String accountNo; + @Schema(description = "收款账号", example = "88677912132") + private String userAccount; @Schema(description = "银行名称", example = "1") private String bankName; @@ -49,7 +49,7 @@ public class BrokerageWithdrawBaseVO { private String bankAddress; @Schema(description = "收款码", example = "https://www.iocoder.cn") - private String accountQrCodeUrl; + private String qrCodeUrl; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "状态不能为空") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java index b18ff74af4..1b5e942235 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawPageReqVO.java @@ -28,10 +28,10 @@ public class BrokerageWithdrawPageReqVO extends PageParam { private Integer type; @Schema(description = "真实姓名", example = "赵六") - private String name; + private String userName; @Schema(description = "账号", example = "886779132") - private String accountNo; + private String userAccount; @Schema(description = "银行名称", example = "1") private String bankName; diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java index 83d473825e..377731b6dc 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawCreateReqVO.java @@ -25,23 +25,18 @@ public class AppBrokerageWithdrawCreateReqVO { @NotNull(message = "提现金额不能为空") private Integer price; - // ========== 银行卡、微信、支付宝 提现相关字段 ========== - @Schema(description = "提现账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456789") - @NotBlank(message = "提现账号不能为空", groups = {Bank.class, Wechat.class, Alipay.class}) - private String accountNo; + @NotBlank(message = "提现账号不能为空", groups = {Bank.class, WechatApi.class, AlipayApi.class}) + 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") - @URL(message = "收款码的图片,必须是一个 URL") - private String accountQrCodeUrl; + @URL(message = "收款码的图片,必须是一个 URL", groups = {WechatQR.class, AlipayQR.class}) + private String qrCodeUrl; - // ========== 银行卡 提现相关字段 ========== - - @Schema(description = "持卡人姓名", example = "张三") - @NotBlank(message = "持卡人姓名不能为空", groups = {Bank.class}) - private String name; @Schema(description = "提现银行", example = "1") @NotNull(message = "提现银行不能为空", groups = {Bank.class}) private String bankName; @@ -54,10 +49,16 @@ public class AppBrokerageWithdrawCreateReqVO { 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) { @@ -65,10 +66,14 @@ public class AppBrokerageWithdrawCreateReqVO { ValidationUtils.validate(validator, this, Wallet.class); } else if (BrokerageWithdrawTypeEnum.BANK.getType().equals(type)) { ValidationUtils.validate(validator, this, Bank.class); - } else if (BrokerageWithdrawTypeEnum.WECHAT.getType().equals(type)) { - ValidationUtils.validate(validator, this, Wechat.class); - } else if (BrokerageWithdrawTypeEnum.ALIPAY.getType().equals(type)) { - ValidationUtils.validate(validator, this, Alipay.class); + } else if (BrokerageWithdrawTypeEnum.WECHAT_QR.getType().equals(type)) { + ValidationUtils.validate(validator, this, WechatQR.class); + } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(type)) { + 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); } } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java index f31c238001..0b996eadb4 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/dataobject/brokerage/BrokerageWithdrawDO.java @@ -57,13 +57,25 @@ public class BrokerageWithdrawDO extends BaseDO { 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 accountQrCodeUrl; + /** * 状态 *

@@ -95,4 +104,27 @@ public class BrokerageWithdrawDO extends BaseDO { */ 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; + } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java index 1942cc42bb..83513ef7f5 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/dal/mysql/brokerage/BrokerageWithdrawMapper.java @@ -27,8 +27,8 @@ public interface BrokerageWithdrawMapper extends BaseMapperX() .eqIfPresent(BrokerageWithdrawDO::getUserId, reqVO.getUserId()) .eqIfPresent(BrokerageWithdrawDO::getType, reqVO.getType()) - .likeIfPresent(BrokerageWithdrawDO::getName, reqVO.getName()) - .eqIfPresent(BrokerageWithdrawDO::getAccountNo, reqVO.getAccountNo()) + .likeIfPresent(BrokerageWithdrawDO::getUserName, reqVO.getUserName()) + .eqIfPresent(BrokerageWithdrawDO::getUserAccount, reqVO.getUserAccount()) .likeIfPresent(BrokerageWithdrawDO::getBankName, reqVO.getBankName()) .eqIfPresent(BrokerageWithdrawDO::getStatus, reqVO.getStatus()) .betweenIfPresent(BrokerageWithdrawDO::getCreateTime, reqVO.getCreateTime()) diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java index 240e771cf8..ebc75fa65f 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java @@ -1,19 +1,20 @@ package cn.iocoder.yudao.module.trade.service.brokerage; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 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.object.ObjectUtils; 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.PayTransferRespDTO; 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.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.dto.SocialUserRespDTO; 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.service.brokerage.bo.BrokerageWithdrawSummaryRespBO; import cn.iocoder.yudao.module.trade.service.config.TradeConfigService; +import com.google.common.base.Objects; import jakarta.annotation.Resource; import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; @@ -40,10 +42,12 @@ import java.time.LocalDateTime; import java.util.Collection; import java.util.Collections; 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.util.collection.CollectionUtils.convertSet; 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.*; /** @@ -64,8 +68,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { @Resource private TradeConfigService tradeConfigService; - @Resource - private NotifyMessageSendApi notifyMessageSendApi; @Resource private PayTransferApi payTransferApi; @Resource @@ -109,44 +111,53 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { } private void auditBrokerageWithdrawSuccess(BrokerageWithdrawDO withdraw) { - // 1.1 钱包 - if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) { - payWalletApi.addWalletBalance(new PayWalletAddBalanceReqDTO() - .setUserId(withdraw.getUserId()).setUserType(UserTypeEnum.MEMBER.getValue()) - .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()); + // 情况一:通过 API 转账 + if (BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) { + createPayTransfer(withdraw); + return; } - // 2. 非支付 API,则直接体现成功 - if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) { - brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(), - new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus())); - } + // 情况二:非 API 转账(手动打款) + brokerageWithdrawMapper.updateByIdAndStatus(withdraw.getId(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus(), + new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus())); } - private Long createPayTransfer(BrokerageWithdrawDO withdraw) { - // 1.1 获取微信 openid - SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId( - UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_PROGRAM.getType()); - // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈; + private void createPayTransfer(BrokerageWithdrawDO withdraw) { + // 1.1 获取基础信息 + String userAccount = withdraw.getUserAccount(); + String userName = withdraw.getUserName(); + String channelCode = null; + Map 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 构建请求 - PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO() - .setAppKey(tradeOrderProperties.getPayAppKey()) - .setChannelCode("wx_lite") // TODO @芋艿:【转账】这里要处理下; - .setMerchantOrderId(withdraw.getId().toString()) - .setPrice(withdraw.getPrice()) - .setSubject("佣金提现") - .setUserAccount(socialUser.getOpenid()).setUserIp(getClientIP()); - // 2. 发起请求 - return payTransferApi.createTransfer(payTransferCreateReqDTO); + PayTransferCreateReqDTO transferReqDTO = new PayTransferCreateReqDTO() + .setAppKey(tradeOrderProperties.getPayAppKey()).setChannelCode(channelCode) + .setMerchantOrderId(withdraw.getId().toString()).setSubject("佣金提现").setPrice(withdraw.getPrice()) + .setUserAccount(userAccount).setUserName(userName).setUserIp(getClientIP()) + .setChannelExtras(channelExtras); + // 1.3 发起请求 + Long payTransferId = payTransferApi.createTransfer(transferReqDTO); + + // 2. 更新提现记录 + brokerageWithdrawMapper.updateById(new BrokerageWithdrawDO().setId(withdraw.getId()) + .setPayTransferId(payTransferId).setTransferChannelCode(channelCode)); } private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) { @@ -220,22 +231,71 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { @Override @Transactional(rollbackFor = Exception.class) public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) { - BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id); - PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId); - // TODO @luchi:建议参考支付那,即使成功的情况下,也要各种校验;金额是否匹配、转账单号是否匹配、是否重复调用; - if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { - withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus()); - // 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()); + // 1.1 校验提现单是否存在 + BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id); + if (withdraw == null) { + log.error("[updateBrokerageWithdrawTransferred][withdraw({}) payTransfer({}) 不存在提现单,请进行处理!]", id, payTransferId); + throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS); } - 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 diff --git a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java index c6dd0e3b2f..386a7dd6c0 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/test/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImplTest.java @@ -56,8 +56,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest { BrokerageWithdrawDO dbBrokerageWithdraw = randomPojo(BrokerageWithdrawDO.class, o -> { // 等会查询到 o.setUserId(null); o.setType(null); - o.setName(null); - o.setAccountNo(null); + o.setUserName(null); + o.setUserAccount(null); o.setBankName(null); o.setStatus(null); o.setCreateTime(null); @@ -68,9 +68,9 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest { // 测试 type 不匹配 brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setType(null))); // 测试 name 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setName(null))); + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserName(null))); // 测试 accountNo 不匹配 - brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setAccountNo(null))); + brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setUserAccount(null))); // 测试 bankName 不匹配 brokerageWithdrawMapper.insert(cloneIgnoreId(dbBrokerageWithdraw, o -> o.setBankName(null))); // 测试 status 不匹配 @@ -87,8 +87,8 @@ public class BrokerageWithdrawServiceImplTest extends BaseDbUnitTest { BrokerageWithdrawPageReqVO reqVO = new BrokerageWithdrawPageReqVO(); reqVO.setUserId(null); reqVO.setType(null); - reqVO.setName(null); - reqVO.setAccountNo(null); + reqVO.setUserName(null); + reqVO.setUserAccount(null); reqVO.setBankName(null); reqVO.setStatus(null); reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28)); diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java index bf6f1bc7ec..5586929ab5 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApi.java @@ -1,6 +1,7 @@ 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.PayWalletRespDTO; /** * 钱包 API 接口 @@ -16,4 +17,13 @@ public interface PayWalletApi { */ void addWalletBalance(PayWalletAddBalanceReqDTO reqDTO); + /** + * 获取钱包信息 + * + * @param userId 用户编号 + * @param userType 用户类型 + * @return 钱包信息 + */ + PayWalletRespDTO getOrCreateWallet(Long userId, Integer userType); + } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java new file mode 100644 index 0000000000..9a8db79768 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/dto/PayWalletRespDTO.java @@ -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; + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java index 98e4a952b6..627cb5c135 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/wallet/PayWalletApiImpl.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.pay.api.wallet; 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.PayWalletRespDTO; 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.service.wallet.PayWalletService; @@ -30,4 +32,10 @@ public class PayWalletApiImpl implements PayWalletApi { 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); + } + } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http index 21255745f1..4162baad1b 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.http @@ -1,4 +1,4 @@ -### 请求 /pay/demo-withdraw/create 接口(支付宝) => 成功 +### 请求 /pay/demo-withdraw/create 接口(支付宝) POST {{baseUrl}}/pay/demo-withdraw/create Authorization: Bearer {{token}} Content-Type: application/json @@ -12,7 +12,7 @@ tenant-id: {{adminTenantId}} "userName": "oespxk7368" } -### 请求 /pay/demo-withdraw/create 接口(微信余额) => 成功 +### 请求 /pay/demo-withdraw/create 接口(微信余额) POST {{baseUrl}}/pay/demo-withdraw/create Authorization: Bearer {{token}} Content-Type: application/json @@ -26,7 +26,7 @@ tenant-id: {{adminTenantId}} "userName": "芋艿" } -### 请求 /pay/demo-withdraw/create 接口(钱包余额) => 成功 +### 请求 /pay/demo-withdraw/create 接口(钱包余额) POST {{baseUrl}}/pay/demo-withdraw/create Authorization: Bearer {{token}} Content-Type: application/json @@ -39,12 +39,12 @@ tenant-id: {{adminTenantId}} "userAccount": "1" } -### 请求 /pay/demo-withdraw/transfer 接口(发起转账) => 成功 +### 请求 /pay/demo-withdraw/transfer 接口(发起转账) POST {{baseUrl}}/pay/demo-withdraw/transfer?id=1 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} -### 请求 /pay/demo-withdraw/page 接口(查询分页) => 成功 +### 请求 /pay/demo-withdraw/page 接口(查询分页) GET {{baseUrl}}/pay/demo-withdraw/page?pageNo=1&pageSize=10 Authorization: Bearer {{token}} tenant-id: {{adminTenantId}} \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java index f14aa5cc12..251ec35ce7 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoWithdrawController.java @@ -51,11 +51,11 @@ public class PayDemoWithdrawController { return success(BeanUtils.toBean(pageResult, PayDemoWithdrawRespVO.class)); } - @PostMapping("/update-status") + @PostMapping("/update-transferred") @Operation(summary = "更新示例提现单的转账状态") // 由 pay-module 转账服务,进行回调 @PermitAll // 无需登录,安全由 PayDemoTransferService 内部校验实现 - public CommonResult updateDemoWithdrawStatus(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { - demoWithdrawService.updateDemoWithdrawStatus(Long.valueOf(notifyReqDTO.getMerchantOrderId()), + public CommonResult updateDemoWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) { + demoWithdrawService.updateDemoWithdrawTransferred(Long.valueOf(notifyReqDTO.getMerchantOrderId()), notifyReqDTO.getPayTransferId()); return success(true); } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java index 9fc70aed55..29e175c2a8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoTransferServiceImpl.java @@ -124,7 +124,7 @@ public class PayDemoTransferServiceImpl implements PayDemoWithdrawService { } @Override - public void updateDemoWithdrawStatus(Long id, Long payTransferId) { + public void updateDemoWithdrawTransferred(Long id, Long payTransferId) { // 1.1 校验转账单是否存在 PayDemoWithdrawDO withdraw = demoTransferMapper.selectById(id); if (withdraw == null) { diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java index 06f2e0ecdf..e8a5980ad8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoWithdrawService.java @@ -43,6 +43,6 @@ public interface PayDemoWithdrawService { * @param id 编号 * @param payTransferId 转账单编号 */ - void updateDemoWithdrawStatus(Long id, Long payTransferId); + void updateDemoWithdrawTransferred(Long id, Long payTransferId); }