1、产品管理、定价规则接口完善;2、支付测试;

This commit is contained in:
gaoqr
2025-08-12 16:44:07 +08:00
parent 280a3392aa
commit c985272d58
29 changed files with 563 additions and 244 deletions
@@ -7,6 +7,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.product.Prod
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.enums.products.ProductStatusEnum;
import com.cf.imes.module.system.service.funds.products.ProductsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -30,36 +31,36 @@ public class ProductsController {
@Resource
private ProductsService productsService;
@PostMapping("")
@PutMapping("")
@Operation(summary = "新建产品")
// @PreAuthorize("@ss.hasPermission('base:products:create')")
@PreAuthorize("@ss.hasPermission('base:products:create')")
public CommonResult<Long> createProducts(@RequestParam("productName") String productName) {
return success(productsService.create(productName));
}
@PutMapping("")
@PostMapping("")
@Operation(summary = "更新产品")
// @PreAuthorize("@ss.hasPermission('base:products:update')")
@PreAuthorize("@ss.hasPermission('base:products:update')")
public CommonResult<Boolean> updateProducts(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
ProductsDO productsDO = BeanUtils.toBean(updateReqVO, ProductsDO.class);
productsService.update(productsDO);
return success(true);
}
@PutMapping("/freeze")
@PostMapping("/freeze")
@Operation(summary = "下架产品")
// @PreAuthorize("@ss.hasPermission('base:products:freeze')")
@PreAuthorize("@ss.hasPermission('base:products:freeze')")
public CommonResult<Boolean> freezeProducts(@Valid Long id) {
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(3);
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());
productsService.update(productsDO);
return success(true);
}
@PutMapping("/melt")
@PostMapping("/melt")
@Operation(summary = "上架产品")
@PreAuthorize("@ss.hasPermission('base:products:melt')")
public CommonResult<Boolean> meltProducts(@Valid Long id) {
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(2);
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus());
productsService.update(productsDO);
return success(true);
}
@@ -75,7 +76,7 @@ public class ProductsController {
}
@GetMapping("/{id}")
@Operation(summary = "获得产品")
@Operation(summary = "查询产品")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('base:products:query')")
public CommonResult<ProductRespVO> getProducts(@PathVariable("id") Long id) {
@@ -84,7 +85,7 @@ public class ProductsController {
}
@GetMapping("/page")
@Operation(summary = "获得产品分页")
@Operation(summary = "查询产品分页")
@PreAuthorize("@ss.hasPermission('base:products:query')")
public CommonResult<PageResult<ProductRespVO>> getProductsPage(@Valid ProductPageReqVO pageReqVO) {
PageResult<ProductsDO> pageResult = productsService.getPage(pageReqVO);
@@ -30,14 +30,14 @@ public class ProductsDetailController {
@Resource
private ProductsDetailService productDetailsService;
@PostMapping("")
@PutMapping("")
@Operation(summary = "新建产品明细")
@PreAuthorize("@ss.hasPermission('base:productDetails:create')")
public CommonResult<Long> createProducts(@Valid @RequestBody ProductDetailsSaveReqVO createReqVO) {
return success(productDetailsService.create(createReqVO));
}
@PutMapping("")
@PostMapping("")
@Operation(summary = "更新产品明细")
@PreAuthorize("@ss.hasPermission('base:productDetails:update')")
public CommonResult<Boolean> updateProducts(@Valid @RequestBody ProductDetailsSaveReqVO updateReqVO) {
@@ -55,7 +55,7 @@ public class ProductsDetailController {
}
@GetMapping("/{id}")
@Operation(summary = "获得产品明细")
@Operation(summary = "查询产品明细")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('base:productDetails:query')")
public CommonResult<List<ProductsDetailDO>> getProducts(@PathVariable("id") Long id) {
@@ -64,7 +64,7 @@ public class ProductsDetailController {
}
@GetMapping("/page")
@Operation(summary = "获得产品明细分页")
@Operation(summary = "查询产品明细分页")
@PreAuthorize("@ss.hasPermission('base:productDetails:query')")
public CommonResult<PageResult<ProductDetailsRespVO>> getProductsPage(@Valid ProductDetailsPageReqVO pageReqVO) {
PageResult<ProductsDetailDO> pageResult = productDetailsService.getPage(pageReqVO);
@@ -6,11 +6,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 com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品表 products Request VO")
@Data
@@ -24,18 +19,7 @@ public class ProductPageReqVO extends PageParam {
@Schema(description = "产品名称", example = "赵六")
private String productName;
@Schema(description = "产品状态", example = "0待上架 1已上架 2已下架")
@Schema(description = "产品状态", example = "0")
@ProductStatus
private Integer status;
@Schema(description = "软件id", example = "1")
private Long softwareId;
@Schema(description = "操作时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
@Schema(description = "是否删除")
private Integer deleted;
}
@@ -1,5 +1,6 @@
package com.cf.imes.module.system.controller.admin.funds.products.vo.product;
import com.cf.imes.module.system.validation.products.ProductStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -17,6 +18,7 @@ public class ProductSaveReqVO {
private String productName;
@Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED)
@ProductStatus
private Integer status;
@Schema(description = "软件id", requiredMode = Schema.RequiredMode.REQUIRED)
@@ -6,12 +6,8 @@ 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.math.BigDecimal;
import java.time.LocalDateTime;
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品详情表 productDetails Request VO")
@Data
@@ -23,19 +19,19 @@ public class ProductDetailsPageReqVO extends PageParam {
private Long id;
@Schema(description = "产品ID", example = "1")
private String productId;
private Long productId;
@Schema(description = "购买方式", example = "购买方式:0时长;1产量")
@Schema(description = "购买方式", example = "0")
@ProductType
private Integer purchaseType;
@Schema(description = "正常时间", example = "半个月")
@Schema(description = "产品时长", example = "1")
private String duration;
@Schema(description = "正常产量", example = "")
private BigDecimal output;
@Schema(description = "赠送时间", example = "半个月")
@Schema(description = "赠送市场", example = "1")
private String giftDuration;
@Schema(description = "赠送产量", example = "")
@@ -50,14 +46,7 @@ public class ProductDetailsPageReqVO extends PageParam {
@Schema(description = "产品现价", example = "80")
private BigDecimal currentPrice;
@Schema(description = "是否特惠", example = "")
@Schema(description = "是否特惠", example = "0")
private Boolean isDiscounted;
@Schema(description = "操作时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
@Schema(description = "是否删除")
private Integer deleted;
}
@@ -1,6 +1,8 @@
package com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails;
import com.cf.imes.framework.common.validation.NumberValid;
import com.cf.imes.module.system.validation.products.ProductType;
import com.cf.imes.module.system.validation.products.TimeUnitValid;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -18,35 +20,46 @@ public class ProductDetailsSaveReqVO {
@NotNull(message = "产品名称不能为空")
private Long productId;
@Schema(description = "购买方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "购买方式:0时长;1产量")
@Schema(description = "购买方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "购买方式不能为空")
@ProductType
private Integer purchaseType;
@Schema(description = "产品时间,格式:时长值 - 时间单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "1month")
@Schema(description = "产品时间,格式:时长值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String duration;
@Schema(description = "产品时长单位,0:年、1:月、2:日", example = "1")
@TimeUnitValid
private Integer durationUnit;
@Schema(description = "产品产量", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private BigDecimal output;
//
@Schema(description = "赠送时间,格式:时长值 - 时间单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "半个月")
@Schema(description = "赠送时间,格式:时长值 - 时间单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String giftDuration;
@Schema(description = "赠送时长单位,0:年、1:月、2:日", example = "1")
@TimeUnitValid
private Integer giftDurationUnit;
@Schema(description = "赠送产量", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private BigDecimal giftOutput;
@Schema(description = "产品原价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "产品时间不能为空")
@NotNull(message = "产品原价不能为空")
@NumberValid(name = "产品原价")
private BigDecimal originalPrice;
@Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
@NumberValid(name = "折扣", integer = 4, fraction = 2)
private BigDecimal discount;
@Schema(description = "产品现价", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
@NotNull(message = "产品时间不能为空")
@NotNull(message = "产品现价不能为空")
@NumberValid(name = "产品现价")
private BigDecimal currentPrice;
@Schema(description = "是否特惠", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
@Schema(description = "是否特惠", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean isDiscounted;
}
@@ -2,7 +2,8 @@ package com.cf.imes.module.system.controller.admin.pay;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayRechargeOrderReqVO;
import com.cf.imes.module.system.service.pay.PayOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -36,13 +37,20 @@ public class PayController {
@Resource
private PayOrderService payOrderService;
@PostMapping()
@Operation(summary = "创建支付订单")
public CommonResult<PayOrderRespDTO> initiatePay(@RequestBody PayOrderUnifiedReqVO payOrderUnifiedReqVO) throws Throwable {
PayOrderRespDTO payOrderRespDTO = payOrderService.initiatePay(payOrderUnifiedReqVO);
@PostMapping("/recharge")
@Operation(summary = "创建充值支付订单")
public CommonResult<PayOrderRespDTO> initiatePay(@RequestBody PayRechargeOrderReqVO payRechargeOrderReqVO) {
PayOrderRespDTO payOrderRespDTO = payOrderService.rechargePay(payRechargeOrderReqVO);
return success(payOrderRespDTO);
}
@PostMapping("/product")
@Operation(summary = "创建产品支付订单")
public CommonResult<PayOrderRespDTO> initiatePay(@RequestBody PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
payOrderService.productPay(payProductOrderUnifiedReqVO);
return success(null);
}
@GetMapping("/order/{channelCode}/{outTradeNo}")
@Operation(summary = "查询支付订单")
public CommonResult<Boolean> queryPayOrder(@PathVariable("outTradeNo") String outTradeNo, @PathVariable("channelCode") int channelCode) {
@@ -57,7 +65,7 @@ public class PayController {
public String notifyOrder(@PathVariable("channelCode") Integer channelCode,
@RequestParam(required = false) Map<String, String> params,
@RequestBody(required = false) String body) {
log.debug("[notifyOrder][channelCode({}) 回调数据({}/{})]", channelCode, params, body);
log.info("[notifyOrder][channelCode({}) 回调数据({}/{})]", channelCode, params, body);
payOrderService.doOrderNotify(channelCode, params, body);
// 支付接口回调相应必须是"success"
return "success";
@@ -0,0 +1,30 @@
package com.cf.imes.module.system.controller.admin.pay.vo;
import com.cf.imes.module.system.validation.pay.PayProductChannelCodeValid;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 产品支付vo
*
* @author Gqr
* @since 2025/4/15 14:30
*/
@Data
@Valid
public class PayProductOrderUnifiedReqVO {
/**
* 产品定价规则编号
*/
@Schema(description = "产品定价规则编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "产品定价规则编号不能为空")
private Long productDetailsId;
@Schema(description = "支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@PayProductChannelCodeValid
@NotNull(message = "支付渠道编码不能为空")
private Integer channelCode;
}
@@ -0,0 +1,35 @@
package com.cf.imes.module.system.controller.admin.pay.vo;
import com.cf.imes.module.system.validation.pay.PayChannelCodeValid;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
/**
* 充值支付vo
*
* @author Gqr
* @since 2025/4/15 14:30
*/
@Data
@Valid
public class PayRechargeOrderReqVO {
@Schema(description = "组织编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "组织编号不能为空")
private Long organId;
@Schema(description = "支付金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "支付金额不能为空")
@DecimalMin(value = "0", inclusive = false, message = "支付金额必须大于零")
private BigDecimal price;
@Schema(description = "支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@PayChannelCodeValid
@NotNull(message = "支付渠道编码不能为空")
private Integer channelCode;
}
@@ -14,8 +14,8 @@ import java.math.BigDecimal;
*
* @author 晨丰科技
*/
@TableName("products_detail")
@KeySequence("products_detail_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@TableName("products_details")
@KeySequence("products_details_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@@ -45,6 +45,11 @@ public class ProductsDetailDO extends BaseDO {
*/
private String duration;
/**
* 产品时长单位
*/
private Integer durationUnit;
/**
* 正常产量,不加赠送产量
*/
@@ -56,6 +61,11 @@ public class ProductsDetailDO extends BaseDO {
*/
private String giftDuration;
/**
* 赠送时间单位
*/
private Integer giftDurationUnit;
/**
* 赠送产量
*/
@@ -34,13 +34,14 @@ public interface ProductsDetailMapper extends BaseMapperX<ProductsDetailDO> {
// 时长查重,添加productId为查询条件
default int selectCountByDuration(String duration, Long productId) {
default int selectCountByDuration(Long productId, Long productDetailId, String duration, Integer durationUnit) {
LambdaQueryWrapperX<ProductsDetailDO> queryWrapper = new LambdaQueryWrapperX<>();
queryWrapper.eq(ProductsDetailDO::getProductId, productId)
.eq(ProductsDetailDO::getDeleted, false)
.eq(ProductsDetailDO::getDuration, duration);
.eq(ProductsDetailDO::getDeleted, false)
.eq(ProductsDetailDO::getDuration, duration)
.eq(ProductsDetailDO::getDurationUnit, durationUnit)
.neIfPresent(ProductsDetailDO::getId, productDetailId);
return Math.toIntExact(selectCount(queryWrapper));
}
@@ -0,0 +1,53 @@
package com.cf.imes.module.system.enums.pay;
import com.baomidou.mybatisplus.annotation.EnumValue;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 产品支付渠道编码
*
* @author Gqr
* @since 2024/7/5 9:25
*/
@Getter
@AllArgsConstructor
public enum PayProductChannelCodeEnum {
/**
* 支付宝
*/
ALIPAY_PC(0),
/**
* 微信
*/
WECHAT_NATIVE(1),
/**
* 账户余额
*/
BALANCE_ONLY(2),
/**
* 账户余额+支付宝
*/
BALANCE_AND_ALIPAY(3),
/**
* 账户余额+微信
*/
BALANCE_AND_WECHAT(4);
@EnumValue
private final Integer code;
@JsonCreator
public static PayProductChannelCodeEnum fromType(Integer type) {
for (PayProductChannelCodeEnum value : values()) {
if (value.getCode().equals(type)) {
return value;
}
}
return null;
}
}
@@ -6,6 +6,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetai
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import jakarta.validation.Valid;
import java.util.List;
/**
@@ -51,4 +52,18 @@ public interface ProductsDetailService {
*/
PageResult<ProductsDetailDO> getPage(ProductDetailsPageReqVO pageReqVO);
/**
* 校验产品明细是否存在
*
* @param id
* @return
*/
ProductsDetailDO validateProductDetailsExists(Long id);
/**
* 校验产品是否存在和时效性
*
* @param productId
*/
void validateProductExist(Long productId);
}
@@ -1,7 +1,7 @@
package com.cf.imes.module.system.service.funds.products;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.nacos.common.utils.StringUtils;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
@@ -12,15 +12,14 @@ import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsDetailMapper;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper;
import com.cf.imes.module.system.enums.products.DurationUnitEnum;
import com.cf.imes.module.system.enums.products.ProductStatusEnum;
import com.cf.imes.module.system.enums.products.ProductsPurchaseTypeEnum;
import com.cf.imes.module.system.util.SnowflakeIdWorker3rd;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.List;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -41,57 +40,37 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
@Resource
private ProductsMapper productsMapper;
@Resource
private SnowflakeIdWorker3rd idWorker;
@Override
public Long create(ProductDetailsSaveReqVO createReqVO) {
// 校验是否有这个产品
validateProductExist(createReqVO.getProductId());
if (createReqVO.getPurchaseType().equals(ProductsPurchaseTypeEnum.DURATION.getStatus())) {
String duration = createReqVO.getDuration();
AssertUtils.notEmpty(duration,PRODUCTS_DURATION_NOT_NULL);
if (ProductsPurchaseTypeEnum.DURATION.getStatus().equals(createReqVO.getPurchaseType())) {
// 校验时长
duration = checkDuration(duration);
String giftDuration = null;
if (createReqVO.getGiftDuration() != null) {
giftDuration = checkDuration(createReqVO.getGiftDuration());
checkDuration(createReqVO.getDuration(), createReqVO.getDurationUnit());
// 校验赠送时长
String giftDuration = createReqVO.getGiftDuration();
if (StringUtils.isNotEmpty(giftDuration)) {
checkDuration(giftDuration, createReqVO.getGiftDurationUnit());
}
createReqVO.setDuration(duration)
.setGiftDuration(giftDuration);
// 校验现价计算是否正确
calculateDiscount(createReqVO.getOriginalPrice(), createReqVO.getCurrentPrice(), createReqVO.getDiscount());
// 查询是否有重复的定价
checkProductDetails(createReqVO);
} else if (createReqVO.getPurchaseType().equals(ProductsPurchaseTypeEnum.OUTPUT.getStatus())) {
if (StringUtils.isBlank(createReqVO.getDuration())) {
throw exception(PRODUCTS_OUTPUT_NOT_NULL);
}
// todo 暂不支持产量
throw exception(PRODUCTS_OUTPUT_NOT_SUPPORT);
}
Long id = idWorker.nextId();
createReqVO.setId(id);
ProductsDetailDO productDetailsDO = BeanUtils.toBean(createReqVO, ProductsDetailDO.class).setId(id);
ProductsDetailDO productDetailsDO = BeanUtils.toBean(createReqVO, ProductsDetailDO.class);
productDetailsMapper.insert(productDetailsDO);
return id;
return productDetailsDO.getId();
}
@Override
public void update(ProductDetailsSaveReqVO updateReqVO) {
@@ -100,45 +79,25 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
validateProductDetailsExists(updateReqVO.getId());
if (updateReqVO.getPurchaseType().equals(ProductsPurchaseTypeEnum.DURATION.getStatus())) {
if (StringUtils.isBlank(updateReqVO.getDuration())) {
throw exception(PRODUCTS_DURATION_NOT_NULL);
}
// 校验时长
String duration = checkDuration(updateReqVO.getDuration());
String giftDuration = null;
if (updateReqVO.getGiftDuration() != null) {
giftDuration = checkDuration(updateReqVO.getGiftDuration());
checkDuration(updateReqVO.getDuration(), updateReqVO.getDurationUnit());
// 校验赠送时长
String giftDuration = updateReqVO.getGiftDuration();
if (StringUtils.isNotEmpty(giftDuration)) {
checkDuration(giftDuration, updateReqVO.getGiftDurationUnit());
}
updateReqVO.setDuration(duration).setGiftDuration(giftDuration);
// 校验现价计算是否正确
calculateDiscount(updateReqVO.getOriginalPrice(), updateReqVO.getCurrentPrice(), updateReqVO.getDiscount());
// 校验同时长是否还有其他的定价规则
checkProductDetails(updateReqVO);
} else if (updateReqVO.getPurchaseType().equals(ProductsPurchaseTypeEnum.OUTPUT.getStatus())) {
if (StringUtils.isBlank(updateReqVO.getDuration())) {
throw exception(PRODUCTS_OUTPUT_NOT_NULL);
}
// todo 暂不支持产量
throw exception(PRODUCTS_OUTPUT_NOT_SUPPORT);
}
ProductsDetailDO productDetailsDO = BeanUtils.toBean(updateReqVO, ProductsDetailDO.class)
.setDiscount(getZeroBigDecimal(updateReqVO.getDiscount()))
.setOriginalPrice(getZeroBigDecimal(updateReqVO.getOriginalPrice()))
.setIsDiscounted(updateReqVO.getIsDiscounted());
productDetailsMapper.updateById(productDetailsDO);
}
/**
* value为空返回0.0
*
* @param value
* @return
*/
private BigDecimal getZeroBigDecimal(BigDecimal value) {
return ObjectUtil.defaultIfNull(value, BigDecimal.ZERO);
productDetailsMapper.updateById(BeanUtils.toBean(updateReqVO, ProductsDetailDO.class));
}
@Override
@@ -160,12 +119,12 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
@Override
public PageResult<ProductsDetailDO> getPage(ProductDetailsPageReqVO pageReqVO) {
return productDetailsMapper.selectPage(pageReqVO.setDeleted(0));
return productDetailsMapper.selectPage(pageReqVO);
}
private ProductsDetailDO validateProductDetailsExists(Long id) {
@Override
public ProductsDetailDO validateProductDetailsExists(Long id) {
if (id == null) {
throw exception(PRODUCTS_DETAIL_NO_EXIST);
}
@@ -182,75 +141,64 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
}
// 时长校验
private String checkDuration(String duration){
if (duration == null || !duration.contains("-")) {
throw new IllegalArgumentException("格式错误,格式为:时长值-单位,例如 10-月");
}
String[] parts = duration.split("-", 2);
String valueStr = parts[0].trim();
String timeUnit = parts[1].trim();
/**
* 校验时长
*
* @param duration
* @param timeUnit
* @return
*/
private void checkDuration(String duration, Integer timeUnit) {
AssertUtils.notEmpty(duration, PRODUCTS_DURATION_NOT_NULL);
AssertUtils.notEmpty(timeUnit, PRODUCTS_DURATION_NOT_NULL);
// 校验时长值
if (valueStr.isEmpty() || !valueStr.matches("^\\d+$")) {
if (!duration.matches("^\\d+$")) {
throw exception(PRODUCTS_DURATION_INT_ERR);
}
// 去除前导 0
valueStr = valueStr.replaceFirst("^0+(?!$)", "");
int timeValue = Integer.parseInt(valueStr);
int timeValue = Integer.parseInt(duration);
if (timeValue <= 0) {
throw exception(PRODUCTS_DURATION_INT_ERR);
}
// 校验时间单位
if (timeUnit.isEmpty()
|| !(timeUnit.equals(DurationUnitEnum.YEAR.getName())
|| timeUnit.equals(DurationUnitEnum.MONTH.getName())
|| timeUnit.equals(DurationUnitEnum.DAY.getName()))) {
throw exception(PRODUCTS_DURATION_UNIT_ERR);
}
return timeValue + timeUnit;
}
// 产品存在性校验
private void validateProductExist(Long productId){
/**
* 产品是否存在和时效性校验
*
* @param productId
*/
@Override
public void validateProductExist(Long productId) {
ProductsDO productsDO = productsMapper.selectOne(new LambdaQueryWrapperX<ProductsDO>()
.eq(ProductsDO::getId, productId)
.eq(ProductsDO::getDeleted, false));
AssertUtils.notEmpty(productsDO,PRODUCTS_NO_EXIST);
AssertUtils.notEmpty(productsDO, PRODUCTS_NO_EXIST);
if(productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())){
if (productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) {
throw exception(PRODUCTS_DETAIL_NO_UPDATE);
}
}
// 时长/产量去重
private void checkProductDetails(ProductDetailsSaveReqVO createReqVO){
/**
* 检查产品下是否有相同时长的规则
*
* @param createReqVO
*/
private void checkProductDetails(ProductDetailsSaveReqVO createReqVO) {
int count;
if (createReqVO.getPurchaseType() == 0){
if (createReqVO.getPurchaseType() == 0) {
String duration = createReqVO.getDuration();
Integer durationUnit = createReqVO.getDurationUnit();
// 时长去重
count = productDetailsMapper.selectCountByDuration(createReqVO.getDuration(), createReqVO.getProductId());
count = productDetailsMapper.selectCountByDuration(createReqVO.getProductId(), createReqVO.getId(), duration, durationUnit);
if (count > 0) {
throw exception(PRODUCTS_DETAIL_DURATION_EXIST);
throw ServiceExceptionUtil.exception(PRODUCTS_DETAIL_DURATION_EXIST, duration, durationUnit);
}
}else if (createReqVO.getPurchaseType() == 1){
} else if (createReqVO.getPurchaseType() == 1) {
//产量去重
count = productDetailsMapper.selectCountByOutput(createReqVO.getOutput(), createReqVO.getProductId());
if (count > 0) {
@@ -260,6 +208,21 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
}
/**
* 计算折扣是否正确
*
* @param originalPrice 原价
* @param currentPrice 当前价格
* @param discount 折扣(百分比)
*/
private void calculateDiscount(BigDecimal originalPrice, BigDecimal currentPrice, BigDecimal discount) {
// 校验当前价格是否正确
// 计算期望现价(保留2位小数,四舍五入)
BigDecimal expectedPrice = originalPrice.multiply(discount).setScale(2, RoundingMode.HALF_UP);
if (currentPrice.compareTo(expectedPrice) != 0) {
throw ServiceExceptionUtil.exception(PRODUCTS_DETAIL_CURRENTPRICE_CALCULATE_ERROR, expectedPrice);
}
}
}
@@ -1,6 +1,5 @@
package com.cf.imes.module.system.service.funds.products;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
@@ -9,7 +8,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.product.Prod
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper;
import com.cf.imes.module.system.enums.products.ProductStatusEnum;
import com.cf.imes.module.system.util.SnowflakeIdWorker3rd;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@@ -27,44 +26,42 @@ import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
*/
@Service
@Validated
public class ProductsServiceImpl implements ProductsService{
public class ProductsServiceImpl implements ProductsService {
@Resource
private ProductsMapper productsMapper;
@Resource
private SnowflakeIdWorker3rd idWorker;
@Override
public Long create(String productName) {
// 校验产品名称唯一
validateProductNameUnique(null, productName);
List<ProductsDO> productsDOS = productsMapper.selectList(new LambdaQueryWrapperX<ProductsDO>()
.eq(ProductsDO::getDeleted, false)
.eq(ProductsDO::getProductName, productName)
.select(ProductsDO::getId));
AssertUtils.empty(productsDOS,PRODUCTS_IS_EXIST);
Long id = idWorker.nextId();
productsMapper.insert(
new ProductsDO()
.setId(id)
.setProductName(productName)
.setStatus(ProductStatusEnum.PRODUCT_WAITING_UPDATES.getStatus())
.setSoftwareId(0)
);
return id;
ProductsDO productsDO = new ProductsDO()
.setProductName(productName)
.setStatus(ProductStatusEnum.PRODUCT_WAITING_UPDATES.getStatus())
.setSoftwareId(0);
productsMapper.insert(productsDO);
return productsDO.getId();
}
@Override
public void update(ProductsDO updateReqVO) {
Integer status = validateProductPlateExists(updateReqVO.getId()).getStatus();
Long updateReqVOId = updateReqVO.getId();
String productName = updateReqVO.getProductName();
// 目标状态
Integer updateReqVOStatus = updateReqVO.getStatus();
// 校验产品是否存在
Integer status = validateProductPlateExists(updateReqVOId).getStatus();
// 校验产品名称唯一
if (StringUtils.isNotEmpty(productName)) {
validateProductNameUnique(updateReqVOId, productName);
}
if(status.equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())){
// 当前状态是上架状态 && 目标状态不是下架 的操作提示已上架
if (ProductStatusEnum.PRODUCT_UPDATES.getStatus().equals(status) && !ProductStatusEnum.isProductDelis(updateReqVOStatus)) {
throw exception(PRODUCTS_NO_UPDATE);
}
productsMapper.updateById(updateReqVO);
@@ -99,8 +96,12 @@ public class ProductsServiceImpl implements ProductsService{
}
/**
* 校验产品id是否存在
*
* @param id
* @return
*/
private ProductsDO validateProductPlateExists(Long id) {
ProductsDO productsDO = productsMapper.selectOne(new LambdaQueryWrapperX<ProductsDO>()
.eq(ProductsDO::getId, id)
@@ -112,6 +113,19 @@ public class ProductsServiceImpl implements ProductsService{
return productsDO;
}
/**
* 校验产品名称是否存在
*
* @param productName
*/
private void validateProductNameUnique(Long productId, String productName) {
List<ProductsDO> productsDOS = productsMapper.selectList(new LambdaQueryWrapperX<ProductsDO>()
.eq(ProductsDO::getDeleted, false)
.eq(ProductsDO::getProductName, productName)
.neIfPresent(ProductsDO::getId, productId)
.select(ProductsDO::getId));
AssertUtils.empty(productsDOS, PRODUCTS_IS_EXIST, productName);
}
}
@@ -1,8 +1,8 @@
package com.cf.imes.module.system.service.pay;
import com.alipay.api.AlipayApiException;
import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayRechargeOrderReqVO;
import java.util.Map;
@@ -14,13 +14,18 @@ import java.util.Map;
*/
public interface PayOrderService {
/**
* 发起支付
* 产品支付
*
* @param payOrderUnifiedReqVO
* @return
* @throws AlipayApiException
* @param payProductOrderUnifiedReqVO
*/
PayOrderRespDTO initiatePay(PayOrderUnifiedReqVO payOrderUnifiedReqVO);
void productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO);
/**
* 账户充值支付
*
* @param payRechargeOrderReqVO
*/
PayOrderRespDTO rechargePay(PayRechargeOrderReqVO payRechargeOrderReqVO);
/**
* 查询支付订单支付状态
@@ -10,18 +10,26 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.common.util.validation.ValidationUtils;
import com.cf.imes.framework.organ.core.context.OrganContextHolder;
import com.cf.imes.framework.pay.config.ChenfengAlipayConfig;
import com.cf.imes.framework.pay.config.ChenfengPayConfig;
import com.cf.imes.framework.pay.core.client.dto.order.PayOrderRespDTO;
import com.cf.imes.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import com.cf.imes.framework.common.exception.PayException;
import com.cf.imes.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganAmountRespVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO;
import com.cf.imes.module.system.controller.admin.pay.vo.PayRechargeOrderReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum;
import com.cf.imes.module.system.service.funds.organamount.OrganAmountService;
import com.cf.imes.module.system.service.funds.products.ProductsDetailService;
import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler;
import com.cf.imes.module.system.service.pay.factory.PayHandlerFactory;
import com.cf.imes.module.system.controller.admin.pay.vo.PayOrderUnifiedReqVO;
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO;
import com.cf.imes.module.system.dal.mysql.pay.PayOrderMapper;
import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum;
@@ -32,6 +40,8 @@ import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import jakarta.validation.Validator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.regex.Matcher;
@@ -43,6 +53,7 @@ import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOTIF
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOT_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_SUBMIT_CHANNEL_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_RECHARGE_ORDER_ORGANID_CHECK_ERROR;
/**
* 支付订单接口 实现类
@@ -69,18 +80,43 @@ public class PayOrderServiceImpl implements PayOrderService {
@Resource
private ChenfengAlipayConfig alipayConfig;
@Override
@Transactional(rollbackFor = Exception.class)
public PayOrderRespDTO initiatePay(PayOrderUnifiedReqVO payOrderUnifiedReqVO) {
// 主动校验一次入参
ValidationUtils.validate(validator, payOrderUnifiedReqVO);
@Resource
private ChenfengPayConfig payConfig;
Integer channelCode = payOrderUnifiedReqVO.getChannelCode();
@Resource
private ProductsDetailService productsDetailService;
@Resource
private OrganAmountService organAmountService;
@Override
public void productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
// 主动校验一次入参
ValidationUtils.validate(validator, payProductOrderUnifiedReqVO);
// 校验产品和产品明细
ProductsDetailDO productsDetailDO = productsDetailService.validateProductDetailsExists(payProductOrderUnifiedReqVO.getProductDetailsId());
productsDetailService.validateProductExist(productsDetailDO.getProductId());
// 查询组织余额信息
OrganAmountRespVO organAmount = organAmountService.getOrganAmount(SecurityFrameworkUtils.getUserOrganId());
}
@Override
public PayOrderRespDTO rechargePay(PayRechargeOrderReqVO payRechargeOrderReqVO) {
// 主动校验一次入参
ValidationUtils.validate(validator, payRechargeOrderReqVO);
// 校验组织名称
AssertUtils.notEquals(payRechargeOrderReqVO.getOrganId(), SecurityFrameworkUtils.getUserOrganId(), PAY_RECHARGE_ORDER_ORGANID_CHECK_ERROR);
Integer channelCode = payRechargeOrderReqVO.getChannelCode();
// 获取渠道方处理器
AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode);
// 创建支付订单
PayOrderDO payOrder = createPayOrder(payOrderUnifiedReqVO);
PayOrderDO payOrder = createPayOrder(payRechargeOrderReqVO.getPrice(), "imes充值", channelCode);
payOrderMapper.insert(payOrder);
// 发起渠道方支付
@@ -99,23 +135,22 @@ public class PayOrderServiceImpl implements PayOrderService {
/**
* 创建支付订单
*
* @param payOrderUnifiedReqVO
* @param
* @return
*/
private PayOrderDO createPayOrder(PayOrderUnifiedReqVO payOrderUnifiedReqVO) {
private PayOrderDO createPayOrder(BigDecimal price, String subject, Integer channelCode) {
PayOrderDO payOrderDO = new PayOrderDO();
// 校验获取组织id
Long organId = validOrganId();
Long payOrderId = IdUtil.getSnowflake(0, 0).nextId();
Long organId = SecurityFrameworkUtils.getUserOrganId();
payOrderDO.setId(payOrderId);
payOrderDO.setPayNo(String.format(PAY_ORDER_PREFIX_FORMAT, organId, payOrderId, DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN)));
payOrderDO.setStatus(PayOrderStatusEnum.WAITING);
payOrderDO.setExpireTime(payOrderUnifiedReqVO.getExpireTime());
payOrderDO.setPrice(payOrderUnifiedReqVO.getPrice());
payOrderDO.setSubject(payOrderUnifiedReqVO.getSubject());
payOrderDO.setChannelCode(PayChannelCodeEnum.fromType(payOrderUnifiedReqVO.getChannelCode()));
payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime()));
payOrderDO.setPrice(price);
payOrderDO.setSubject(subject);
payOrderDO.setChannelCode(PayChannelCodeEnum.fromType(channelCode));
payOrderDO.setOrganId(organId);
return payOrderDO;
}
@@ -5,6 +5,7 @@ import cn.hutool.core.lang.Assert;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.alibaba.fastjson.JSON;
import com.alipay.api.AlipayApiException;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePagePayModel;
@@ -27,6 +28,7 @@ import jakarta.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;
@@ -60,6 +62,12 @@ public class AlipayPcPayHandler extends AbstractPayHandler {
model.setProductCode(alipayConfig.getProductCode());
model.setQrPayMode(alipayConfig.getQrPayMode());
Map<String, String> passbackMap = new HashMap<>();
passbackMap.put("test1", "1");
passbackMap.put("test2", "2");
passbackMap.put("test3", outTradeNo);
model.setPassbackParams(JSON.toJSONString(passbackMap));
// 1.2 构建 AlipayTradePagePayRequest 请求
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setBizModel(model);
@@ -22,7 +22,7 @@ import java.lang.annotation.Target;
validatedBy = {PayChannelCodeValidator.class}
)
public @interface PayChannelCodeValid {
String message() default "不支持的支付渠道0:支付宝、1:微信";
String message() default "不支持的支付渠道";
Class<?>[] groups() default {};
@@ -0,0 +1,31 @@
package com.cf.imes.module.system.validation.pay;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 产品支付渠道编码校验注解
* @author Gqr
* @since 2025/4/18 10:21
*/
@Target({
ElementType.FIELD,
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {PayProductChannelCodeValidator.class}
)
public @interface PayProductChannelCodeValid {
String message() default "不支持的支付渠道";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,27 @@
package com.cf.imes.module.system.validation.pay;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
/**
* 产品支付渠道编码校验器
*
* @author Gqr
* @since 2025/4/18 10:22
*/
public class PayProductChannelCodeValidator implements ConstraintValidator<PayProductChannelCodeValid, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (ObjectUtil.isNull(value)) {
return true;
}
for (PayProductChannelCodeEnum codeEnum : PayProductChannelCodeEnum.values()) {
if (ObjectUtil.equal(codeEnum.getCode(), value)) {
return true;
}
}
return false;
}
}
@@ -17,7 +17,7 @@ import java.lang.annotation.*;
)
public @interface ProductType {
String message() default "购买方式:0 时长;1 产量";
String message() default "购买方式不合法0 时长;1 产量";
Class<?>[] groups() default {};
@@ -0,0 +1,32 @@
package com.cf.imes.module.system.validation.products;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 时长单位校验注解
*
* @author Gqr
* @since 2025/8/11 14:18
*/
@Target({
ElementType.FIELD,
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {TimeUnitValidator.class}
)
public @interface TimeUnitValid {
String message() default "时长单位不合法";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
@@ -0,0 +1,30 @@
package com.cf.imes.module.system.validation.products;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.module.system.enums.products.DurationUnitEnum;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import java.util.Objects;
/**
* 时长单位校验器
*
* @author Gqr
* @since 2025/8/11 14:19
*/
public class TimeUnitValidator implements ConstraintValidator<TimeUnitValid, Integer> {
@Override
public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
if (ObjectUtil.isNull(value)) {
return true;
}
for (DurationUnitEnum unitEnum : DurationUnitEnum.values()) {
if (Objects.equals(unitEnum.getStatus(), value)) {
return true;
}
}
return false;
}
}