diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java index 8dada3f..1f773dc 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/dict/core/DictFrameworkUtils.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.framework.dict.core; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; @@ -12,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import java.time.Duration; import java.util.List; +import java.util.Objects; /** * 字典工具类 @@ -93,4 +95,19 @@ public class DictFrameworkUtils { return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue(); } + @SneakyThrows + public static String parseDictDataLabel(String dictType, Integer value) { + if (value == null) { + return null; + } + return parseDictDataLabel(dictType, String.valueOf(value)); + } + + @SneakyThrows + public static String parseDictDataLabel(String dictType, String value) { + DictDataRespDTO dictData = GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)); + return dictData != null ? dictData.getLabel() : null; + } + + } diff --git a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/DictTypeConstants.java b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/DictTypeConstants.java index ff09e59..afde53e 100644 --- a/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/DictTypeConstants.java +++ b/yudao-module-mall/yudao-module-trade-api/src/main/java/cn/iocoder/yudao/module/trade/enums/DictTypeConstants.java @@ -7,6 +7,9 @@ package cn.iocoder.yudao.module.trade.enums; */ public interface DictTypeConstants { + String BROKERAGE_RECORD_STATUS = "brokerage_record_status"; // 佣金记录状态 + + String BROKERAGE_WITHDRAW_TYPE = "brokerage_withdraw_type"; // 佣金提现类型 String BROKERAGE_WITHDRAW_STATUS = "brokerage_withdraw_status"; // 佣金提现状态 } 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 5cea802..66df7cf 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 @@ -46,8 +46,7 @@ public class BrokerageWithdrawController { @Operation(summary = "通过申请") @PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')") public CommonResult approveBrokerageWithdraw(@RequestParam("id") Long id) { - brokerageWithdrawService.auditBrokerageWithdraw(id, - BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP()); + brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP()); return success(true); } diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java index 1749677..9f55b27 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/AppBrokerageWithdrawController.java @@ -1,14 +1,23 @@ package cn.iocoder.yudao.module.trade.controller.app.brokerage; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.dict.core.DictFrameworkUtils; +import cn.iocoder.yudao.module.pay.api.transfer.PayTransferApi; +import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO; import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO; import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawPageReqVO; import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawRespVO; import cn.iocoder.yudao.module.trade.convert.brokerage.BrokerageWithdrawConvert; import cn.iocoder.yudao.module.trade.dal.dataobject.brokerage.BrokerageWithdrawDO; +import cn.iocoder.yudao.module.trade.enums.DictTypeConstants; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum; +import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawTypeEnum; import cn.iocoder.yudao.module.trade.service.brokerage.BrokerageWithdrawService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -17,6 +26,8 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.Valid; +import java.util.Objects; + import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; @@ -30,12 +41,38 @@ public class AppBrokerageWithdrawController { @Resource private BrokerageWithdrawService brokerageWithdrawService; + @Resource + private PayTransferApi payTransferApi; + @GetMapping("/page") @Operation(summary = "获得分销提现分页") public CommonResult> getBrokerageWithdrawPage(AppBrokerageWithdrawPageReqVO pageReqVO) { - PageResult pageResult = brokerageWithdrawService.getBrokerageWithdrawPage( - BrokerageWithdrawConvert.INSTANCE.convert(pageReqVO, getLoginUserId())); - return success(BrokerageWithdrawConvert.INSTANCE.convertPage03(pageResult)); + PageResult pageResult = brokerageWithdrawService.getBrokerageWithdrawPage(BrokerageWithdrawConvert.INSTANCE.convert(pageReqVO, getLoginUserId())); + return success(BeanUtils.toBean(pageResult, AppBrokerageWithdrawRespVO.class, withdrawVO -> + withdrawVO.setTypeName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_TYPE, withdrawVO.getType())) + .setStatusName(DictFrameworkUtils.parseDictDataLabel(DictTypeConstants.BROKERAGE_WITHDRAW_STATUS, withdrawVO.getStatus())))); + } + + @GetMapping("/get") + @Operation(summary = "获得佣金提现") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult getBrokerageWithdraw(@RequestParam("id") Long id) { + BrokerageWithdrawDO withdraw = brokerageWithdrawService.getBrokerageWithdraw(id); + if (withdraw == null || ObjUtil.notEqual(withdraw.getUserId(), getLoginUserId())) { + return success(null); + } + // 审核中(转账中),并且是微信转账,需要返回 mchId 用于确认收款 + AppBrokerageWithdrawRespVO withdrawVO = BeanUtils.toBean(withdraw, AppBrokerageWithdrawRespVO.class); + if (Objects.equals(withdraw.getStatus(), BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.getStatus()) + && Objects.equals(withdraw.getType(), BrokerageWithdrawTypeEnum.WECHAT_API.getType()) + && withdraw.getPayTransferId() != null) { + PayTransferRespDTO transfer = payTransferApi.getTransfer(withdraw.getPayTransferId()); + if (transfer != null) { + withdrawVO.setTransferChannelPackageInfo(transfer.getChannelPackageInfo()) + .setTransferChannelMchId(transfer.getChannelMchId()); + } + } + return success(withdrawVO); } @PostMapping("/create") diff --git a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java index 4cfe930..3d0b988 100644 --- a/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java +++ b/yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/brokerage/vo/withdraw/AppBrokerageWithdrawRespVO.java @@ -12,6 +12,12 @@ public class AppBrokerageWithdrawRespVO { @Schema(description = "提现编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Long id; + @Schema(description = "提现类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; + + @Schema(description = "提现类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "微信") + private String typeName; + @Schema(description = "提现状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private Integer status; @@ -24,4 +30,15 @@ public class AppBrokerageWithdrawRespVO { @Schema(description = "提现时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; + // ========== 微信转账专属 ========== + + @Schema(description = "转账单编号", example = "1024") + private Long payTransferId; + + @Schema(description = "渠道 package 信息") + private String transferChannelPackageInfo; + + @Schema(description = "渠道商户号") + private String transferChannelMchId; + } 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 f31c238..b4dea8a 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 @@ -30,9 +30,10 @@ public class BrokerageWithdrawDO extends BaseDO { */ @TableId private Long id; + /** * 用户编号 - * + *

* 关联 MemberUserDO 的 id 字段 */ private Long userId; @@ -41,14 +42,17 @@ public class BrokerageWithdrawDO extends BaseDO { * 提现金额,单位:分 */ private Integer price; + /** * 提现手续费,单位:分 */ private Integer feePrice; + /** * 当前总佣金,单位:分 */ private Integer totalPrice; + /** * 提现类型 *

@@ -60,39 +64,71 @@ public class BrokerageWithdrawDO extends BaseDO { * 真实姓名 */ private String name; + /** * 账号 */ private String accountNo; + /** * 银行名称 */ private String bankName; + /** * 开户地址 */ private String bankAddress; + /** * 收款码 */ private String accountQrCodeUrl; + /** * 状态 *

* 枚举 {@link BrokerageWithdrawStatusEnum} */ private Integer status; + /** * 审核驳回原因 */ private String auditReason; + /** * 审核时间 */ private LocalDateTime auditTime; + /** * 备注 */ private String remark; + // ========== 转账相关字段 ========== + + /** + * 转账单编号 + *

+ * 关联 {@link cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO#getId()} + */ + private Long payTransferId; + + /** + * 转账渠道 + */ + 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/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 92eb749..549fda1 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 @@ -2,9 +2,11 @@ package cn.iocoder.yudao.module.trade.service.brokerage; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.json.JSONUtil; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayTransferCreateRespDTO; 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; @@ -37,9 +39,7 @@ import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; import javax.validation.Validator; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -84,15 +84,24 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) { // 1.1 校验存在 BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id); + // 1.2 特殊:【重新转账】如果是提现失败,并且状态是审核中,那么更新状态为审核中,并且清空 transferErrorMsg + if (BrokerageWithdrawStatusEnum.WITHDRAW_FAIL.getStatus().equals(withdraw.getStatus())) { + int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(), + new BrokerageWithdrawDO().setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg("")); + if (updateCount == 0) { + throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING); + } + withdraw.setStatus(BrokerageWithdrawStatusEnum.AUDITING.getStatus()).setTransferErrorMsg(""); + } // 1.2 校验状态为审核中 if (ObjectUtil.notEqual(BrokerageWithdrawStatusEnum.AUDITING.getStatus(), withdraw.getStatus())) { throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING); } // 2. 更新状态 - int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(), + int updateCount = brokerageWithdrawMapper.updateByIdAndStatus(id, withdraw.getStatus(), new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now())); - if (rows == 0) { + if (updateCount == 0) { throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING); } @@ -118,7 +127,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { // 1.2 微信 API } else if (BrokerageWithdrawTypeEnum.WECHAT_API.getType().equals(withdraw.getType())) { // TODO @luchi:这里,要加个转账单号的记录;另外,调用 API 转账,是立马成功,还是有延迟的哈? - Long payTransferId = createPayTransfer(withdraw); + createPayTransfer(withdraw); // 1.3 剩余类型,都是手动打款,所以不处理 } else { // TODO 可优化:未来可以考虑,接入支付宝、银联等 API 转账,实现自动打款 @@ -132,21 +141,41 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService { } } - private Long createPayTransfer(BrokerageWithdrawDO withdraw) { + private void createPayTransfer(BrokerageWithdrawDO withdraw) { // 1.1 获取微信 openid - SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId( - UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType()); + SocialUserRespDTO socialUser = socialUserApi.getSocialUserByUserId(UserTypeEnum.MEMBER.getValue(), withdraw.getUserId(), SocialTypeEnum.WECHAT_MINI_APP.getType()); // TODO @luchi:这里,需要校验非空。如果空的话,要有业务异常哈; // 1.2 构建请求 + Map channelExtras = new HashMap<>(); + List> sceneReportInfos = new ArrayList<>(); + sceneReportInfos.add(new LinkedHashMap() {{ + put("infoType", "岗位类型"); + put("infoContent", "分销员"); + }}); + sceneReportInfos.add(new LinkedHashMap() {{ + put("infoType", "报酬说明"); + put("infoContent", "邀请新用户有礼,提现" + MoneyUtils.fenToYuanStr(withdraw.getPrice()) + "元"); + }}); + channelExtras.put("sceneId", "1005"); + channelExtras.put("sceneReportInfos", JSONUtil.toJsonStr(sceneReportInfos)); PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO() .setAppKey(tradeOrderProperties.getPayAppKey()) - .setChannelCode("wx_lite").setType(PayTransferTypeEnum.WX_BALANCE.getType()) + .setChannelCode("wx_lite") + .setType(PayTransferTypeEnum.WX_BALANCE.getType()) .setMerchantTransferId(withdraw.getId().toString()) .setPrice(withdraw.getPrice()) - .setSubject("佣金提现") - .setOpenid(socialUser.getOpenid()).setUserIp(getClientIP()); + .setSubject("佣金报酬") + .setOpenid(socialUser.getOpenid()) + .setUserIp(getClientIP()) + .setChannelExtras(channelExtras); // 2. 发起请求 - return payTransferApi.createTransfer(payTransferCreateReqDTO); + PayTransferCreateRespDTO transferRespDTO = payTransferApi.createTransfer(payTransferCreateReqDTO); + + // 2. 更新提现记录 + brokerageWithdrawMapper.updateById(new BrokerageWithdrawDO() + .setId(withdraw.getId()) + .setPayTransferId(transferRespDTO.getId()) + .setTransferChannelCode("wx_lite")); } private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) { diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayTransferCreateRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayTransferCreateRespDTO.java new file mode 100755 index 0000000..7e2c5e1 --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayTransferCreateRespDTO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.pay.api.refund.dto; + +import lombok.Data; + +/** + * 转账单创建 Response DTO + * + * @author 芋道源码 + */ +@Data +public class PayTransferCreateRespDTO { + + /** + * 编号 + */ + private Long id; + + // ========== 其它字段 ========== + + /** + * 渠道 package 信息 + * + * 特殊:目前只有微信转账有这个东西!!! + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + +} diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java index 29ab90a..f186366 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.pay.api.transfer; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayTransferCreateRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferRespDTO; @@ -18,7 +19,7 @@ public interface PayTransferApi { * @param reqDTO 创建请求 * @return 转账单编号 */ - Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); /** * 获得转账单 diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java index d287dcc..0156ca9 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/dto/PayTransferRespDTO.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.pay.api.transfer.dto; import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum; import lombok.Data; +import java.time.LocalDateTime; + @Data public class PayTransferRespDTO { @@ -16,6 +18,22 @@ public class PayTransferRespDTO { */ private String no; + /** + * 转账渠道编码 + *

+ * 枚举 {PayChannelEnum} + */ + private String channelCode; + + // ========== 商户相关字段 ========== + + /** + * 商户转账单编号 + */ + private String merchantTransferId; + + // ========== 转账相关字段 ========== + /** * 转账金额,单位:分 */ @@ -23,9 +41,42 @@ public class PayTransferRespDTO { /** * 转账状态 - * + *

* 枚举 {@link PayTransferStatusEnum} */ private Integer status; + /** + * 订单转账成功时间 + */ + private LocalDateTime successTime; + + // ========== 其它字段 ========== + + /** + * 调用渠道的错误码 + */ + private String channelErrorCode; + /** + * 调用渠道的错误提示 + */ + private String channelErrorMsg; + + /** + * 渠道 package 信息 + *

+ * 特殊:目前只有微信转账有这个东西!!! + * + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; + /** + * 渠道商户号 + *

+ * 特殊:目前只有微信转账有这个东西!!! + * + * @see JSAPI 调起用户确认收款 + */ + private String channelMchId; + } diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java index 7b0d673..8c86800 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java @@ -73,7 +73,8 @@ public interface ErrorCodeConstants { ErrorCode PAY_SAME_MERCHANT_TRANSFER_PRICE_NOT_MATCH = new ErrorCode(1_007_009_003, "两次相同转账请求的金额不匹配"); ErrorCode PAY_MERCHANT_TRANSFER_EXISTS = new ErrorCode(1_007_009_004, "该笔业务的转账已经发起,请查询转账订单相关状态"); ErrorCode PAY_TRANSFER_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_005, "转账单不处于待转账"); - ErrorCode PAY_TRANSFER_STATUS_IS_NOT_PENDING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); + ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING = new ErrorCode(1_007_009_006, "转账单不处于待转账或转账中"); + ErrorCode PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING = new ErrorCode(1_007_009_007, "通知转账结果失败,原因:转账单不处于待转账或转账中"); // ========== 钱包用户转账模块 1-007-010-000 ========== ErrorCode WALLET_USER_TRANSFER_NOT_FOUND = new ErrorCode(1_007_010_000, "用户转账单不存在"); diff --git a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java index 6f2f27c..15a0fea 100644 --- a/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java +++ b/yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/transfer/PayTransferStatusEnum.java @@ -44,16 +44,18 @@ public enum PayTransferStatusEnum { public static boolean isWaiting(Integer status) { return Objects.equals(status, WAITING.getStatus()); } + public static boolean isInProgress(Integer status) { return Objects.equals(status, IN_PROGRESS.getStatus()); } /** * 是否处于待转账或者转账中的状态 + * * @param status 状态 */ public static boolean isPendingStatus(Integer status) { - return Objects.equals(status, WAITING.getStatus()) || Objects.equals(status, IN_PROGRESS.getStatus()); + return Objects.equals(status, IN_PROGRESS.getStatus()); } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java index e6f2ac3..df107a8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApiImpl.java @@ -1,15 +1,21 @@ package cn.iocoder.yudao.module.pay.api.transfer; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayTransferCreateRespDTO; 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.dal.dataobject.channel.PayChannelDO; import cn.iocoder.yudao.module.pay.dal.dataobject.transfer.PayTransferDO; +import cn.iocoder.yudao.module.pay.service.channel.PayChannelService; import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + /** * 转账单 API 实现类 * @@ -22,15 +28,26 @@ public class PayTransferApiImpl implements PayTransferApi { @Resource private PayTransferService payTransferService; + @Resource + private PayChannelService payChannelService; + @Override - public Long createTransfer(PayTransferCreateReqDTO reqDTO) { + public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { return payTransferService.createTransfer(reqDTO); } @Override public PayTransferRespDTO getTransfer(Long id) { PayTransferDO transfer = payTransferService.getTransfer(id); - return BeanUtils.toBean(transfer, PayTransferRespDTO.class); + if (transfer == null) { + return null; + } + PayChannelDO channel = payChannelService.getChannel(transfer.getChannelId()); + String mchId = null; + if (channel != null && channel.getConfig() instanceof WxPayClientConfig) { + mchId = ((WxPayClientConfig) channel.getConfig()).getMchId(); + } + return BeanUtils.toBean(transfer, PayTransferRespDTO.class).setChannelMchId(mchId); } } diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java new file mode 100755 index 0000000..e6d64ba --- /dev/null +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/transfer/AppPayTransferController.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.pay.controller.app.transfer; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.annotation.Resource; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "用户 APP - 转账单") +@RestController +@RequestMapping("/pay/transfer") +@Validated +@Slf4j +public class AppPayTransferController { + + @Resource + private PayTransferService transferService; + + @GetMapping("/sync") + @Operation(summary = "同步转账单") // 目的:解决微信转账的异步回调可能有延迟的问题 + @Parameter(name = "id", description = "编号", required = true, example = "1024") + public CommonResult syncTransfer(@RequestParam("id") Long id) { + transferService.syncTransfer(id); + return success(true); + } + +} diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java index 8f3563f..52bc3b1 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/transfer/PayTransferDO.java @@ -17,12 +17,13 @@ import java.time.LocalDateTime; import java.util.Map; // TODO 芋艿:需要详细 review + /** * 转账单 DO * * @author jason */ -@TableName(value ="pay_transfer", autoResultMap = true) +@TableName(value = "pay_transfer", autoResultMap = true) @KeySequence("pay_transfer_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data public class PayTransferDO extends BaseDO { @@ -41,21 +42,21 @@ public class PayTransferDO extends BaseDO { /** * 应用编号 - * + *

* 关联 {@link PayAppDO#getId()} */ private Long appId; /** * 转账渠道编号 - * + *

* 关联 {@link PayChannelDO#getId()} */ private Long channelId; /** * 转账渠道编码 - * + *

* 枚举 {@link PayChannelEnum} */ private String channelCode; @@ -63,7 +64,7 @@ public class PayTransferDO extends BaseDO { // ========== 商户相关字段 ========== /** * 商户转账单编号 - * + *

* 例如说,内部系统 A 的订单号,需要保证每个 PayAppDO 唯一 */ private String merchantTransferId; @@ -72,7 +73,7 @@ public class PayTransferDO extends BaseDO { /** * 类型 - * + *

* 枚举 {@link PayTransferTypeEnum} */ private Integer type; @@ -94,7 +95,7 @@ public class PayTransferDO extends BaseDO { /** * 转账状态 - * + *

* 枚举 {@link PayTransferStatusRespEnum} */ private Integer status; @@ -155,4 +156,12 @@ public class PayTransferDO extends BaseDO { */ private String channelNotifyData; + /** + * 渠道 package 信息 + *

+ * 特殊:目前只有微信转账有这个东西!!! + * + * @see JSAPI 调起用户确认收款 + */ + private String channelPackageInfo; } \ No newline at end of file diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java index c86dd28..84616d1 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferService.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pay.service.transfer; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayTransferCreateRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; @@ -18,10 +19,10 @@ public interface PayTransferService { /** * 创建转账单,并发起转账 - * + *

* 此时,会发起转账渠道的调用 * - * @param reqVO 请求 + * @param reqVO 请求 * @param userIp 用户 ip * @return 渠道的返回结果 */ @@ -33,10 +34,11 @@ public interface PayTransferService { * @param reqDTO 创建请求 * @return 转账单编号 */ - Long createTransfer(@Valid PayTransferCreateReqDTO reqDTO); + PayTransferCreateRespDTO createTransfer(@Valid PayTransferCreateReqDTO reqDTO); /** * 获取转账单 + * * @param id 转账单编号 */ PayTransferDO getTransfer(Long id); @@ -56,11 +58,18 @@ public interface PayTransferService { */ int syncTransfer(); + /** + * 【单个】同步渠道转账单状态 + * + * @param id 转账单编号 + */ + void syncTransfer(Long id); + /** * 渠道的转账通知 * - * @param channelId 渠道编号 - * @param notify 通知 + * @param channelId 渠道编号 + * @param notify 通知 */ void notifyTransfer(Long channelId, PayTransferRespDTO notify); diff --git a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java index bdd9555..c7295b8 100644 --- a/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java +++ b/yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/transfer/PayTransferServiceImpl.java @@ -5,12 +5,14 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.pay.core.client.PayClient; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO; import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO; import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferStatusRespEnum; import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.pay.api.refund.dto.PayTransferCreateRespDTO; import cn.iocoder.yudao.module.pay.api.transfer.dto.PayTransferCreateReqDTO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferCreateReqVO; import cn.iocoder.yudao.module.pay.controller.admin.transfer.vo.PayTransferPageReqVO; @@ -31,11 +33,13 @@ import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import javax.validation.Validator; +import java.util.Arrays; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.pay.convert.transfer.PayTransferConvert.INSTANCE; import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING; import static cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum.*; // TODO @jason:等彻底实现完,单测写写; @@ -74,14 +78,14 @@ public class PayTransferServiceImpl implements PayTransferService { // 2. 创建转账单,发起转账 PayTransferCreateReqDTO req = INSTANCE.convert(reqVO).setUserIp(userIp); - Long transferId = createTransfer(req); + PayTransferCreateRespDTO transfer = createTransfer(req); // 3. 返回转账单 - return getTransfer(transferId); + return getTransfer(transfer.getId()); } @Override - public Long createTransfer(PayTransferCreateReqDTO reqDTO) { + public PayTransferCreateRespDTO createTransfer(PayTransferCreateReqDTO reqDTO) { // 1.1 校验 App PayAppDO payApp = appService.validPayApp(reqDTO.getAppKey()); // 1.2 校验支付渠道是否有效 @@ -91,35 +95,38 @@ public class PayTransferServiceImpl implements PayTransferService { log.error("[createTransfer][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); throw exception(CHANNEL_NOT_FOUND); } - // 1.3 校验转账单已经发起过转账。 + // 1.3 校验转账单已经发起过转账 PayTransferDO transfer = validateTransferCanCreate(reqDTO, payApp.getId()); + // 2.1 情况一:不存在创建转账单,则进行创建 if (transfer == null) { - // 2.不存在创建转账单. 否则允许使用相同的 no 再次发起转账 String no = noRedisDAO.generate(TRANSFER_NO_PREFIX); - transfer = INSTANCE.convert(reqDTO) - .setChannelId(channel.getId()) - .setNo(no).setStatus(WAITING.getStatus()) - .setNotifyUrl(payApp.getTransferNotifyUrl()) - .setAppId(channel.getAppId()); + transfer = BeanUtils.toBean(reqDTO, PayTransferDO.class) + .setAppId(channel.getAppId()).setChannelId(channel.getId()) + .setNo(no).setStatus(PayTransferStatusEnum.WAITING.getStatus()) + .setNotifyUrl(payApp.getTransferNotifyUrl()); transferMapper.insert(transfer); + } else { + // 2.2 情况二:存在创建转账单,但是状态为关闭,则更新为等待中 + transferMapper.updateByIdAndStatus(transfer.getId(), Arrays.asList(transfer.getStatus()), new PayTransferDO().setStatus(PayTransferStatusEnum.WAITING.getStatus())); } + PayTransferRespDTO unifiedTransferResp = null; try { // 3. 调用三方渠道发起转账 - PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer) - .setOutTransferNo(transfer.getNo()); - transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel)); - PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); + PayTransferUnifiedReqDTO transferUnifiedReq = BeanUtils.toBean(reqDTO, PayTransferUnifiedReqDTO.class) + .setOutTransferNo(transfer.getNo()) + .setNotifyUrl(genChannelTransferNotifyUrl(channel)); + unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq); // 4. 通知转账结果 getSelf().notifyTransfer(channel, unifiedTransferResp); } catch (Throwable e) { // 注意这里仅打印异常,不进行抛出。 // 原因是:虽然调用支付渠道进行转账发生异常(网络请求超时),实际转账成功。这个结果,后续转账轮询可以拿到。 - // 或者使用相同 no 再次发起转账请求 - log.error("[createTransfer][转账 id({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); + // 或者,使用相同 no 再次发起转账请求 + log.error("[createTransfer][转账编号({}) requestDTO({}) 发生异常]", transfer.getId(), reqDTO, e); } - - return transfer.getId(); + return new PayTransferCreateRespDTO().setId(transfer.getId()) + .setChannelPackageInfo(unifiedTransferResp != null ? unifiedTransferResp.getChannelPackageInfo() : null); } /** @@ -169,86 +176,89 @@ public class PayTransferServiceImpl implements PayTransferService { } private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) { - // 1.校验 + // 1. 校验 PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); if (transfer == null) { throw exception(PAY_TRANSFER_NOT_FOUND); } - if (isInProgress(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + if (PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) { // 如果已经是转账中,直接返回,不用重复更新 + log.info("[notifyTransferProgressing][transfer({}) 已经是转账中状态,无需更新]", transfer.getId()); return; } - if (!isWaiting(transfer.getStatus())) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + if (!PayTransferStatusEnum.isWaiting(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); } - // 2.更新 + + // 2. 更新状态 int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), - CollUtil.newArrayList(WAITING.getStatus()), - new PayTransferDO().setStatus(IN_PROGRESS.getStatus())); + Arrays.asList(PayTransferStatusEnum.WAITING.getStatus()), + new PayTransferDO().setStatus(PayTransferStatusEnum.IN_PROGRESS.getStatus()) + .setChannelPackageInfo(notify.getChannelPackageInfo())); if (updateCounts == 0) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING); + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_IS_NOT_WAITING); } - log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId()); + log.info("[notifyTransferProgressing][transfer({}) 更新为转账进行中状态]", transfer.getId()); } private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) { - // 1.校验 + // 1. 校验状态 PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); if (transfer == null) { throw exception(PAY_TRANSFER_NOT_FOUND); } - if (isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 + if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) { // 如果已成功,直接返回,不用重复更新 + log.info("[notifyTransferSuccess][transfer({}) 已经是成功状态,无需更新]", transfer.getId()); return; } - if (!isPendingStatus(transfer.getStatus())) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + if (!PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); } - // 2.更新 + + // 2. 更新状态 int updateCounts = transferMapper.updateByIdAndStatus(transfer.getId(), - CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), - new PayTransferDO().setStatus(SUCCESS.getStatus()).setSuccessTime(notify.getSuccessTime()) + CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.IN_PROGRESS.getStatus()), + new PayTransferDO().setStatus(PayTransferStatusEnum.SUCCESS.getStatus()) + .setSuccessTime(notify.getSuccessTime()) .setChannelTransferNo(notify.getChannelTransferNo()) - .setChannelId(channel.getId()).setChannelCode(channel.getCode()) .setChannelNotifyData(JsonUtils.toJsonString(notify))); if (updateCounts == 0) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); } - log.info("[updateTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); + log.info("[notifyTransferSuccess][transfer({}) 更新为已转账]", transfer.getId()); // 3. 插入转账通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), - transfer.getId()); + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); } private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) { - // 1.校验 + // 1. 校验状态 PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo()); if (transfer == null) { throw exception(PAY_TRANSFER_NOT_FOUND); } - if (isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 - log.info("[updateTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId()); + if (PayTransferStatusEnum.isClosed(transfer.getStatus())) { // 如果已是关闭状态,直接返回,不用重复更新 + log.info("[notifyTransferClosed][transfer({}) 已经是关闭状态,无需更新]", transfer.getId()); return; } - if (!isPendingStatus(transfer.getStatus())) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + if (!PayTransferStatusEnum.isPendingStatus(transfer.getStatus())) { + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); } - // 2.更新 + // 2. 更新状态 int updateCount = transferMapper.updateByIdAndStatus(transfer.getId(), - CollUtil.newArrayList(WAITING.getStatus(), IN_PROGRESS.getStatus()), - new PayTransferDO().setStatus(CLOSED.getStatus()).setChannelId(channel.getId()) - .setChannelCode(channel.getCode()).setChannelTransferNo(notify.getChannelTransferNo()) - .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg()) - .setChannelNotifyData(JsonUtils.toJsonString(notify))); + CollUtil.newArrayList(PayTransferStatusEnum.WAITING.getStatus(), PayTransferStatusEnum.IN_PROGRESS.getStatus()), + new PayTransferDO().setStatus(PayTransferStatusEnum.CLOSED.getStatus()) + .setChannelTransferNo(notify.getChannelTransferNo()) + .setChannelNotifyData(JsonUtils.toJsonString(notify)) + .setChannelErrorCode(notify.getChannelErrorCode()).setChannelErrorMsg(notify.getChannelErrorMsg())); if (updateCount == 0) { - throw exception(PAY_TRANSFER_STATUS_IS_NOT_PENDING); + throw exception(PAY_TRANSFER_NOTIFY_FAIL_STATUS_NOT_WAITING_OR_PROCESSING); } - log.info("[updateTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); + log.info("[notifyTransferClosed][transfer({}) 更新为关闭状态]", transfer.getId()); // 3. 插入转账通知记录 - notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), - transfer.getId()); + notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(), transfer.getId()); } @@ -275,6 +285,15 @@ public class PayTransferServiceImpl implements PayTransferService { return count; } + @Override + public void syncTransfer(Long id) { + PayTransferDO transfer = transferMapper.selectById(id); + if (transfer == null) { + throw exception(PAY_TRANSFER_NOT_FOUND); + } + syncTransfer(transfer); + } + private boolean syncTransfer(PayTransferDO transfer) { try { // 1. 查询转账订单信息 diff --git a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java index 32f99fc..2bfdc9b 100644 --- a/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java +++ b/yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/weixin/AbstractWxPayClient.java @@ -458,45 +458,25 @@ public abstract class AbstractWxPayClient extends AbstractPayClient transferDetailList = Collections.singletonList( -// TransferBatchesRequest.TransferDetail.newBuilder() -// .outDetailNo(reqDTO.getOutTransferNo()) -// .transferAmount(reqDTO.getPrice()) -// .transferRemark(reqDTO.getSubject()) -// .openid(reqDTO.getOpenid()) -// .build()); -// // TODO @luchi:能不能我们搞个 TransferBatchesRequestX extends TransferBatchesRequest,这样更简洁一点。 -// TransferBatchesRequest transferBatches = TransferBatchesRequest.newBuilder() -// .appid(this.config.getAppId()) -// .outBatchNo(reqDTO.getOutTransferNo()) -// .batchName(reqDTO.getSubject()) -// .batchRemark(reqDTO.getSubject()) -// .totalAmount(reqDTO.getPrice()) -// .totalNum(transferDetailList.size()) -// .transferDetailList(transferDetailList).build() -// .setNotifyUrl(reqDTO.getNotifyUrl()); -// // 2.1 执行请求 -// TransferBatchesResult transferBatchesResult = client.getTransferService().transferBatches(transferBatches); -// // 2.2 创建返回结果 -// return PayTransferRespDTO.dealingOf(transferBatchesResult.getBatchId(), reqDTO.getOutTransferNo(), transferBatchesResult); -// } @Override protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException { // 1. 构建 TransferBillsRequest 请求 + String appId = null; + if (Objects.equals(this.config.getMchType(), "Single")) { + appId = this.config.getAppId(); + } else { + appId = this.config.getSubAppId(); + } TransferBillsRequest request = TransferBillsRequest.newBuilder() - .appid(this.config.getAppId()) + .appid(appId) .outBillNo(reqDTO.getOutTransferNo()) .transferAmount(reqDTO.getPrice()) .transferRemark(reqDTO.getSubject()) .transferSceneId(reqDTO.getChannelExtras().get("sceneId")) .openid(reqDTO.getOpenid()) .userName(reqDTO.getUserName()) - .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"), - TransferBillsRequest.TransferSceneReportInfo.class)) + .transferSceneReportInfos(JsonUtils.parseArray(reqDTO.getChannelExtras().get("sceneReportInfos"), TransferBillsRequest.TransferSceneReportInfo.class)) .notifyUrl(reqDTO.getNotifyUrl()) .build(); // 特殊:微信转账,必须 0.3 元起,才允许传入姓名 @@ -530,27 +510,20 @@ public abstract class AbstractWxPayClient extends AbstractPayClient Objects.equals("SUCCESS", detail.getDetailStatus()))) { - return PayTransferRespDTO.successOf(transferBatch.getBatchId(), parseDateV3(transferBatch.getUpdateTime()), - transferBatch.getOutBatchNo(), response); - } - if (response.getTransferDetailList().stream().anyMatch(detail -> Objects.equals("FAIL", detail.getDetailStatus()))) { - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), - transferBatch.getOutBatchNo(), response); - } + // 1. 执行请求 + TransferBillsGetResult response = client.getTransferService().getBillsByOutBillNo(outTradeNo); + + // 2. 创建返回结果 + String state = response.getState(); + if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) { + return PayTransferRespDTO.processingOf(response.getTransferBillNo(), response.getOutBillNo(), response); } - if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) { - return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(), - transferBatch.getOutBatchNo(), response); + if (Objects.equals("SUCCESS", state)) { + return PayTransferRespDTO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()), + response.getOutBillNo(), response); } - return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response); + return PayTransferRespDTO.closedOf(state, response.getFailReason(), + response.getOutBillNo(), response); } // ========== 各种工具方法 ==========