# Conflicts:
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/kefu/KeFuMessageController.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/kefu/AppKeFuMessageController.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/kefu/KeFuMessageServiceImpl.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/record/BrokerageRecordBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/user/BrokerageUserBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/user/BrokerageUserClearBrokerageUserReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/user/BrokerageUserUpdateBrokerageEnabledReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/user/BrokerageUserUpdateBrokerageUserReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/brokerage/vo/withdraw/BrokerageWithdrawRejectReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/DeliveryPickUpStoreController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreBaseVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreCreateReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/delivery/vo/pickup/DeliveryPickUpStoreUpdateReqVO.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/admin/order/TradeOrderController.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/brokerage/BrokerageWithdrawServiceImpl.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreService.java
#	yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/service/delivery/DeliveryPickUpStoreServiceImpl.java
#	yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/transfer/PayTransferApi.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/app/vo/PayAppBaseVO.java
#	yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClientConfig.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/refund/PayRefundUnifiedReqDTO.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/transfer/PayTransferUnifiedReqDTO.java
#	yudao-module-pay/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/NonePayClientConfig.java
pull/158/MERGE
YunaiV 2024-11-25 20:26:19 +08:00
commit 2b8334018a
98 changed files with 1215 additions and 354 deletions

View File

@ -51,7 +51,8 @@ public class ProductBrowseHistoryController {
convertSet(pageResult.getList(), ProductBrowseHistoryDO::getSpuId));
return success(BeanUtils.toBean(pageResult, ProductBrowseHistoryRespVO.class,
vo -> Optional.ofNullable(spuMap.get(vo.getSpuId()))
.ifPresent(spu -> vo.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setPrice(spu.getPrice()))));
.ifPresent(spu -> vo.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setPrice(spu.getPrice())
.setSalesCount(spu.getSalesCount()).setStock(spu.getStock()))));
}
}

View File

@ -1,12 +1,9 @@
package cn.iocoder.yudao.module.product.controller.admin.history.vo;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
@Schema(description = "管理后台 - 商品浏览记录 Response VO")
@ -31,4 +28,10 @@ public class ProductBrowseHistoryRespVO {
@Schema(description = "商品单价", example = "100")
private Integer price;
@Schema(description = "商品销量", example = "100")
private Integer salesCount;
@Schema(description = "库存", example = "100")
private Integer stock;
}

View File

@ -67,7 +67,8 @@ public class AppProductBrowseHistoryController {
Map<Long, ProductSpuDO> spuMap = convertMap(productSpuService.getSpuList(spuIds), ProductSpuDO::getId);
return success(BeanUtils.toBean(pageResult, AppProductBrowseHistoryRespVO.class,
vo -> Optional.ofNullable(spuMap.get(vo.getSpuId()))
.ifPresent(spu -> vo.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setPrice(spu.getPrice()))));
.ifPresent(spu -> vo.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setPrice(spu.getPrice())
.setSalesCount(spu.getSalesCount()).setStock(spu.getStock()))));
}
}

View File

@ -17,13 +17,19 @@ public class AppProductBrowseHistoryRespVO {
// ========== 商品相关字段 ==========
@Schema(description = "商品 SPU 名称", example = "赵六")
@Schema(description = "商品 SPU 名称", requiredMode = REQUIRED, example = "赵六")
private String spuName;
@Schema(description = "商品封面图", example = "https://domain/pic.png")
@Schema(description = "商品封面图", requiredMode = REQUIRED, example = "https://www.iocoder.cn/pic.png")
private String picUrl;
@Schema(description = "商品单价", example = "100")
@Schema(description = "商品单价", requiredMode = REQUIRED, example = "50")
private Integer price;
@Schema(description = "商品销量", requiredMode = REQUIRED, example = "60")
private Integer salesCount;
@Schema(description = "库存", requiredMode = REQUIRED, example = "80")
private Integer stock;
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.conversation.KeFuConversationUpdatePinnedReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
import cn.iocoder.yudao.module.promotion.service.kefu.KeFuConversationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -34,6 +35,25 @@ public class KeFuConversationController {
@Resource
private MemberUserApi memberUserApi;
@GetMapping("/get")
@Operation(summary = "获得客服会话")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:query')")
public CommonResult<KeFuConversationRespVO> getConversation(@RequestParam("id") Long id) {
KeFuConversationDO conversation = conversationService.getConversation(id);
if (conversation == null) {
return success(null);
}
// 拼接数据
KeFuConversationRespVO result = BeanUtils.toBean(conversation, KeFuConversationRespVO.class);
MemberUserRespDTO memberUser = memberUserApi.getUser(conversation.getUserId()).getCheckedData();
if (memberUser != null) {
result.setUserAvatar(memberUser.getAvatar()).setUserNickname(memberUser.getNickname());
}
return success(result);
}
@PutMapping("/update-conversation-pinned")
@Operation(summary = "置顶/取消置顶客服会话")
@PreAuthorize("@ss.hasPermission('promotion:kefu-conversation:update')")

View File

@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.promotion.controller.admin.kefu;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
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.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageListReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
@ -14,12 +13,13 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -56,19 +56,18 @@ public class KeFuMessageController {
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得客服消息分页")
@GetMapping("/list")
@Operation(summary = "获得客服消息列表")
@PreAuthorize("@ss.hasPermission('promotion:kefu-message:query')")
public CommonResult<PageResult<KeFuMessageRespVO>> getKeFuMessagePage(@Valid KeFuMessagePageReqVO pageReqVO) {
public CommonResult<List<KeFuMessageRespVO>> getKeFuMessageList(@Valid KeFuMessageListReqVO pageReqVO) {
// 获得数据
PageResult<KeFuMessageDO> pageResult = messageService.getKeFuMessagePage(pageReqVO);
List<KeFuMessageDO> list = messageService.getKeFuMessageList(pageReqVO);
// 拼接数据
PageResult<KeFuMessageRespVO> result = BeanUtils.toBean(pageResult, KeFuMessageRespVO.class);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(filterList(result.getList(),
List<KeFuMessageRespVO> result = BeanUtils.toBean(list, KeFuMessageRespVO.class);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(filterList(result,
item -> UserTypeEnum.ADMIN.getValue().equals(item.getSenderType())), KeFuMessageRespVO::getSenderId));
result.getList().forEach(item-> findAndThen(userMap, item.getSenderId(),
user -> item.setSenderAvatar(user.getAvatar())));
result.forEach(item -> findAndThen(userMap, item.getSenderId(), user -> item.setSenderAvatar(user.getAvatar())));
return success(result);
}

View File

@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 客服消息列表 Request VO")
@Data
public class KeFuMessageListReqVO {
private static final Integer LIMIT = 10;
@Schema(description = "会话编号", example = "12580")
@NotNull(message = "会话编号不能为空")
private Long conversationId;
@Schema(description = "发送时间", example = "2024-03-27 12:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTime;
@Schema(description = "每次查询条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "每次查询条数不能为空")
@Min(value = 1, message = "每次查询条数最小值为 1")
@Max(value = 100, message = "每次查询最大值为 100")
private Integer limit = LIMIT;
}

View File

@ -1,16 +0,0 @@
package cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.ToString;
@Schema(description = "管理后台 - 客服消息分页 Request VO")
@Data
@ToString(callSuper = true)
public class KeFuMessagePageReqVO extends PageParam {
@Schema(description = "会话编号", example = "12580")
private Long conversationId;
}

View File

@ -5,11 +5,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 积分商城活动分页 Request VO")
@Data
@ -17,20 +12,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_
@ToString(callSuper = true)
public class PointActivityPageReqVO extends PageParam {
@Schema(description = "积分商城活动商品", example = "19509")
private Long spuId;
@Schema(description = "活动状态", example = "2")
private Integer status;
@Schema(description = "备注", example = "你说的对")
private String remark;
@Schema(description = "排序")
private Integer sort;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -2,23 +2,29 @@ package cn.iocoder.yudao.module.promotion.controller.app.kefu;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
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.module.promotion.controller.admin.kefu.vo.message.KeFuMessageRespVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import cn.iocoder.yudao.module.promotion.service.kefu.KeFuMessageService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "用户 APP - 客服消息")
@ -30,6 +36,9 @@ public class AppKeFuMessageController {
@Resource
private KeFuMessageService kefuMessageService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/send")
@Operation(summary = "发送客服消息")
public CommonResult<Long> sendKefuMessage(@Valid @RequestBody AppKeFuMessageSendReqVO sendReqVO) {
@ -45,11 +54,17 @@ public class AppKeFuMessageController {
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得客服消息分页")
public CommonResult<PageResult<KeFuMessageRespVO>> getKefuMessagePage(@Valid AppKeFuMessagePageReqVO pageReqVO) {
PageResult<KeFuMessageDO> pageResult = kefuMessageService.getKeFuMessagePage(pageReqVO, getLoginUserId());
return success(BeanUtils.toBean(pageResult, KeFuMessageRespVO.class));
@GetMapping("/list")
@Operation(summary = "获得客服消息列表")
public CommonResult<List<KeFuMessageRespVO>> getKefuMessageList(@Valid AppKeFuMessagePageReqVO pageReqVO) {
List<KeFuMessageDO> list = kefuMessageService.getKeFuMessageList(pageReqVO, getLoginUserId());
// 拼接数据
List<KeFuMessageRespVO> result = BeanUtils.toBean(list, KeFuMessageRespVO.class);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(filterList(result,
item -> UserTypeEnum.ADMIN.getValue().equals(item.getSenderType())), KeFuMessageRespVO::getSenderId));
result.forEach(item -> findAndThen(userMap, item.getSenderId(), user -> item.setSenderAvatar(user.getAvatar())));
return success(result);
}
}

View File

@ -1,16 +1,33 @@
package cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
@Schema(description = "用户 App - 客服消息分页 Request VO")
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "用户 App - 客服消息 Request VO")
@Data
@ToString(callSuper = true)
public class AppKeFuMessagePageReqVO extends PageParam {
public class AppKeFuMessagePageReqVO {
private static final Integer LIMIT = 10;
@Schema(description = "会话编号", example = "12580")
private Long conversationId;
@Schema(description = "发送时间", example = "2024-03-27 12:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTime;
@Schema(description = "每次查询条数,最大值为 100", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "每次查询条数不能为空")
@Min(value = 1, message = "每次查询条数最小值为 1")
@Max(value = 100, message = "每次查询最大值为 100")
private Integer limit = LIMIT;
}

View File

@ -4,8 +4,6 @@ import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "用户 App - 积分商城活动 Response VO")
@Data
public class AppPointActivityRespVO {
@ -30,20 +28,6 @@ public class AppPointActivityRespVO {
@ExcelProperty("积分商城活动总库存")
private Integer totalStock;
// TODO @puhui999只返回必要的字段例如说 remark、sort、createTime 应该是不需要的呢。也可以看看别的也不需要哈。
@Schema(description = "备注", example = "你说的对")
@ExcelProperty("备注")
private String remark;
@Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("排序")
private Integer sort;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
// ========== 商品字段 ==========
@Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取

View File

@ -217,8 +217,7 @@ public interface CombinationActivityConvert {
List<CombinationRecordDO> createRecords = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
// 基础信息和团长保持一致
CombinationRecordDO newRecord = BeanUtils.toBean(headRecord, CombinationRecordDO.class)
.setId(null).setHeadId(headRecord.getHeadId());
CombinationRecordDO newRecord = convert5(headRecord).setHeadId(headRecord.getId());
// 虚拟信息
newRecord.setCount(0) // 会单独更新下,在后续的 Service 逻辑里
.setUserId(0L).setNickname("").setAvatar("").setOrderId(0L);
@ -226,5 +225,7 @@ public interface CombinationActivityConvert {
}
return createRecords;
}
@Mapping(target = "id", ignore = true)
CombinationRecordDO convert5(CombinationRecordDO headRecord);
}

View File

@ -1,10 +1,8 @@
package cn.iocoder.yudao.module.promotion.dal.mysql.kefu;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageListReqVO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -21,10 +19,20 @@ import java.util.List;
@Mapper
public interface KeFuMessageMapper extends BaseMapperX<KeFuMessageDO> {
default PageResult<KeFuMessageDO> selectPage(KeFuMessagePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<KeFuMessageDO>()
.eqIfPresent(KeFuMessageDO::getConversationId, reqVO.getConversationId())
.orderByDesc(KeFuMessageDO::getCreateTime));
/**
*
* 1.
* 2.
*
* @param reqVO
* @return
*/
default List<KeFuMessageDO> selectList(KeFuMessageListReqVO reqVO) {
return selectList(new QueryWrapperX<KeFuMessageDO>()
.eqIfPresent("conversation_id", reqVO.getConversationId())
.ltIfPresent("create_time", reqVO.getCreateTime())
.orderByDesc("create_time")
.limitN(reqVO.getLimit()));
}
default List<KeFuMessageDO> selectListByConversationIdAndUserTypeAndReadStatus(Long conversationId, Integer userType,
@ -40,10 +48,4 @@ public interface KeFuMessageMapper extends BaseMapperX<KeFuMessageDO> {
.in(KeFuMessageDO::getId, ids));
}
default PageResult<KeFuMessageDO> selectPage(AppKeFuMessagePageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<KeFuMessageDO>()
.eqIfPresent(KeFuMessageDO::getConversationId, pageReqVO.getConversationId())
.orderByDesc(KeFuMessageDO::getCreateTime));
}
}

View File

@ -19,12 +19,8 @@ public interface PointActivityMapper extends BaseMapperX<PointActivityDO> {
default PageResult<PointActivityDO> selectPage(PointActivityPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<PointActivityDO>()
.eqIfPresent(PointActivityDO::getSpuId, reqVO.getSpuId())
.eqIfPresent(PointActivityDO::getStatus, reqVO.getStatus())
.eqIfPresent(PointActivityDO::getRemark, reqVO.getRemark())
.eqIfPresent(PointActivityDO::getSort, reqVO.getSort())
.betweenIfPresent(PointActivityDO::getCreateTime, reqVO.getCreateTime())
.orderByDesc(PointActivityDO::getId));
.orderByDesc(PointActivityDO::getSort));
}
/**

View File

@ -375,7 +375,7 @@ public class CombinationRecordServiceImpl implements CombinationRecordService {
CombinationRecordDO updateRecord = new CombinationRecordDO().setId(item.getId())
.setStatus(status.getStatus()).setEndTime(now);
if (CombinationRecordStatusEnum.isSuccess(status.getStatus())) { // 虚拟成团完事更改状态成功后还需要把参与人数修改为成团需要人数
updateRecord.setUserCount(updateRecord.getUserSize());
updateRecord.setUserCount(records.size());
}
updateRecords.add(updateRecord);
});

View File

@ -13,6 +13,14 @@ import java.util.List;
*/
public interface KeFuConversationService {
/**
*
*
* @param id
* @return
*/
KeFuConversationDO getConversation(Long id);
/**
*
*

View File

@ -30,6 +30,11 @@ public class KeFuConversationServiceImpl implements KeFuConversationService {
@Resource
private KeFuConversationMapper conversationMapper;
@Override
public KeFuConversationDO getConversation(Long id) {
return conversationMapper.selectById(id);
}
@Override
public void deleteKefuConversation(Long id) {
// 校验存在

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.promotion.service.kefu;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageListReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
@ -9,6 +8,8 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import javax.validation.Valid;
import java.util.List;
/**
* Service
*
@ -47,7 +48,7 @@ public interface KeFuMessageService {
* @param pageReqVO
* @return
*/
PageResult<KeFuMessageDO> getKeFuMessagePage(KeFuMessagePageReqVO pageReqVO);
List<KeFuMessageDO> getKeFuMessageList(KeFuMessageListReqVO pageReqVO);
/**
*
@ -56,6 +57,6 @@ public interface KeFuMessageService {
* @param userId
* @return
*/
PageResult<KeFuMessageDO> getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId);
List<KeFuMessageDO> getKeFuMessageList(AppKeFuMessagePageReqVO pageReqVO, Long userId);
}

View File

@ -2,14 +2,12 @@ package cn.iocoder.yudao.module.promotion.service.kefu;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.infra.api.websocket.WebSocketSenderApi;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageListReqVO;
import cn.iocoder.yudao.module.promotion.controller.admin.kefu.vo.message.KeFuMessageSendReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessagePageReqVO;
import cn.iocoder.yudao.module.promotion.controller.app.kefu.vo.message.AppKeFuMessageSendReqVO;
@ -17,12 +15,13 @@ import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuConversationDO;
import cn.iocoder.yudao.module.promotion.dal.dataobject.kefu.KeFuMessageDO;
import cn.iocoder.yudao.module.promotion.dal.mysql.kefu.KeFuMessageMapper;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -113,9 +112,9 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
// 2.3 发送消息通知会员,管理员已读 -> 会员更新发送的消息状态
KeFuMessageDO keFuMessage = getFirst(filterList(messageList, message -> UserTypeEnum.MEMBER.getValue().equals(message.getSenderType())));
assert keFuMessage != null; // 断言避免警告
getSelf().sendAsyncMessageToMember(keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY);
getSelf().sendAsyncMessageToMember(keFuMessage.getSenderId(), KEFU_MESSAGE_ADMIN_READ, conversation.getId());
// 2.4 通知所有管理员消息已读
getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_ADMIN_READ, StrUtil.EMPTY);
getSelf().sendAsyncMessageToAdmin(KEFU_MESSAGE_ADMIN_READ, conversation.getId());
}
private void validateReceiverExist(Long receiverId, Integer receiverType) {
@ -138,20 +137,20 @@ public class KeFuMessageServiceImpl implements KeFuMessageService {
}
@Override
public PageResult<KeFuMessageDO> getKeFuMessagePage(KeFuMessagePageReqVO pageReqVO) {
return keFuMessageMapper.selectPage(pageReqVO);
public List<KeFuMessageDO> getKeFuMessageList(KeFuMessageListReqVO pageReqVO) {
return keFuMessageMapper.selectList(pageReqVO);
}
@Override
public PageResult<KeFuMessageDO> getKeFuMessagePage(AppKeFuMessagePageReqVO pageReqVO, Long userId) {
public List<KeFuMessageDO> getKeFuMessageList(AppKeFuMessagePageReqVO pageReqVO, Long userId) {
// 1. 获得客服会话
KeFuConversationDO conversation = conversationService.getConversationByUserId(userId);
if (conversation == null) {
return PageResult.empty();
return Collections.emptyList();
}
// 2. 设置会话编号
pageReqVO.setConversationId(conversation.getId());
return keFuMessageMapper.selectPage(pageReqVO);
return keFuMessageMapper.selectList(BeanUtils.toBean(pageReqVO, KeFuMessageListReqVO.class));
}
private KeFuMessageServiceImpl getSelf() {

View File

@ -36,6 +36,7 @@ public interface ErrorCodeConstants {
ErrorCode ORDER_UPDATE_ADDRESS_FAIL_STATUS_NOT_DELIVERED = new ErrorCode(1_011_000_031, "交易订单修改收货地址失败,原因:订单不是【待发货】状态");
ErrorCode ORDER_CREATE_FAIL_EXIST_UNPAID = new ErrorCode(1_011_000_032, "交易订单创建失败,原因:存在未付款订单");
ErrorCode ORDER_CANCEL_PAID_FAIL = new ErrorCode(1_011_000_033, "交易订单取消支付失败,原因:订单不是【{}】状态");
ErrorCode ORDER_PICK_UP_FAIL_NOT_VERIFY_USER = new ErrorCode(1_011_000_034, "交易订单自提失败,原因:你没有核销该门店订单的权限");
// ========== After Sale 模块 1-011-000-100 ==========
ErrorCode AFTER_SALE_NOT_FOUND = new ErrorCode(1_011_000_100, "售后单不存在");
@ -80,6 +81,7 @@ public interface ErrorCodeConstants {
// ========== 物流 PICK_UP 模块 1-011-006-000 ==========
ErrorCode PICK_UP_STORE_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店不存在");
ErrorCode PICK_UP_STORE_STAFF_NOT_EXISTS = new ErrorCode(1_011_006_000, "自提门店店员不存在");
// ========== 分销用户 模块 1-011-007-000 ==========
ErrorCode BROKERAGE_USER_NOT_EXISTS = new ErrorCode(1_011_007_000, "分销用户不存在");

View File

@ -11,9 +11,6 @@ public interface MessageTemplateConstants {
String SMS_ORDER_DELIVERY = "order_delivery"; // 短信模版编号
String SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE = "brokerage_withdraw_audit_approve"; // 佣金提现(审核通过)
String SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT = "brokerage_withdraw_audit_reject"; // 佣金提现(审核不通过)
// ======================= 小程序订阅消息模版 =======================
String WXA_ORDER_DELIVERY = "订单发货通知";

View File

@ -17,8 +17,9 @@ public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
WALLET(1, "钱包"),
BANK(2, "银行卡"),
WECHAT(3, "微信"),
WECHAT(3, "微信"), // 手动打款
ALIPAY(4, "支付宝"),
WECHAT_API(5, "微信零钱"), // 自动打款,通过微信转账 API
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BrokerageWithdrawTypeEnum::getType).toArray();
@ -37,4 +38,14 @@ public enum BrokerageWithdrawTypeEnum implements IntArrayValuable {
return ARRAYS;
}
/**
* API
*
* @param type
* @return
*/
public static boolean isApi(Integer type) {
return WECHAT_API.getType().equals(type);
}
}

View File

@ -0,0 +1,4 @@
/**
*
*/
package cn.iocoder.yudao.module.trade.controller.admin.base.system;

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.trade.controller.admin.base.system.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "用户精简信息 VO")
@Data
public class UserSimpleBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
}

View File

@ -44,7 +44,7 @@ public class BrokerageRecordController {
@Operation(summary = "获得佣金记录")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:brokerage-record:query')")
public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Integer id) {
public CommonResult<BrokerageRecordRespVO> getBrokerageRecord(@RequestParam("id") Long id) {
BrokerageRecordDO brokerageRecord = brokerageRecordService.getBrokerageRecord(id);
return success(BrokerageRecordConvert.INSTANCE.convert(brokerageRecord));
}

View File

@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import cn.iocoder.yudao.module.pay.api.notify.dto.PayTransferNotifyReqDTO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRejectReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawRespVO;
@ -14,6 +15,8 @@ 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 jakarta.annotation.security.PermitAll;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@ -24,11 +27,13 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
@Tag(name = "管理后台 - 佣金提现")
@RestController
@RequestMapping("/trade/brokerage-withdraw")
@Validated
@Slf4j
public class BrokerageWithdrawController {
@Resource
@ -40,8 +45,9 @@ public class BrokerageWithdrawController {
@PutMapping("/approve")
@Operation(summary = "通过申请")
@PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Integer id) {
brokerageWithdrawService.auditBrokerageWithdraw(id, BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "");
public CommonResult<Boolean> approveBrokerageWithdraw(@RequestParam("id") Long id) {
brokerageWithdrawService.auditBrokerageWithdraw(id,
BrokerageWithdrawStatusEnum.AUDIT_SUCCESS, "", getClientIP());
return success(true);
}
@ -49,7 +55,8 @@ public class BrokerageWithdrawController {
@Operation(summary = "驳回申请")
@PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:audit')")
public CommonResult<Boolean> rejectBrokerageWithdraw(@Valid @RequestBody BrokerageWithdrawRejectReqVO reqVO) {
brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(), BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason());
brokerageWithdrawService.auditBrokerageWithdraw(reqVO.getId(),
BrokerageWithdrawStatusEnum.AUDIT_FAIL, reqVO.getAuditReason(), getClientIP());
return success(true);
}
@ -57,7 +64,7 @@ public class BrokerageWithdrawController {
@Operation(summary = "获得佣金提现")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('trade:brokerage-withdraw:query')")
public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Integer id) {
public CommonResult<BrokerageWithdrawRespVO> getBrokerageWithdraw(@RequestParam("id") Long id) {
BrokerageWithdrawDO brokerageWithdraw = brokerageWithdrawService.getBrokerageWithdraw(id);
return success(BrokerageWithdrawConvert.INSTANCE.convert(brokerageWithdraw));
}
@ -75,4 +82,15 @@ public class BrokerageWithdrawController {
return success(BrokerageWithdrawConvert.INSTANCE.convertPage(pageResult, userMap));
}
// TODO @luchiupdate-transferredurl 改成这个。和 update-paid 、update-refunded 保持一致
@PostMapping("/update-transfer")
@Operation(summary = "更新转账订单为转账成功") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
@PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
public CommonResult<Boolean> updateBrokerageWithdrawTransferred(@RequestBody PayTransferNotifyReqDTO notifyReqDTO) {
log.info("[updateAfterRefund][notifyReqDTO({})]", notifyReqDTO);
brokerageWithdrawService.updateBrokerageWithdrawTransferred(
Long.parseLong(notifyReqDTO.getMerchantTransferId()), notifyReqDTO.getPayTransferId());
return success(true);
}
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.record;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

View File

@ -14,7 +14,7 @@ import java.time.LocalDateTime;
public class BrokerageRecordRespVO extends BrokerageRecordBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "28896")
private Integer id;
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 清除推广员 Request VO")
@Data
@ToString(callSuper = true)

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
@Data
@ToString(callSuper = true)

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.user;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分销用户 - 修改推广员 Request VO")
@Data
@ToString(callSuper = true)

View File

@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 驳回申请 Request VO")
@Data
@ToString(callSuper = true)
@ -14,7 +13,7 @@ public class BrokerageWithdrawRejectReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
@NotNull(message = "编号不能为空")
private Integer id;
private Long id;
@Schema(description = "审核驳回原因", example = "不对")
@NotEmpty(message = "审核驳回原因不能为空")

View File

@ -14,7 +14,7 @@ import java.time.LocalDateTime;
public class BrokerageWithdrawRespVO extends BrokerageWithdrawBaseVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7161")
private Integer id;
private Long id;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;

View File

@ -1,8 +1,13 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
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.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.trade.controller.admin.base.system.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.*;
import cn.iocoder.yudao.module.trade.convert.delivery.DeliveryPickUpStoreConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
@ -10,12 +15,12 @@ import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
@ -30,6 +35,9 @@ public class DeliveryPickUpStoreController {
@Resource
private DeliveryPickUpStoreService deliveryPickUpStoreService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建自提门店")
@PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:create')")
@ -60,10 +68,16 @@ public class DeliveryPickUpStoreController {
@PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:query')")
public CommonResult<DeliveryPickUpStoreRespVO> getDeliveryPickUpStore(@RequestParam("id") Long id) {
DeliveryPickUpStoreDO deliveryPickUpStore = deliveryPickUpStoreService.getDeliveryPickUpStore(id);
return success(DeliveryPickUpStoreConvert.INSTANCE.convert(deliveryPickUpStore));
if (deliveryPickUpStore == null) {
return success(null);
}
List<AdminUserRespDTO> verifyUsers = CollUtil.isNotEmpty(deliveryPickUpStore.getVerifyUserIds()) ?
adminUserApi.getUserList(deliveryPickUpStore.getVerifyUserIds()).getCheckedData() : null;
return success(BeanUtils.toBean(deliveryPickUpStore, DeliveryPickUpStoreRespVO.class)
.setVerifyUsers(BeanUtils.toBean(verifyUsers, UserSimpleBaseVO.class)));
}
@GetMapping("/list-all-simple")
@GetMapping("/simple-list")
@Operation(summary = "获得自提门店精简信息列表")
public CommonResult<List<DeliveryPickUpStoreSimpleRespVO>> getSimpleDeliveryPickUpStoreList() {
List<DeliveryPickUpStoreDO> list = deliveryPickUpStoreService.getDeliveryPickUpStoreListByStatus(
@ -88,4 +102,12 @@ public class DeliveryPickUpStoreController {
return success(DeliveryPickUpStoreConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/bind")
@Operation(summary = "绑定自提店员")
@PreAuthorize("@ss.hasPermission('trade:delivery:pick-up-store:create')")
public CommonResult<Boolean> bindDeliveryPickUpStore(@Valid @RequestBody DeliveryPickUpBindReqVO bindReqVO) {
deliveryPickUpStoreService.bindDeliveryPickUpStore(bindReqVO);
return success(true);
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.ToString;
import java.util.List;
@Schema(description = "管理后台 - 自提门店绑定核销人 Request VO")
@Data
@ToString(callSuper = true)
public class DeliveryPickUpBindReqVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "23128")
@NotNull(message = "编号不能为空")
private Long id;
@Schema(description = "绑定用户编号组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "23128")
@NotEmpty(message = "绑定用户编号组数不能未空")
private List<Long> verifyUserIds;
}

View File

@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.framework.common.validation.Mobile;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.time.LocalTime;
/**

View File

@ -1,7 +1,9 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 自提门店创建 Request VO")
@Data

View File

@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import lombok.*;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;

View File

@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup;
import cn.iocoder.yudao.module.trade.controller.admin.base.system.user.UserSimpleBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 自提门店 Response VO")
@Data
@ -19,4 +21,7 @@ public class DeliveryPickUpStoreRespVO extends DeliveryPickUpStoreBaseVO {
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "核销用户数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<UserSimpleBaseVO> verifyUsers;
}

View File

@ -5,6 +5,8 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 自提门店精简信息 Response VO")
@Data
@NoArgsConstructor
@ -29,4 +31,7 @@ public class DeliveryPickUpStoreSimpleRespVO {
@Schema(description = "门店详细地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "复旦大学路 188 号")
private String detailAddress;
@Schema(description = "绑定用户编号组数", requiredMode = Schema.RequiredMode.REQUIRED, example = "23128")
private List<Long> verifyUserIds;
}

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 自提门店更新 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)

View File

@ -10,19 +10,18 @@ import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderLogDO;
import cn.iocoder.yudao.module.trade.service.aftersale.AfterSaleService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderLogService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderQueryService;
import cn.iocoder.yudao.module.trade.service.order.TradeOrderUpdateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -31,6 +30,7 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 交易订单")
@RestController
@ -144,7 +144,7 @@ public class TradeOrderController {
@Parameter(name = "id", description = "交易订单编号")
@PreAuthorize("@ss.hasPermission('trade:order:pick-up')")
public CommonResult<Boolean> pickUpOrderById(@RequestParam("id") Long id) {
tradeOrderUpdateService.pickUpOrderByAdmin(id);
tradeOrderUpdateService.pickUpOrderByAdmin(getLoginUserId(), id);
return success(true);
}
@ -153,7 +153,7 @@ public class TradeOrderController {
@Parameter(name = "pickUpVerifyCode", description = "自提核销码")
@PreAuthorize("@ss.hasPermission('trade:order:pick-up')")
public CommonResult<Boolean> pickUpOrderByVerifyCode(@RequestParam("pickUpVerifyCode") String pickUpVerifyCode) {
tradeOrderUpdateService.pickUpOrderByAdmin(pickUpVerifyCode);
tradeOrderUpdateService.pickUpOrderByAdmin(getLoginUserId(), pickUpVerifyCode);
return success(true);
}

View File

@ -26,8 +26,6 @@ public interface DeliveryPickUpStoreConvert {
DeliveryPickUpStoreDO convert(DeliveryPickUpStoreUpdateReqVO bean);
DeliveryPickUpStoreRespVO convert(DeliveryPickUpStoreDO bean);
List<DeliveryPickUpStoreRespVO> convertList(List<DeliveryPickUpStoreDO> list);
PageResult<DeliveryPickUpStoreRespVO> convertPage(PageResult<DeliveryPickUpStoreDO> page);
@ -43,14 +41,13 @@ public interface DeliveryPickUpStoreConvert {
default List<AppDeliveryPickUpStoreRespVO> convertList(List<DeliveryPickUpStoreDO> list,
Double latitude, Double longitude) {
List<AppDeliveryPickUpStoreRespVO> voList = CollectionUtils.convertList(list, store -> {
return CollectionUtils.convertList(list, store -> {
AppDeliveryPickUpStoreRespVO storeVO = convert03(store);
if (latitude != null && longitude != null) {
storeVO.setDistance(NumberUtils.getDistance(latitude, longitude, storeVO.getLatitude(), storeVO.getLongitude()));
}
return storeVO;
});
return voList;
}
@Mapping(source = "areaId", target = "areaName", qualifiedByName = "convertAreaIdToAreaName")
AppDeliveryPickUpStoreRespVO convert03(DeliveryPickUpStoreDO bean);

View File

@ -2,19 +2,23 @@ package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalTime;
import java.util.List;
/**
* DO
*
* @author jason
*/
@TableName(value ="trade_delivery_pick_up_store")
@TableName(value ="trade_delivery_pick_up_store", autoResultMap = true)
@KeySequence("trade_delivery_pick_up_store_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryPickUpStoreDO extends BaseDO {
@ -74,6 +78,16 @@ public class DeliveryPickUpStoreDO extends BaseDO {
*/
private Double longitude;
/**
*
*
*
*
* {@link AdminUserRespDTO#getId()}
*/
@TableField(typeHandler = LongListTypeHandler.class)
private List<Long> verifyUserIds;
/**
*
*

View File

@ -1,49 +0,0 @@
package cn.iocoder.yudao.module.trade.dal.dataobject.delivery;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
// TODO @芋艿:后续再详细 review 一轮
// TODO @芋艿:可能改成 DeliveryPickUpStoreUserDO
/**
* DO
*
* @author jason
*/
@TableName(value ="trade_delivery_pick_up_store_staff")
@KeySequence("trade_delivery_pick_up_store_staff_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
public class DeliveryPickUpStoreStaffDO extends BaseDO {
/**
*
*/
@TableId
private Long id;
/**
*
*
* {@link DeliveryPickUpStoreDO#getId()}
*/
private Long storeId;
/**
* id
*
* {AdminUserDO#getId()}
*/
private Long adminUserId;
/**
*
*
* {@link CommonStatusEnum}
*/
private Integer status;
}

View File

@ -35,7 +35,7 @@ public interface BrokerageWithdrawMapper extends BaseMapperX<BrokerageWithdrawDO
.orderByAsc(BrokerageWithdrawDO::getStatus).orderByDesc(BrokerageWithdrawDO::getId));
}
default int updateByIdAndStatus(Integer id, Integer status, BrokerageWithdrawDO updateObj) {
default int updateByIdAndStatus(Long id, Integer status, BrokerageWithdrawDO updateObj) {
return update(updateObj, new LambdaUpdateWrapper<BrokerageWithdrawDO>()
.eq(BrokerageWithdrawDO::getId, id)
.eq(BrokerageWithdrawDO::getStatus, status));

View File

@ -1,14 +0,0 @@
package cn.iocoder.yudao.module.trade.dal.mysql.delivery;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreStaffDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface DeliveryPickUpStoreStaffMapper extends BaseMapperX<DeliveryPickUpStoreStaffDO> {
}

View File

@ -32,7 +32,7 @@ public interface BrokerageRecordService {
* @param id
* @return
*/
BrokerageRecordDO getBrokerageRecord(Integer id);
BrokerageRecordDO getBrokerageRecord(Long id);
/**
*

View File

@ -64,7 +64,7 @@ public class BrokerageRecordServiceImpl implements BrokerageRecordService {
private ProductSkuApi productSkuApi;
@Override
public BrokerageRecordDO getBrokerageRecord(Integer id) {
public BrokerageRecordDO getBrokerageRecord(Long id) {
return brokerageRecordMapper.selectById(id);
}

View File

@ -27,8 +27,9 @@ public interface BrokerageWithdrawService {
* @param id
* @param status
* @param auditReason
* @param userIp IP
*/
void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason);
void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp);
/**
*
@ -36,7 +37,7 @@ public interface BrokerageWithdrawService {
* @param id
* @return
*/
BrokerageWithdrawDO getBrokerageWithdraw(Integer id);
BrokerageWithdrawDO getBrokerageWithdraw(Long id);
/**
*
@ -55,6 +56,16 @@ public interface BrokerageWithdrawService {
*/
Long createBrokerageWithdraw(Long userId, AppBrokerageWithdrawCreateReqVO createReqVO);
/**
* API
*
*
*
* @param id
* @param payTransferId
*/
void updateBrokerageWithdrawTransferred(Long id, Long payTransferId);
/**
* userId
*

View File

@ -1,38 +1,48 @@
package cn.iocoder.yudao.module.trade.service.brokerage;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
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.number.MoneyUtils;
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.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.notify.dto.NotifySendSingleToUserReqDTO;
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;
import cn.iocoder.yudao.module.trade.controller.admin.brokerage.vo.withdraw.BrokerageWithdrawPageReqVO;
import cn.iocoder.yudao.module.trade.controller.app.brokerage.vo.withdraw.AppBrokerageWithdrawCreateReqVO;
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.dal.dataobject.config.TradeConfigDO;
import cn.iocoder.yudao.module.trade.dal.mysql.brokerage.BrokerageWithdrawMapper;
import cn.iocoder.yudao.module.trade.enums.MessageTemplateConstants;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageRecordBizTypeEnum;
import cn.iocoder.yudao.module.trade.enums.brokerage.BrokerageWithdrawStatusEnum;
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 jakarta.annotation.Resource;
import jakarta.validation.Validator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
/**
@ -42,6 +52,7 @@ import static cn.iocoder.yudao.module.trade.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
@Slf4j
public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Resource
@ -54,13 +65,22 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
@Resource
private NotifyMessageSendApi notifyMessageSendApi;
@Resource
private PayTransferApi payTransferApi;
@Resource
private SocialUserApi socialUserApi;
@Resource
private PayWalletApi payWalletApi;
@Resource
private Validator validator;
@Resource
private TradeOrderProperties tradeOrderProperties;
@Override
@Transactional(rollbackFor = Exception.class)
public void auditBrokerageWithdraw(Integer id, BrokerageWithdrawStatusEnum status, String auditReason) {
public void auditBrokerageWithdraw(Long id, BrokerageWithdrawStatusEnum status, String auditReason, String userIp) {
// 1.1 校验存在
BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
// 1.2 校验状态为审核中
@ -68,41 +88,67 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
}
// 2. 更新
// 2. 更新状态
int rows = brokerageWithdrawMapper.updateByIdAndStatus(id, BrokerageWithdrawStatusEnum.AUDITING.getStatus(),
new BrokerageWithdrawDO().setStatus(status.getStatus()).setAuditReason(auditReason).setAuditTime(LocalDateTime.now()));
if (rows == 0) {
throw exception(BROKERAGE_WITHDRAW_STATUS_NOT_AUDITING);
}
String templateCode;
// 3.1 审批通过的后续处理
if (BrokerageWithdrawStatusEnum.AUDIT_SUCCESS.equals(status)) {
templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_APPROVE;
// 3.1 通过时佣金转余额
if (BrokerageWithdrawTypeEnum.WALLET.getType().equals(withdraw.getType())) {
// todo 疯狂:
}
// TODO 疯狂:调用转账接口
auditBrokerageWithdrawSuccess(withdraw);
// 3.2 审批不通过的后续处理
} else if (BrokerageWithdrawStatusEnum.AUDIT_FAIL.equals(status)) {
templateCode = MessageTemplateConstants.SMS_BROKERAGE_WITHDRAW_AUDIT_REJECT;
// 3.2 驳回时需要退还用户佣金
brokerageRecordService.addBrokerage(withdraw.getUserId(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT,
String.valueOf(withdraw.getId()), withdraw.getPrice(), BrokerageRecordBizTypeEnum.WITHDRAW_REJECT.getTitle());
} else {
throw new IllegalArgumentException("不支持的提现状态:" + status);
}
// 4. 通知用户
Map<String, Object> templateParams = MapUtil.<String, Object>builder()
.put("createTime", LocalDateTimeUtil.formatNormal(withdraw.getCreateTime()))
.put("price", MoneyUtils.fenToYuanStr(withdraw.getPrice()))
.put("reason", auditReason)
.build();
notifyMessageSendApi.sendSingleMessageToMember(new NotifySendSingleToUserReqDTO()
.setUserId(withdraw.getUserId()).setTemplateCode(templateCode).setTemplateParams(templateParams)).checkError();
}
private BrokerageWithdrawDO validateBrokerageWithdrawExists(Integer id) {
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.BROKERAGE_WITHDRAW.getType()).setBizId(withdraw.getId().toString())
.setPrice(withdraw.getPrice())).checkError();
// 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则直接体现成功
if (!BrokerageWithdrawTypeEnum.isApi(withdraw.getType())) {
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_APP.getType()).getCheckedData();
// TODO @luchi这里需要校验非空。如果空的话要有业务异常哈
// 1.2 构建请求
PayTransferCreateReqDTO payTransferCreateReqDTO = new PayTransferCreateReqDTO()
.setAppKey(tradeOrderProperties.getPayAppKey())
.setChannelCode("wx_lite").setType(PayTransferTypeEnum.WX_BALANCE.getType())
.setMerchantTransferId(withdraw.getId().toString())
.setPrice(withdraw.getPrice())
.setSubject("佣金提现")
.setOpenid(socialUser.getOpenid()).setUserIp(getClientIP());
// 2. 发起请求
return payTransferApi.createTransfer(payTransferCreateReqDTO).getCheckedData();
}
private BrokerageWithdrawDO validateBrokerageWithdrawExists(Long id) {
BrokerageWithdrawDO withdraw = brokerageWithdrawMapper.selectById(id);
if (withdraw == null) {
throw exception(BROKERAGE_WITHDRAW_NOT_EXISTS);
@ -111,7 +157,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
}
@Override
public BrokerageWithdrawDO getBrokerageWithdraw(Integer id) {
public BrokerageWithdrawDO getBrokerageWithdraw(Long id) {
return brokerageWithdrawMapper.selectById(id);
}
@ -141,15 +187,6 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
return withdraw.getId();
}
@Override
public List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
BrokerageWithdrawStatusEnum status) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
}
/**
*
*
@ -157,7 +194,7 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
* @param percent
* @return
*/
Integer calculateFeePrice(Integer withdrawPrice, Integer percent) {
private Integer calculateFeePrice(Integer withdrawPrice, Integer percent) {
Integer feePrice = 0;
if (percent != null && percent > 0) {
feePrice = MoneyUtils.calculateRatePrice(withdrawPrice, Double.valueOf(percent));
@ -171,11 +208,42 @@ public class BrokerageWithdrawServiceImpl implements BrokerageWithdrawService {
* @param withdrawPrice
* @return
*/
TradeConfigDO validateWithdrawPrice(Integer withdrawPrice) {
private TradeConfigDO validateWithdrawPrice(Integer withdrawPrice) {
TradeConfigDO tradeConfig = tradeConfigService.getTradeConfig();
if (tradeConfig.getBrokerageWithdrawMinPrice() != null && withdrawPrice < tradeConfig.getBrokerageWithdrawMinPrice()) {
throw exception(BROKERAGE_WITHDRAW_MIN_PRICE, MoneyUtils.fenToYuanStr(tradeConfig.getBrokerageWithdrawMinPrice()));
}
return tradeConfig;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateBrokerageWithdrawTransferred(Long id, Long payTransferId) {
BrokerageWithdrawDO withdraw = validateBrokerageWithdrawExists(id);
PayTransferRespDTO transfer = payTransferApi.getTransfer(payTransferId).getCheckedData();
// TODO @luchi建议参考支付那即使成功的情况下也要各种校验金额是否匹配、转账单号是否匹配、是否重复调用
if (PayTransferStatusEnum.isSuccess(transfer.getStatus())) {
withdraw.setStatus(BrokerageWithdrawStatusEnum.WITHDRAW_SUCCESS.getStatus());
// TODO @luchi发送站内信
} else if (PayTransferStatusEnum.isPendingStatus(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);
}
@Override
public List<BrokerageWithdrawSummaryRespBO> getWithdrawSummaryListByUserId(Collection<Long> userIds,
BrokerageWithdrawStatusEnum status) {
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
return brokerageWithdrawMapper.selectCountAndSumPriceByUserIdAndStatus(userIds, status.getStatus());
}
}

View File

@ -1,12 +1,13 @@
package cn.iocoder.yudao.module.trade.service.delivery;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpBindReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStorePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreUpdateReqVO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import jakarta.validation.Valid;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
@ -55,6 +56,14 @@ public interface DeliveryPickUpStoreService {
*/
List<DeliveryPickUpStoreDO> getDeliveryPickUpStoreList(Collection<Long> ids);
/**
*
*
* @param status
* @return
*/
List<DeliveryPickUpStoreDO> getDeliveryPickUpStoreListByStatus(Integer status);
/**
*
*
@ -64,10 +73,10 @@ public interface DeliveryPickUpStoreService {
PageResult<DeliveryPickUpStoreDO> getDeliveryPickUpStorePage(DeliveryPickUpStorePageReqVO pageReqVO);
/**
*
*
*
* @param status
* @return
* @param bindReqVO
*/
List<DeliveryPickUpStoreDO> getDeliveryPickUpStoreListByStatus(Integer status);
void bindDeliveryPickUpStore(DeliveryPickUpBindReqVO bindReqVO);
}

View File

@ -1,16 +1,19 @@
package cn.iocoder.yudao.module.trade.service.delivery;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpBindReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreCreateReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStorePageReqVO;
import cn.iocoder.yudao.module.trade.controller.admin.delivery.vo.pickup.DeliveryPickUpStoreUpdateReqVO;
import cn.iocoder.yudao.module.trade.convert.delivery.DeliveryPickUpStoreConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.dal.mysql.delivery.DeliveryPickUpStoreMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
@ -29,6 +32,9 @@ public class DeliveryPickUpStoreServiceImpl implements DeliveryPickUpStoreServic
@Resource
private DeliveryPickUpStoreMapper deliveryPickUpStoreMapper;
@Resource
private AdminUserApi adminUserApi;
@Override
public Long createDeliveryPickUpStore(DeliveryPickUpStoreCreateReqVO createReqVO) {
// 插入
@ -56,7 +62,8 @@ public class DeliveryPickUpStoreServiceImpl implements DeliveryPickUpStoreServic
}
private void validateDeliveryPickUpStoreExists(Long id) {
if (deliveryPickUpStoreMapper.selectById(id) == null) {
DeliveryPickUpStoreDO deliveryPickUpStore = deliveryPickUpStoreMapper.selectById(id);
if (deliveryPickUpStore == null) {
throw exception(PICK_UP_STORE_NOT_EXISTS);
}
}
@ -71,14 +78,26 @@ public class DeliveryPickUpStoreServiceImpl implements DeliveryPickUpStoreServic
return deliveryPickUpStoreMapper.selectBatchIds(ids);
}
@Override
public List<DeliveryPickUpStoreDO> getDeliveryPickUpStoreListByStatus(Integer status) {
return deliveryPickUpStoreMapper.selectListByStatus(status);
}
@Override
public PageResult<DeliveryPickUpStoreDO> getDeliveryPickUpStorePage(DeliveryPickUpStorePageReqVO pageReqVO) {
return deliveryPickUpStoreMapper.selectPage(pageReqVO);
}
@Override
public List<DeliveryPickUpStoreDO> getDeliveryPickUpStoreListByStatus(Integer status) {
return deliveryPickUpStoreMapper.selectListByStatus(status);
public void bindDeliveryPickUpStore(DeliveryPickUpBindReqVO bindReqVO) {
// 1.1 校验门店存在
validateDeliveryPickUpStoreExists(bindReqVO.getId());
// 1.2 校验用户存在
adminUserApi.validateUserList(bindReqVO.getVerifyUserIds()).checkError();
// 2. 更新
DeliveryPickUpStoreDO updateObj = BeanUtils.toBean(bindReqVO, DeliveryPickUpStoreDO.class);
deliveryPickUpStoreMapper.updateById(updateObj);
}
}

View File

@ -129,16 +129,18 @@ public interface TradeOrderUpdateService {
/**
*
*
* @param userId
* @param id
*/
void pickUpOrderByAdmin(Long id);
void pickUpOrderByAdmin(Long userId, Long id);
/**
*
*
* @param userId
* @param pickUpVerifyCode
*/
void pickUpOrderByAdmin(String pickUpVerifyCode);
void pickUpOrderByAdmin(Long userId, String pickUpVerifyCode);
/**
*

View File

@ -36,6 +36,7 @@ import cn.iocoder.yudao.module.trade.controller.app.order.vo.item.AppTradeOrderI
import cn.iocoder.yudao.module.trade.convert.order.TradeOrderConvert;
import cn.iocoder.yudao.module.trade.dal.dataobject.cart.CartDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryExpressDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.delivery.DeliveryPickUpStoreDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderDO;
import cn.iocoder.yudao.module.trade.dal.dataobject.order.TradeOrderItemDO;
import cn.iocoder.yudao.module.trade.dal.mysql.order.TradeOrderItemMapper;
@ -48,6 +49,7 @@ import cn.iocoder.yudao.module.trade.framework.order.core.annotations.TradeOrder
import cn.iocoder.yudao.module.trade.framework.order.core.utils.TradeOrderLogUtils;
import cn.iocoder.yudao.module.trade.service.cart.CartService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryExpressService;
import cn.iocoder.yudao.module.trade.service.delivery.DeliveryPickUpStoreService;
import cn.iocoder.yudao.module.trade.service.message.TradeMessageService;
import cn.iocoder.yudao.module.trade.service.message.bo.TradeOrderMessageWhenDeliveryOrderReqBO;
import cn.iocoder.yudao.module.trade.service.order.handler.TradeOrderHandler;
@ -104,6 +106,8 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
private DeliveryExpressService deliveryExpressService;
@Resource
private TradeMessageService tradeMessageService;
@Resource
private DeliveryPickUpStoreService pickUpStoreService;
@Resource
private PayOrderApi payOrderApi;
@ -718,14 +722,14 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
@Override
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_PICK_UP_RECEIVE)
public void pickUpOrderByAdmin(Long id) {
getSelf().pickUpOrder(tradeOrderMapper.selectById(id));
public void pickUpOrderByAdmin(Long userId, Long id) {
getSelf().pickUpOrder(userId, tradeOrderMapper.selectById(id));
}
@Override
@TradeOrderLog(operateType = TradeOrderOperateTypeEnum.ADMIN_PICK_UP_RECEIVE)
public void pickUpOrderByAdmin(String pickUpVerifyCode) {
getSelf().pickUpOrder(tradeOrderMapper.selectOneByPickUpVerifyCode(pickUpVerifyCode));
public void pickUpOrderByAdmin(Long userId, String pickUpVerifyCode) {
getSelf().pickUpOrder(userId, tradeOrderMapper.selectOneByPickUpVerifyCode(pickUpVerifyCode));
}
@Override
@ -734,13 +738,19 @@ public class TradeOrderUpdateServiceImpl implements TradeOrderUpdateService {
}
@Transactional(rollbackFor = Exception.class)
public void pickUpOrder(TradeOrderDO order) {
public void pickUpOrder(Long userId, TradeOrderDO order) {
if (order == null) {
throw exception(ORDER_NOT_FOUND);
}
if (ObjUtil.notEqual(DeliveryTypeEnum.PICK_UP.getType(), order.getDeliveryType())) {
throw exception(ORDER_RECEIVE_FAIL_DELIVERY_TYPE_NOT_PICK_UP);
}
DeliveryPickUpStoreDO deliveryPickUpStore = pickUpStoreService.getDeliveryPickUpStore(order.getPickUpStoreId());
if (deliveryPickUpStore == null
|| !CollUtil.contains(deliveryPickUpStore.getVerifyUserIds(), userId)) {
throw exception(ORDER_PICK_UP_FAIL_NOT_VERIFY_USER);
}
receiveOrder0(order);
}

View File

@ -2,14 +2,18 @@ package cn.iocoder.yudao.module.pay.api.transfer;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
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.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import javax.validation.Valid;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 转账单")
@ -21,4 +25,9 @@ public interface PayTransferApi {
@Operation(summary = "创建转账单")
CommonResult<Long> createTransfer(@Valid @RequestBody PayTransferCreateReqDTO reqDTO);
@GetMapping(PREFIX + "/get")
@Operation(summary = "获得转账单")
@Parameter(name = "id", description = "转账单编号", required = true, example = "1024")
CommonResult<PayTransferRespDTO> getTransfer(@RequestParam("id") Long id);
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.pay.api.transfer.dto;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
import lombok.Data;
@Data
public class PayTransferRespDTO {
/**
*
*/
private Long id;
/**
*
*/
private String no;
/**
*
*/
private Integer price;
/**
*
*
* {@link PayTransferStatusEnum}
*/
private Integer status;
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.pay.api.wallet;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
import cn.iocoder.yudao.module.pay.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 钱包")
public interface PayWalletApi {
String PREFIX = ApiConstants.PREFIX + "/wallet";
@PostMapping(PREFIX + "/add-balance")
@Operation(summary = "添加钱包余额")
CommonResult<Boolean> addWalletBalance(@Valid @RequestBody PayWalletAddBalanceReqDTO reqDTO);
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.pay.api.wallet.dto;
import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* Request DTO
*
* @author
*/
@Data
public class PayWalletAddBalanceReqDTO {
/**
*
*
* MemberUserDO id AdminUserDO id
*/
@NotNull(message = "用户编号不能为空")
private Long userId;
/**
*
*
* {@link UserTypeEnum}
*/
@NotNull(message = "用户类型不能为空")
private Integer userType;
/**
*
*/
@NotNull(message = "关联业务分类不能为空")
private Integer bizType;
/**
*
*/
@NotNull(message = "关联业务编号不能为空")
private String bizId;
/**
*
*
*
*/
@NotNull(message = "交易金额不能为空")
private Integer price;
}

View File

@ -19,7 +19,8 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
RECHARGE_REFUND(2, "充值退款"),
PAYMENT(3, "支付"),
PAYMENT_REFUND(4, "支付退款"),
UPDATE_BALANCE(5, "更新余额");
UPDATE_BALANCE(5, "更新余额"),
BROKERAGE_WITHDRAW(6, "分佣提现");
/**
*
@ -36,4 +37,9 @@ public enum PayWalletBizTypeEnum implements IntArrayValuable {
public int[] array() {
return ARRAYS;
}
public static PayWalletBizTypeEnum valueOf(Integer type) {
return Arrays.stream(values()).filter(item -> item.getType().equals(type)).findFirst().orElse(null);
}
}

View File

@ -1,7 +1,10 @@
package cn.iocoder.yudao.module.pay.api.transfer;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
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.transfer.PayTransferDO;
import cn.iocoder.yudao.module.pay.service.transfer.PayTransferService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@ -27,4 +30,10 @@ public class PayTransferApiImpl implements PayTransferApi {
return success(payTransferService.createTransfer(reqDTO));
}
@Override
public CommonResult<PayTransferRespDTO> getTransfer(Long id) {
PayTransferDO transfer = payTransferService.getTransfer(id);
return success(BeanUtils.toBean(transfer, PayTransferRespDTO.class));
}
}

View File

@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.pay.api.wallet;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pay.api.wallet.dto.PayWalletAddBalanceReqDTO;
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;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.*;
/**
* API
*
* @author jason
*/
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
public class PayWalletApiImpl implements PayWalletApi {
@Resource
private PayWalletService payWalletService;
@Override
public CommonResult<Boolean> addWalletBalance(PayWalletAddBalanceReqDTO reqDTO) {
// 创建或获取钱包
PayWalletDO wallet = payWalletService.getOrCreateWallet(reqDTO.getUserId(), reqDTO.getUserType());
Assert.notNull(wallet, "钱包({}/{})不存在", reqDTO.getUserId(), reqDTO.getUserType());
// 增加余额
PayWalletBizTypeEnum bizType = PayWalletBizTypeEnum.valueOf(reqDTO.getBizType());
payWalletService.addWalletBalance(wallet.getId(), reqDTO.getBizId(), bizType, reqDTO.getPrice());
return success(true);
}
}

View File

@ -1,18 +1,16 @@
package cn.iocoder.yudao.module.pay.controller.admin.app.vo;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.*;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.*;
/**
* Base VO VO 使
* VO Swagger
*/
* Base VO VO 使
* VO Swagger
*/
@Data
public class PayAppBaseVO {
@ -42,4 +40,8 @@ public class PayAppBaseVO {
@URL(message = "退款结果的回调地址必须为 URL 格式")
private String refundNotifyUrl;
@Schema(description = "转账结果的回调地址", example = "http://127.0.0.1:48080/transfer-callback")
@URL(message = "转账结果的回调地址必须为 URL 格式")
private String transferNotifyUrl;
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskDetailRespVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskPageReqVO;
import cn.iocoder.yudao.module.pay.controller.admin.notify.vo.PayNotifyTaskRespVO;
@ -18,6 +19,7 @@ import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
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;
@ -26,9 +28,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.validation.Valid;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
@ -49,6 +51,8 @@ public class PayNotifyController {
@Resource
private PayRefundService refundService;
@Resource
private PayTransferService payTransferService;
@Resource
private PayNotifyService notifyService;
@Resource
private PayAppService appService;
@ -65,7 +69,7 @@ public class PayNotifyController {
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
log.error("[notifyOrder][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
@ -79,13 +83,13 @@ public class PayNotifyController {
@Operation(summary = "支付渠道的统一【退款】回调")
@PermitAll
public String notifyRefund(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
log.error("[notifyRefund][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
@ -95,6 +99,26 @@ public class PayNotifyController {
return "success";
}
@PostMapping(value = "/transfer/{channelId}")
@Operation(summary = "支付渠道的统一【转账】回调")
@PermitAll
public String notifyTransfer(@PathVariable("channelId") Long channelId,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
log.info("[notifyTransfer][channelId({}) 回调数据({}/{})]", channelId, params, body);
// 1. 校验支付渠道是否存在
PayClient payClient = channelService.getPayClient(channelId);
if (payClient == null) {
log.error("[notifyTransfer][渠道编号({}) 找不到对应的支付客户端]", channelId);
throw exception(CHANNEL_NOT_FOUND);
}
// 2. 解析通知数据
PayTransferRespDTO notify = payClient.parseTransferNotify(params, body);
payTransferService.notifyTransfer(channelId, notify);
return "success";
}
@GetMapping("/get-detail")
@Operation(summary = "获得回调通知的明细")
@Parameter(name = "id", description = "编号", required = true, example = "1024")

View File

@ -24,7 +24,8 @@ public interface PayTransferConvert {
PayTransferCreateReqDTO convert(PayDemoTransferCreateReqVO vo);
PayTransferRespVO convert(PayTransferDO bean);
PayTransferRespVO convert(PayTransferDO bean);
PageResult<PayTransferPageItemRespVO> convertPage(PageResult<PayTransferDO> pageResult);
}

View File

@ -23,10 +23,6 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
PayTransferDO::getMerchantTransferId, merchantTransferId);
}
default PayTransferDO selectByNo(String no){
return selectOne(PayTransferDO::getNo, no);
}
default PageResult<PayTransferDO> selectPage(PayTransferPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<PayTransferDO>()
.eqIfPresent(PayTransferDO::getNo, reqVO.getNo())
@ -41,9 +37,15 @@ public interface PayTransferMapper extends BaseMapperX<PayTransferDO> {
.orderByDesc(PayTransferDO::getId));
}
default List<PayTransferDO> selectListByStatus(Integer status){
default List<PayTransferDO> selectListByStatus(Integer status) {
return selectList(PayTransferDO::getStatus, status);
}
default PayTransferDO selectByAppIdAndNo(Long appId, String no) {
return selectOne(PayTransferDO::getAppId, appId,
PayTransferDO::getNo, no);
}
}

View File

@ -68,6 +68,19 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
return update(null, lambdaUpdateWrapper);
}
/**
*
*
* @param id id
* @param price
*/
default void updateWhenAdd(Long id, Integer price) {
LambdaUpdateWrapper<PayWalletDO> lambdaUpdateWrapper = new LambdaUpdateWrapper<PayWalletDO>()
.setSql(" balance = balance + " + price)
.eq(PayWalletDO::getId, id);
update(null, lambdaUpdateWrapper);
}
/**
*
*
@ -114,7 +127,6 @@ public interface PayWalletMapper extends BaseMapperX<PayWalletDO> {
return update(null, lambdaUpdateWrapper);
}
}

View File

@ -39,6 +39,15 @@ public class PayProperties {
@URL(message = "支付回调地址的格式必须是 URL")
private String refundNotifyUrl;
/**
*
*
* PayNotifyController notifyTransfer URL
*
* => yudao-module-pay transferNotifyUrl => PayAppDO.transferNotifyUrl
*/
private String transferNotifyUrl;
/**
* no
*/

View File

@ -177,6 +177,11 @@ public class WalletPayClient extends AbstractPayClient<NonePayClientConfig> {
throw new IllegalStateException(String.format("支付退款单[%s] 状态不正确", outRefundNo));
}
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
throw new UnsupportedOperationException("未实现");
}
@Override
public PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现");

View File

@ -1,6 +1,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.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;
@ -54,4 +55,13 @@ public interface PayTransferService {
* @return
*/
int syncTransfer();
/**
*
*
* @param channelId
* @param notify
*/
void notifyTransfer(Long channelId, PayTransferRespDTO notify);
}

View File

@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.pay.dal.mysql.transfer.PayTransferMapper;
import cn.iocoder.yudao.module.pay.dal.redis.no.PayNoRedisDAO;
import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
import cn.iocoder.yudao.module.pay.enums.transfer.PayTransferStatusEnum;
import cn.iocoder.yudao.module.pay.framework.pay.config.PayProperties;
import cn.iocoder.yudao.module.pay.service.app.PayAppService;
import cn.iocoder.yudao.module.pay.service.channel.PayChannelService;
import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
@ -50,6 +51,9 @@ public class PayTransferServiceImpl implements PayTransferService {
private static final String TRANSFER_NO_PREFIX = "T";
@Resource
private PayProperties payProperties;
@Resource
private PayTransferMapper transferMapper;
@Resource
@ -96,13 +100,15 @@ public class PayTransferServiceImpl implements PayTransferService {
transfer = INSTANCE.convert(reqDTO)
.setChannelId(channel.getId())
.setNo(no).setStatus(WAITING.getStatus())
.setNotifyUrl(payApp.getTransferNotifyUrl());
.setNotifyUrl(payApp.getTransferNotifyUrl())
.setAppId(channel.getAppId());
transferMapper.insert(transfer);
}
try {
// 3. 调用三方渠道发起转账
PayTransferUnifiedReqDTO transferUnifiedReq = INSTANCE.convert2(transfer)
.setOutTransferNo(transfer.getNo());
transferUnifiedReq.setNotifyUrl(genChannelTransferNotifyUrl(channel));
PayTransferRespDTO unifiedTransferResp = client.unifiedTransfer(transferUnifiedReq);
// 4. 通知转账结果
getSelf().notifyTransfer(channel, unifiedTransferResp);
@ -116,6 +122,16 @@ public class PayTransferServiceImpl implements PayTransferService {
return transfer.getId();
}
/**
*
*
* @param channel
* @return + "/" + channel id
*/
private String genChannelTransferNotifyUrl(PayChannelDO channel) {
return payProperties.getTransferNotifyUrl() + "/" + channel.getId();
}
private PayTransferDO validateTransferCanCreate(PayTransferCreateReqDTO dto, Long appId) {
PayTransferDO transfer = transferMapper.selectByAppIdAndMerchantTransferId(appId, dto.getMerchantTransferId());
if (transfer != null) {
@ -154,7 +170,7 @@ public class PayTransferServiceImpl implements PayTransferService {
private void notifyTransferInProgress(PayChannelDO channel, PayTransferRespDTO notify) {
// 1.校验
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
@ -172,16 +188,12 @@ public class PayTransferServiceImpl implements PayTransferService {
throw exception(PAY_TRANSFER_STATUS_IS_NOT_WAITING);
}
log.info("[notifyTransferInProgress][transfer({}) 更新为转账进行中状态]", transfer.getId());
// 3. 插入转账通知记录
notifyService.createPayNotifyTask(PayNotifyTypeEnum.TRANSFER.getType(),
transfer.getId());
}
private void notifyTransferSuccess(PayChannelDO channel, PayTransferRespDTO notify) {
// 1.校验
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
@ -210,7 +222,7 @@ public class PayTransferServiceImpl implements PayTransferService {
private void notifyTransferClosed(PayChannelDO channel, PayTransferRespDTO notify) {
// 1.校验
PayTransferDO transfer = transferMapper.selectByNo(notify.getOutTransferNo());
PayTransferDO transfer = transferMapper.selectByAppIdAndNo(channel.getAppId(), notify.getOutTransferNo());
if (transfer == null) {
throw exception(PAY_TRANSFER_NOT_FOUND);
}
@ -283,7 +295,7 @@ public class PayTransferServiceImpl implements PayTransferService {
}
}
private void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
public void notifyTransfer(Long channelId, PayTransferRespDTO notify) {
// 校验渠道是否有效
PayChannelDO channel = channelService.validPayChannel(channelId);
// 通知转账结果给对应的业务

View File

@ -187,7 +187,7 @@ public class PayWalletServiceImpl implements PayWalletService {
// 2. 加锁,更新钱包余额(目的:避免钱包流水的并发更新时,余额变化不连贯)
return lockRedisDAO.lock(walletId, UPDATE_TIMEOUT_MILLIS, () -> {
// 2. 更新钱包金额
// 3. 更新钱包金额
switch (bizType) {
case PAYMENT_REFUND: { // 退款更新
walletMapper.updateWhenConsumptionRefund(payWallet.getId(), price);
@ -198,15 +198,15 @@ public class PayWalletServiceImpl implements PayWalletService {
break;
}
case UPDATE_BALANCE: // 更新余额
walletMapper.updateWhenRecharge(payWallet.getId(), price);
case BROKERAGE_WITHDRAW: // 分佣提现
walletMapper.updateWhenAdd(payWallet.getId(), price);
break;
default: {
// TODO 其它类型待实现
throw new UnsupportedOperationException("待实现");
throw new UnsupportedOperationException("待实现:" + bizType);
}
}
// 3. 生成钱包流水
// 4. 生成钱包流水
WalletTransactionCreateReqBO transactionCreateReqBO = new WalletTransactionCreateReqBO()
.setWalletId(payWallet.getId()).setPrice(price).setBalance(payWallet.getBalance() + price)
.setBizId(bizId).setBizType(bizType.getType()).setTitle(bizType.getDescription());

View File

@ -109,4 +109,5 @@ yudao:
pay:
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址
demo: true # 关闭演示模式

View File

@ -137,4 +137,5 @@ yudao:
enable: false
pay:
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
transfer-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/transfer # 支付渠道的【转账】回调地址

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.pay.config;
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
import cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
/**

View File

@ -79,6 +79,8 @@ public interface PayClient {
*/
PayRefundRespDTO getRefund(String outTradeNo, String outRefundNo);
// ============ 转账相关 ==========
/**
*
*
@ -95,4 +97,14 @@ public interface PayClient {
* @return
*/
PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type);
/**
* transfer
*
* @param params HTTP content type application/x-www-form-urlencoded
* @param body HTTP request body
* @return
*/
PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body);
}

View File

@ -2,7 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import javax.validation.Validator;
import jakarta.validation.Validator;
/**
*

View File

@ -5,9 +5,9 @@ import lombok.Data;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.Map;

View File

@ -7,9 +7,9 @@ import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.hibernate.validator.constraints.URL;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
/**
* 退 Request DTO

View File

@ -5,14 +5,15 @@ import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.URL;
import java.util.Map;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.Alipay;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.WxPay;
import static cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum.*;
/**
* Request DTO
@ -76,4 +77,12 @@ public class PayTransferUnifiedReqDTO {
*
*/
private Map<String, String> channelExtras;
/**
* notify
*/
@NotEmpty(message = "转账结果的回调地址不能为空")
@URL(message = "转账结果的 notify 回调地址必须是 URL 格式")
private String notifyUrl;
}

View File

@ -0,0 +1,129 @@
package cn.iocoder.yudao.framework.pay.core.client.dto.transfer;
import com.github.binarywang.wxpay.bean.notify.OriginNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayBaseNotifyV3Result;
import com.google.gson.annotations.SerializedName;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
// TODO @luchi这个可以复用 wxjava 里的类么?
@NoArgsConstructor
public class WxPayTransferPartnerNotifyV3Result implements Serializable, WxPayBaseNotifyV3Result<WxPayTransferPartnerNotifyV3Result.TransferNotifyResult> {
private static final long serialVersionUID = -1L;
/**
*
*/
private OriginNotifyResponse rawData;
/**
*
*/
private TransferNotifyResult result;
@Override
public void setRawData(OriginNotifyResponse rawData) {
this.rawData = rawData;
}
@Override
public void setResult(TransferNotifyResult data) {
this.result = data;
}
public TransferNotifyResult getResult() {
return result;
}
public OriginNotifyResponse getRawData() {
return rawData;
}
@Data
@NoArgsConstructor
public static class TransferNotifyResult implements Serializable {
private static final long serialVersionUID = 1L;
/*********************** ********************
/**
*
*/
@SerializedName(value = "out_batch_no")
protected String outBatchNo;
/**
*
*/
@SerializedName(value = "batch_id")
protected String batchId;
/**
*
*/
@SerializedName(value = "batch_status")
protected String batchStatus;
/**
*
*/
@SerializedName(value = "total_num")
protected Integer totalNum;
/**
*
*/
@SerializedName(value = "total_amount")
protected Integer totalAmount;
/**
*
*/
@SerializedName(value = "update_time")
private String updateTime;
/*********************** FINISHED ********************
/**
*
*/
@SerializedName(value = "success_amount")
protected Integer successAmount;
/**
*
*/
@SerializedName(value = "success_num")
protected Integer successNum;
/**
*
*/
@SerializedName(value = "fail_amount")
protected Integer failAmount;
/**
*
*/
@SerializedName(value = "fail_num")
protected Integer failNum;
/*********************** CLOSED ********************
/**
*
*/
@SerializedName(value = "mchid")
protected String mchId;
/**
*
*/
@SerializedName(value = "close_reason")
protected String closeReason;
}
}

View File

@ -219,6 +219,22 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
}
}
@Override
public final PayTransferRespDTO parseTransferNotify(Map<String, String> params, String body) {
try {
return doParseTransferNotify(params, body);
} catch (ServiceException ex) { // 业务异常,都是实现类已经翻译,所以直接抛出即可
throw ex;
} catch (Throwable ex) {
log.error("[doParseTransferNotify][客户端({}) params({}) body({}) 解析失败]",
getId(), params, body, ex);
throw buildPayException(ex);
}
}
protected abstract PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body)
throws Throwable;
@Override
public final PayTransferRespDTO getTransfer(String outTradeNo, PayTransferTypeEnum type) {
try {

View File

@ -3,7 +3,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import lombok.Data;
import javax.validation.Validator;
import jakarta.validation.Validator;
/**
* PayClientConfig

View File

@ -325,6 +325,12 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
}
}
// TODO @chihuo这里是不是也要实现支付宝的。
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
throw new UnsupportedOperationException("未实现");
}
// ========== 各种工具方法 ==========
protected String formatAmount(Integer amount) {

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException;
@ -56,4 +57,5 @@ public class AlipayAppPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@ -5,6 +5,7 @@ import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException;
@ -82,4 +83,5 @@ public class AlipayBarPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, "",
reqDTO.getOutTradeNo(), response);
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.Method;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import com.alipay.api.AlipayApiException;
@ -66,4 +67,5 @@ public class AlipayPcPayClient extends AbstractAlipayPayClient {
return PayOrderRespDTO.waitingOf(displayMode, response.getBody(),
reqDTO.getOutTradeNo(), response);
}
}

View File

@ -57,6 +57,11 @@ public class MockPayClient extends AbstractPayClient<NonePayClientConfig> {
outRefundNo, MOCK_RESP_SUCCESS_DATA);
}
@Override
protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws Throwable {
throw new UnsupportedOperationException("未实现");
}
@Override
protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body) {
throw new UnsupportedOperationException("模拟支付无退款回调");

View File

@ -14,6 +14,7 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
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.client.dto.transfer.WxPayTransferPartnerNotifyV3Result;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
@ -23,6 +24,10 @@ import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesRequest;
import com.github.binarywang.wxpay.bean.transfer.QueryTransferBatchesResult;
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesRequest;
import com.github.binarywang.wxpay.bean.transfer.TransferBatchesResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
@ -31,6 +36,8 @@ import lombok.extern.slf4j.Slf4j;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -349,6 +356,33 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
return PayRefundRespDTO.failureOf(result.getOutRefundNo(), response);
}
@Override
public PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body) throws WxPayException {
switch (config.getApiVersion()) {
case API_VERSION_V3:
return parseTransferNotifyV3(body);
case API_VERSION_V2:
throw new UnsupportedOperationException("V2 版本暂不支持,建议使用 V3 版本");
default:
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
}
}
private PayTransferRespDTO parseTransferNotifyV3(String body) throws WxPayException {
// 1. 解析回调
// TODO @luchi这个可以复用 wxjava 里的类么?
WxPayTransferPartnerNotifyV3Result response = client.baseParseOrderNotifyV3Result(body, null, WxPayTransferPartnerNotifyV3Result.class, WxPayTransferPartnerNotifyV3Result.TransferNotifyResult.class);
WxPayTransferPartnerNotifyV3Result.TransferNotifyResult result = response.getResult();
// 2. 构建结果
if (Objects.equals("FINISHED", result.getBatchStatus())) {
if (result.getFailNum() <= 0) {
return PayTransferRespDTO.successOf(result.getBatchId(), parseDateV3(result.getUpdateTime()),
result.getOutBatchNo(), response);
}
}
return PayTransferRespDTO.closedOf(result.getBatchStatus(), result.getCloseReason(), result.getOutBatchNo(), response);
}
@Override
protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo) throws WxPayException {
try {
@ -427,13 +461,54 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
}
@Override
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) {
throw new UnsupportedOperationException("待实现");
protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws WxPayException {
// 1. 构建 TransferBatchesRequest 请求
List<TransferBatchesRequest.TransferDetail> 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 doGetTransfer(String outTradeNo, PayTransferTypeEnum type) {
throw new UnsupportedOperationException("待实现");
protected PayTransferRespDTO doGetTransfer(String outTradeNo, PayTransferTypeEnum type) throws WxPayException {
QueryTransferBatchesRequest request = QueryTransferBatchesRequest.newBuilder()
.outBatchNo(outTradeNo).needQueryDetail(true).offset(0).limit(20).detailStatus("ALL")
.build();
QueryTransferBatchesResult response = client.getTransferService().transferBatchesOutBatchNo(request);
QueryTransferBatchesResult.TransferBatch transferBatch = response.getTransferBatch();
if (Objects.equals("FINISHED", transferBatch.getBatchStatus())) {
// 明细中全部成功则成功,任一失败则失败
if (response.getTransferDetailList().stream().allMatch(detail -> 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);
}
}
if (Objects.equals("CLOSED", transferBatch.getBatchStatus())) {
return PayTransferRespDTO.closedOf(transferBatch.getBatchStatus(), transferBatch.getCloseReason(),
transferBatch.getOutBatchNo(), response);
}
return PayTransferRespDTO.dealingOf(transferBatch.getBatchId(), transferBatch.getOutBatchNo(), response);
}
// ========== 各种工具方法 ==========

View File

@ -1,8 +1,8 @@
package cn.iocoder.yudao.framework.pay.core.enums.channel;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.NonePayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
import cn.iocoder.yudao.framework.pay.core.client.impl.weixin.WxPayClientConfig;
import lombok.AllArgsConstructor;

View File

@ -0,0 +1,118 @@
package com.github.binarywang.wxpay.bean.transfer;
import com.github.binarywang.wxpay.v3.SpecEncrypt;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
/**
* API
*
* @author zhongjun
* created on 2022/6/17
**/
@Data
@Builder(builderMethodName = "newBuilder")
@NoArgsConstructor
@AllArgsConstructor
public class TransferBatchesRequest implements Serializable {
private static final long serialVersionUID = -2175582517588397426L;
/**
* appid
*/
@SerializedName("appid")
private String appid;
/**
*
*/
@SerializedName("out_batch_no")
private String outBatchNo;
/**
*
*/
@SerializedName("batch_name")
private String batchName;
/**
*
*/
@SerializedName("batch_remark")
private String batchRemark;
/**
*
*/
@SerializedName("total_amount")
private Integer totalAmount;
/**
*
*/
@SerializedName("total_num")
private Integer totalNum;
/**
*
*/
@SpecEncrypt
@SerializedName("transfer_detail_list")
private List<TransferDetail> transferDetailList;
/**
* ID
*/
@SerializedName("transfer_scene_id")
private String transferSceneId;
/**
* url访urlhttps
*/
@SerializedName("notify_url")
private String notifyUrl;
@Data
@Builder(builderMethodName = "newBuilder")
@AllArgsConstructor
@NoArgsConstructor
public static class TransferDetail {
/**
*
*/
@SerializedName("out_detail_no")
private String outDetailNo;
/**
*
*/
@SerializedName("transfer_amount")
private Integer transferAmount;
/**
*
*/
@SerializedName("transfer_remark")
private String transferRemark;
/**
*
*/
@SerializedName("openid")
private String openid;
/**
*
*/
@SpecEncrypt
@SerializedName("user_name")
private String userName;
}
}

View File

@ -1 +1 @@
cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration
cn.iocoder.yudao.framework.pay.config.YudaoPayAutoConfiguration

View File

@ -35,4 +35,7 @@ public class UserPageReqVO extends PageParam {
@Schema(description = "部门编号,同时筛选子部门", example = "1024")
private Long deptId;
@Schema(description = "角色编号", example = "1024")
private Long roleId;
}

View File

@ -25,13 +25,14 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
return selectOne(AdminUserDO::getMobile, mobile);
}
default PageResult<AdminUserDO> selectPage(UserPageReqVO reqVO, Collection<Long> deptIds) {
default PageResult<AdminUserDO> selectPage(UserPageReqVO reqVO, Collection<Long> deptIds, Collection<Long> userIds) {
return selectPage(reqVO, new LambdaQueryWrapperX<AdminUserDO>()
.likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername())
.likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile())
.eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus())
.betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime())
.inIfPresent(AdminUserDO::getDeptId, deptIds)
.inIfPresent(AdminUserDO::getId, userIds)
.orderByDesc(AdminUserDO::getId));
}

View File

@ -46,8 +46,7 @@ import java.time.LocalDateTime;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.system.enums.LogRecordConstants.*;
@ -272,7 +271,12 @@ public class AdminUserServiceImpl implements AdminUserService {
@Override
public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()));
// 如果有角色编号,查询角色对应的用户编号
Set<Long> userIds = reqVO.getRoleId() != null ?
permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId())) : null;
// 分页查询
return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
}
@Override
@ -349,7 +353,7 @@ public class AdminUserServiceImpl implements AdminUserService {
}
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
Long deptId, Set<Long> postIds) {
Long deptId, Set<Long> postIds) {
// 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
return DataPermissionUtils.executeIgnore(() -> {
// 校验用户存在