1、新增组织充值和支付宝回调处理;2、新增余额明细查询;

This commit is contained in:
gaoqr
2025-08-13 17:34:11 +08:00
parent c985272d58
commit df1915989a
23 changed files with 744 additions and 54 deletions
@@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 渠道支付订单 Response DTO
@@ -75,6 +76,11 @@ public class PayOrderRespDTO {
*/
private String channelErrorMsg;
/**
* 发起支付给支付方后,异步回调原封不动回到调用方
*/
private Map<String,Object> passbackParams;
/**
* 创建【WAITING】状态的订单返回
*/
@@ -122,6 +128,23 @@ public class PayOrderRespDTO {
return respDTO;
}
/**
* 创建指定状态的订单返回,适合支付渠道回调时
*/
public static PayOrderRespDTO of(Integer status, String channelOrderNo, String channelUserId, LocalDateTime successTime,
String outTradeNo, Object rawData, Map<String, Object> passbackParams) {
PayOrderRespDTO respDTO = new PayOrderRespDTO();
respDTO.status = status;
respDTO.channelOrderNo = channelOrderNo;
respDTO.channelUserId = channelUserId;
respDTO.successTime = successTime;
// 相对通用的字段
respDTO.outTradeNo = outTradeNo;
respDTO.rawData = rawData;
respDTO.passbackParams = passbackParams;
return respDTO;
}
/**
* 创建【CLOSED】状态的订单返回,适合调用支付渠道失败时
*/
@@ -85,6 +85,11 @@ public class PayOrderUnifiedReqDTO {
*/
private Map<String, String> channelExtras;
/**
* 发起支付给支付方后,异步回调原封不动回到调用方
*/
private Map<String, Object> passbackParams;
/**
* 展示模式
*
@@ -16,4 +16,10 @@ public class RedisKeyConstants {
* 刷新权限
*/
public static final String PERMISSION_REFRESH = "PERMISSION_REFRESH";
/**
*
*/
public static final String PAY_NOTIFY_ORDER_LOCK_KEY = "pay_recharge_order:%s%s";
}
@@ -411,7 +411,7 @@ public class ErrorCodeConstants {
//=========== 资金管理 1-002-043-000 ============
public static final ErrorCode ORGAN_AMOUNT_NO_EXIST = new ErrorCode(1_002_043_001, "当前组织无资金管理");
public static final ErrorCode ORGAN_AMOUNT_NO_EXIST = new ErrorCode(1_002_043_001, "当前组织没有可用的资金记录,请联系客服");
public static final ErrorCode INVOICE_NO_EXIST = new ErrorCode(1_002_043_002, "当前发票记录有误,请重新查看");
public static final ErrorCode INVOICING_REJECT_REASON = new ErrorCode(1_002_043_003, "请填写拒绝开票原因");
public static final ErrorCode INVOICE_FILE_IS_NULL = new ErrorCode(1_002_043_004, "发票附件不存在,请重新上传");
@@ -433,6 +433,7 @@ public class ErrorCodeConstants {
public static final ErrorCode ADVERTISEMENT_REPUBLISH_NO_TIME = new ErrorCode(1_002_043_021, "广告重新发布,应重新选择投放时间");
public static final ErrorCode RECHARGE_ACTIVE_NAME_IS_EXIST = new ErrorCode(1_002_043_022, "当前赠送金活动名称已存在,请修改");
public static final ErrorCode IMAGE_UPLOAD_ERROR = new ErrorCode(1_002_043_023, "图片上传失败,请重试或重新选择图片");
public static final ErrorCode ORGAN_RECHARGE_AMOUNT_UPDATE_ERROR = new ErrorCode(1_002_043_024, "组织充值失败,请检查组织资金数据");
//=========== 支付相关 1-002-048-000 ============
public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_048_000, "暂不支持的支付渠道,请使用其他支付渠道");
@@ -442,6 +443,9 @@ public class ErrorCodeConstants {
public static final ErrorCode PAY_ORDER_STATUS_IS_NOT_WAITING = new ErrorCode(1_002_048_004, "支付订单不处于待支付");
public static final ErrorCode PAY_ORDER_SUBMIT_CHANNEL_ERROR = new ErrorCode(1_002_048_005, "发起渠道支付异常,请联系客服");
public static final ErrorCode PAY_RECHARGE_ORDER_ORGANID_CHECK_ERROR = new ErrorCode(1_002_048_006, "充值目标组织和当前操作人组织不同,发起充值支付失败");
public static final ErrorCode PAY_RECHARGE_LOCK_EXISTS_ERROR = new ErrorCode(1_002_048_007, "组织下存在待支付的充值订单,请稍后再试");
public static final ErrorCode PAY_NOTIFY_PASSBACK_NOT_EXISTS_ERROR = new ErrorCode(1_002_048_008, "支付回调缺少passback_params,请检查支付渠道");
public static final ErrorCode PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR = new ErrorCode(1_002_048_009, "不支持的支付回调tradeType:【{}】,请检查支付渠道");
//=========== 产品相关 1-002-045-000 ============
public static final ErrorCode PRODUCTS_NO_EXIST = new ErrorCode(1_002_045_001, "当前产品不存在");
@@ -0,0 +1,63 @@
package com.cf.imes.module.system.controller.admin.funds.balancedetails;
import com.cf.imes.framework.common.pojo.CommonResult;
import com.cf.imes.framework.common.pojo.PageParam;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.excel.core.util.ExcelUtils;
import com.cf.imes.framework.operatelog.core.annotations.OperateLog;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsRespVO;
import com.cf.imes.module.system.service.funds.incomeandexpense.IncomeExpenseService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import static com.cf.imes.framework.common.pojo.CommonResult.success;
import static com.cf.imes.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
/**
* @author 组织余额明细
*/
@Tag(name = "管理后台 - 组织余额明细")
@RestController
@RequestMapping("/system/balance")
@Validated
public class BalanceDetailsController {
@Resource
private IncomeExpenseService incomeExpenseService;
@GetMapping("")
@Operation(summary = "查询组织余额明细分页")
public CommonResult<PageResult<BalanceDetailsRespVO>> getBalanceDetailsPage(@Valid BalanceDetailsPageReqVO pageReqVO) {
return success(incomeExpenseService.getBalanceDetailsPage(pageReqVO));
}
@GetMapping("/export")
@Operation(summary = "组织余额明细导出")
@OperateLog(type = EXPORT)
public void balanceDetailsExport(@Valid BalanceDetailsPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<BalanceDetailsRespVO> list = incomeExpenseService.getBalanceDetailsPage(pageReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "余额明细表.xls", "数据", BalanceDetailsRespVO.class,
BeanUtils.toBean(list, BalanceDetailsRespVO.class));
}
}
@@ -0,0 +1,40 @@
package com.cf.imes.module.system.controller.admin.funds.balancedetails.vo;
import com.cf.imes.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import static com.cf.imes.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
@Schema(description = "管理后台 - 组织余额明细分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@Builder
public class BalanceDetailsPageReqVO extends PageParam {
@Schema(description = "流水号")
private String businessNo;
@Schema(description = "交易类型")
private Integer tradeType;
@Schema(description = "收支类型")
private Integer incomeExpenseType;
@Schema(description = "创建时间", example = "[2022-07-01 ,2022-07-01]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate[] createTime;
@Schema(description = "组织ID")
private Long organId;
}
@@ -0,0 +1,82 @@
package com.cf.imes.module.system.controller.admin.funds.balancedetails.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class BalanceDetailsRespVO {
@Schema(description = "流水号")
@ExcelProperty("流水号")
private String businessNo;
@Schema(description = "交易类型,0充值 1软件购买")
@ExcelProperty("交易类型")
private Integer tradeType;
@Schema(description = "收支类型,0收入 1支出")
private Integer incomeExpenseType;
@Schema(description = "入账现金")
@ExcelProperty("入账现金")
private BigDecimal entryAmount;
@Schema(description = "入账赠送金")
@ExcelProperty("入账赠送金")
private BigDecimal entryGiftAmount;
@Schema(description = "支出现金")
@ExcelProperty("支出现金")
private BigDecimal expensesAmount;
@Schema(description = "支出赠送金")
@ExcelProperty("支出赠送金")
private BigDecimal expensesGiftAmount;
@Schema(description = "可用金额")
@ExcelProperty("可用金额")
private BigDecimal availableAmount;
@Schema(description = "现金金额")
@ExcelProperty("现金金额")
private BigDecimal amount;
@Schema(description = "赠送金额")
@ExcelProperty("赠送金额")
private BigDecimal giftAmount;
@Schema(description = "交易时间")
@ExcelProperty("交易时间")
private LocalDateTime createTime;
@Schema(description = "操作人")
@ExcelProperty("操作人")
private String creator;
@Schema(description = "支出/入账现金金额")
private BigDecimal cashAmountChange;
@Schema(description = "支出/入账赠送金金额")
private BigDecimal bonusAmountChange;
}
@@ -1,6 +1,5 @@
package com.cf.imes.module.system.controller.admin.funds.organamount.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -9,12 +8,6 @@ import java.math.BigDecimal;
@Schema(description = "管理后台 - 组织余额信息 Response VO")
@Data
public class OrganAmountRespVO {
@Schema(description = "可用总金额")
private BigDecimal availableAmount;
@Schema(description = "现金余额")
private BigDecimal amount;
@@ -26,5 +19,7 @@ public class OrganAmountRespVO {
@Schema(description = "可开票金额")
private BigDecimal invoiceableAmount;
@Schema(description = "产品有效期")
private String productEfficientDate;
}
@@ -0,0 +1,31 @@
package com.cf.imes.module.system.controller.admin.funds.organamount.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.math.BigDecimal;
/**
* @author Gqr
* @since 2025/8/13 16:46
*/
@Schema(description = "管理后台 - 组织充值后余额信息 Response VO")
@Data
@AllArgsConstructor
public class OrganRechargeAmountRespVO {
/**
* 可用余额
*/
private BigDecimal availableAmount;
/**
* 现金余额
*/
private BigDecimal amount;
/**
* 赠送金余额
*/
private BigDecimal giftAmount;
}
@@ -2,10 +2,10 @@ 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.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.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
@@ -44,15 +44,17 @@ public class PayController {
return success(payOrderRespDTO);
}
@PostMapping("/product")
@Operation(summary = "创建产品支付订单")
public CommonResult<PayOrderRespDTO> initiatePay(@RequestBody PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
payOrderService.productPay(payProductOrderUnifiedReqVO);
return success(null);
}
// @PostMapping("/product")
// @Operation(summary = "创建产品支付订单")
// public CommonResult<PayOrderRespDTO> initiatePay(@RequestBody PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
// payOrderService.productPay(payProductOrderUnifiedReqVO);
// return success(null);
// }
@GetMapping("/order/{channelCode}/{outTradeNo}")
@Operation(summary = "查询支付订单")
@Parameter(name = "outTradeNo", description = "支付流水号", required = true, example = "P1O1955")
@Parameter(name = "channelCode", description = "支付渠道", required = true, example = "0")
public CommonResult<Boolean> queryPayOrder(@PathVariable("outTradeNo") String outTradeNo, @PathVariable("channelCode") int channelCode) {
payOrderService.queryPayOrderStatus(outTradeNo, channelCode);
return success(true);
@@ -9,8 +9,8 @@ import lombok.*;
import java.math.BigDecimal;
@TableName("organ_amount")
@KeySequence("organ_amount_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@TableName("system_organization_amount")
@KeySequence("system_organization_amount_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@@ -62,5 +62,8 @@ public class OrganAmountDO extends BaseDO {
*/
private BigDecimal invoiceableAmount;
/**
* 版本号
*/
private int version;
}
@@ -0,0 +1,118 @@
package com.cf.imes.module.system.dal.dataobject.incomeandexpense;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.math.BigDecimal;
/**
* @author 收支明细表
*/
@TableName("income_expense_details")
@KeySequence("income_expense_details_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class IncomeExpenseDetailsDO extends BaseDO {
/**
* 主键
*/
@TableId
private Long id;
/**
* 流水号
*/
private String businessNo;
/**
* 组织ID
*/
private Long organId;
/**
* 充值-赠送金规则表
*/
private Long giftMoneyId;
/**
* 购买-购买记录id
*/
private Long purchaseId;
/**
* 交易类型
*/
private Integer tradeType;
/**
* 收支类型
*/
private Integer incomeExpenseType;
/**
* 支出/入账现金金额
*/
private BigDecimal cashAmountChange;
/**
* 支出/入账赠送金金额
*/
private BigDecimal bonusAmountChange;
/**
* 可用余额
*/
private BigDecimal availableAmount;
/**
* 现金金额
*/
private BigDecimal amount;
/**
* 赠送金额
*/
private BigDecimal giftAmount;
/**
* 货币类型
*/
private Integer currency;
/**
* 货币单位
*/
private String currencyUnit;
}
@@ -0,0 +1,32 @@
package com.cf.imes.module.system.dal.mysql.incomeandexpense;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO;
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 收支明细 Mapper
*
* @author Gqr
* @since 2025/8/13 11:41
*/
@Mapper
public interface IncomeExpenseDetailsMapper extends BaseMapperX<IncomeExpenseDetailsDO> {
default PageResult<IncomeExpenseDetailsDO> selectDetailsPage(BalanceDetailsPageReqVO pageReqVO, List<Long> organIds) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<IncomeExpenseDetailsDO>()
.eqIfPresent(IncomeExpenseDetailsDO::getOrganId, pageReqVO.getOrganId())
.inIfPresent(IncomeExpenseDetailsDO::getOrganId, organIds)
.eq(IncomeExpenseDetailsDO::getDeleted, false)
.likeIfPresent(IncomeExpenseDetailsDO::getBusinessNo, pageReqVO.getBusinessNo())
.eqIfPresent(IncomeExpenseDetailsDO::getTradeType,pageReqVO.getTradeType())
.eqIfPresent(IncomeExpenseDetailsDO::getIncomeExpenseType,pageReqVO.getIncomeExpenseType())
.betweenIfPresent(IncomeExpenseDetailsDO::getCreateTime,pageReqVO.getCreateTime()));
}
}
@@ -0,0 +1,51 @@
package com.cf.imes.module.system.enums.pay;
import cn.hutool.core.util.ObjectUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author 交易类型枚举类
*/
@Getter
@AllArgsConstructor
public enum IncomeExpenseTypeEnum {
/**
* 收入
*/
INCOME(0, "收入"),
/**
* 支出
*/
EXPENSES(1, "支出");
/**
* 类型
*/
private final Integer type;
/**
* 名称
*/
private final String name;
public static IncomeExpenseTypeEnum fromType(Integer type) {
if (type == null) {
return null;
}
for (IncomeExpenseTypeEnum value : values()) {
if (ObjectUtil.equal(value.getType(), type)) {
return value;
}
}
return null;
}
}
@@ -0,0 +1,11 @@
package com.cf.imes.module.system.enums.pay;
/**
* 支付常量
* @author Gqr
* @since 2025/8/12 17:49
*/
public class PayConstants {
public static final String PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY = "tradeType"; //支付passback 交易类型key
public static final String PAY_PASSBACK_PARAMS_KEY = "passback_params";// 回调参数passback key
}
@@ -0,0 +1,34 @@
package com.cf.imes.module.system.enums.pay;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 交易类型枚举
* @author Gqr
* @since 2025/8/12 17:45
*/
@Getter
@AllArgsConstructor
public enum TradeTypeEnum {
/**
* 充值
*/
RECHARGE(0),
/**
* 产品购买
*/
PRODUCT(1);
private final Integer code;
public static TradeTypeEnum fromCode(Integer code) {
for (TradeTypeEnum value : TradeTypeEnum.values()) {
if (value.code.equals(code)) {
return value;
}
}
return null;
}
}
@@ -0,0 +1,29 @@
package com.cf.imes.module.system.service.funds.incomeandexpense;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsRespVO;
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
/**
* 收支明细接口
*
* @author Gqr
* @since 2025/8/13 10:19
*/
public interface IncomeExpenseService {
/**
* 创建收支明细
*
* @param incomeExpenseDetailsDO
*/
void createDetail(IncomeExpenseDetailsDO incomeExpenseDetailsDO);
/**
* 查询余额明细分页
*
* @param pageReqVO
* @return
*/
PageResult<BalanceDetailsRespVO> getBalanceDetailsPage(BalanceDetailsPageReqVO pageReqVO);
}
@@ -0,0 +1,51 @@
package com.cf.imes.module.system.service.funds.incomeandexpense;
import cn.hutool.core.collection.CollUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.balancedetails.vo.BalanceDetailsRespVO;
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
import com.cf.imes.module.system.dal.mysql.incomeandexpense.IncomeExpenseDetailsMapper;
import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
/**
* 收支明细接口实现类
*
* @author Gqr
* @since 2025/8/13 10:29
*/
@Service
public class IncomeExpenseServiceImpl implements IncomeExpenseService {
@Resource
private IncomeExpenseDetailsMapper incomeExpenseDetailsMapper;
@Override
public void createDetail(IncomeExpenseDetailsDO incomeExpenseDetailsDO) {
incomeExpenseDetailsMapper.insert(incomeExpenseDetailsDO);
}
@Override
public PageResult<BalanceDetailsRespVO> getBalanceDetailsPage(BalanceDetailsPageReqVO pageReqVO) {
PageResult<IncomeExpenseDetailsDO> pageResult = incomeExpenseDetailsMapper.selectDetailsPage(pageReqVO,null);
if(CollUtil.isEmpty(pageResult.getList())){
return new PageResult<>();
}
PageResult<BalanceDetailsRespVO> result = BeanUtils.toBean(pageResult, BalanceDetailsRespVO.class);
// 根据收支类型赋值对应的 支出/收入字段
result.getList().forEach(f -> {
if (f.getIncomeExpenseType().equals(IncomeExpenseTypeEnum.INCOME.getType())) {
f.setEntryAmount(f.getCashAmountChange());
f.setEntryGiftAmount(f.getBonusAmountChange());
} else {
f.setExpensesAmount(f.getCashAmountChange());
f.setExpensesGiftAmount(f.getBonusAmountChange());
}
});
return result;
}
}
@@ -3,12 +3,31 @@ package com.cf.imes.module.system.service.funds.organamount;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.AllOrganAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganRechargeAmountRespVO;
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
import java.math.BigDecimal;
/**
* @author 资金管理接口
*/
public interface OrganAmountService {
/**
* 校验机构套餐
*
* @param organId
*/
OrganAmountDO validOrganAmount(Long organId);
/**
* 更新机构余额
*
* @param organId
* @param amount 充值金额
* @return
*/
OrganRechargeAmountRespVO recharge(Long organId, BigDecimal amount);
OrganAmountRespVO getOrganAmount(Long organId);
@@ -1,11 +1,14 @@
package com.cf.imes.module.system.service.funds.organamount;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.executor.api.funds.ExecutorFundsApi;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.AllOrganAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganRechargeAmountRespVO;
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
import com.cf.imes.module.system.dal.mysql.funds.organamount.OrganAmountMapper;
import com.cf.imes.module.system.service.organ.OrganService;
@@ -18,6 +21,7 @@ import java.math.BigDecimal;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_AMOUNT_NO_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_RECHARGE_AMOUNT_UPDATE_ERROR;
/**
* @author 资金管理接口 实现类
@@ -38,27 +42,63 @@ public class OrganAmountServiceImpl implements OrganAmountService {
private ExecutorFundsApi executorFundsApi;
@Override
public OrganAmountDO validOrganAmount(Long organId) {
OrganAmountDO organAmountDO = organAmountMapper.selectOne(new LambdaQueryWrapper<OrganAmountDO>().eq(OrganAmountDO::getOrganId, organId));
AssertUtils.notEmpty(organAmountDO, ORGAN_AMOUNT_NO_EXIST);
return organAmountDO;
}
@Override
public OrganRechargeAmountRespVO recharge(Long organId, BigDecimal price) {
OrganAmountDO organAmountDO = organAmountMapper.selectOne(new LambdaQueryWrapper<OrganAmountDO>().eq(OrganAmountDO::getOrganId, organId));
int updated = organAmountMapper.update(new LambdaUpdateWrapper<OrganAmountDO>()
.eq(OrganAmountDO::getOrganId, organId)
.eq(OrganAmountDO::getVersion, organAmountDO.getVersion())
.set(OrganAmountDO::getAmount, organAmountDO.getAmount().add(price))
.set(OrganAmountDO::getVersion, organAmountDO.getVersion() + 1));
// 1. 首次更新失败,认为出现并发,再查一次再更新
if (updated == 0) {
organAmountDO = organAmountMapper.selectOne(new LambdaQueryWrapper<OrganAmountDO>().eq(OrganAmountDO::getOrganId, organId));
updated = organAmountMapper.update(new LambdaUpdateWrapper<OrganAmountDO>()
.eq(OrganAmountDO::getOrganId, organId)
.eq(OrganAmountDO::getVersion, organAmountDO.getVersion())
.set(OrganAmountDO::getAmount, organAmountDO.getAmount().add(price))
.set(OrganAmountDO::getVersion, organAmountDO.getVersion() + 1));
// 2. 第二次更新失败,资金表可能不存在
if (updated == 0) {
throw exception(ORGAN_RECHARGE_AMOUNT_UPDATE_ERROR);
} else {
// 可用余额:现金+赠送金+支付价格
BigDecimal availableAmount = organAmountDO.getAmount().add(price).add(organAmountDO.getGiftAmount());
// 现金余额:现金+支付价格
BigDecimal amount = organAmountDO.getAmount().add(price);
return new OrganRechargeAmountRespVO(availableAmount, amount, organAmountDO.getGiftAmount());
}
} else {
// 可用余额:现金+赠送金+支付价格
BigDecimal availableAmount = organAmountDO.getAmount().add(price).add(organAmountDO.getGiftAmount());
// 现金余额:现金+支付价格
BigDecimal amount = organAmountDO.getAmount().add(price);
return new OrganRechargeAmountRespVO(availableAmount, amount, organAmountDO.getGiftAmount());
}
}
@Override
public OrganAmountRespVO getOrganAmount(Long organId) {
organService.validOrgan(organId);
OrganAmountDO organAmountDO = validateOrganAmountExists(organId);
OrganAmountDO organAmountDO = validOrganAmount(organId);
OrganAmountRespVO organAmountRespVO = BeanUtils.toBean(organAmountDO, OrganAmountRespVO.class);
organAmountRespVO.setAvailableAmount(organAmountRespVO.getAmount().add(organAmountRespVO.getGiftAmount()));
return organAmountRespVO;
}
@Override
public AllOrganAmountRespVO getOrganAmountAll() {
@@ -76,21 +116,4 @@ public class OrganAmountServiceImpl implements OrganAmountService {
}
private OrganAmountDO validateOrganAmountExists(Long organId) {
OrganAmountDO organAmountDO = organAmountMapper.selectById(organId);
if (ObjectUtil.isNull(organAmountDO)) {
throw exception(ORGAN_AMOUNT_NO_EXIST);
}
return organAmountDO;
}
}
@@ -3,6 +3,7 @@ package com.cf.imes.module.system.service.pay;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -10,6 +11,7 @@ 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.exception.util.ServiceExceptionUtil;
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;
@@ -22,10 +24,16 @@ 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.funds.organamount.vo.OrganRechargeAmountRespVO;
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.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum;
import com.cf.imes.module.system.enums.pay.PayChannelCodeEnum;
import com.cf.imes.module.system.enums.pay.PayConstants;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.service.funds.incomeandexpense.IncomeExpenseService;
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;
@@ -43,11 +51,14 @@ import jakarta.validation.Validator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_NOTIFY_PASSBACK_NOT_EXISTS_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_CHANNEL_QUERY_NOT_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOTIFY_DATA_PARSE_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_ORDER_NOT_EXIST;
@@ -89,6 +100,9 @@ public class PayOrderServiceImpl implements PayOrderService {
@Resource
private OrganAmountService organAmountService;
@Resource
private IncomeExpenseService incomeExpenseService;
@Override
public void productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
// 主动校验一次入参
@@ -104,25 +118,34 @@ public class PayOrderServiceImpl implements PayOrderService {
}
@Override
@Transactional(rollbackFor = Exception.class)
public PayOrderRespDTO rechargePay(PayRechargeOrderReqVO payRechargeOrderReqVO) {
// 主动校验一次入参
ValidationUtils.validate(validator, payRechargeOrderReqVO);
Long organId = payRechargeOrderReqVO.getOrganId();
// 校验组织名称
AssertUtils.notEquals(payRechargeOrderReqVO.getOrganId(), SecurityFrameworkUtils.getUserOrganId(), PAY_RECHARGE_ORDER_ORGANID_CHECK_ERROR);
AssertUtils.notEquals(organId, SecurityFrameworkUtils.getUserOrganId(), PAY_RECHARGE_ORDER_ORGANID_CHECK_ERROR);
// 校验组织套餐
organAmountService.validOrganAmount(organId);
Integer channelCode = payRechargeOrderReqVO.getChannelCode();
// 获取渠道方处理器
Integer channelCode = payRechargeOrderReqVO.getChannelCode();
AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode);
// 创建支付订单
PayOrderDO payOrder = createPayOrder(payRechargeOrderReqVO.getPrice(), "imes充值", channelCode);
payOrderMapper.insert(payOrder);
// 拼接充值个性化参数
Map<String, Object> passbackParams = new HashMap<>();
passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY, TradeTypeEnum.RECHARGE.getCode());
// 发起渠道方支付
PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null;
try {
payOrderUnifiedReqDTO = createPayOrderUnifiedReqDTO(payOrder);
payOrderUnifiedReqDTO = createPayOrderUnifiedReqDTO(payOrder, passbackParams);
return payHandler.unifiedOrder(payOrderUnifiedReqDTO);
} catch (Throwable e) {
// 系统异常,则包装成 PayException 异常抛出
@@ -161,7 +184,7 @@ public class PayOrderServiceImpl implements PayOrderService {
* @param payOrderDO
* @return
*/
private PayOrderUnifiedReqDTO createPayOrderUnifiedReqDTO(PayOrderDO payOrderDO) {
private PayOrderUnifiedReqDTO createPayOrderUnifiedReqDTO(PayOrderDO payOrderDO, Map<String, Object> passbackParams) {
PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = new PayOrderUnifiedReqDTO();
String subject = payOrderDO.getSubject();
@@ -171,6 +194,7 @@ public class PayOrderServiceImpl implements PayOrderService {
payOrderUnifiedReqDTO.setPrice(payOrderDO.getPrice().intValue());
payOrderUnifiedReqDTO.setExpireTime(payOrderDO.getExpireTime());
payOrderUnifiedReqDTO.setNotifyUrl(String.format(alipayConfig.getNotifyUrl(), payOrderDO.getChannelCode().getCode()));
payOrderUnifiedReqDTO.setPassbackParams(passbackParams);
return payOrderUnifiedReqDTO;
}
@@ -299,6 +323,50 @@ public class PayOrderServiceImpl implements PayOrderService {
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
}
log.debug("[updateOrderExtensionSuccess][order({}) 更新为已支付]", order.getId());
// 3. 根据交易类型做对应回调处理
Map<String, Object> passbackParams = notify.getPassbackParams();
Object passbackObj = passbackParams.get(PayConstants.PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY);
AssertUtils.notEmpty(passbackObj, PAY_NOTIFY_PASSBACK_NOT_EXISTS_ERROR);
switch (TradeTypeEnum.fromCode(NumberUtil.parseInt(passbackObj.toString()))) {
case RECHARGE:
notifyRechargeOrderSuccess(order, organId);
break;
case PRODUCT:
break;
default:
throw ServiceExceptionUtil.exception(PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR, passbackObj.toString());
}
}
/**
* 充值成功回调
* @param payOrderDO
* @param organId
*/
private void notifyRechargeOrderSuccess(PayOrderDO payOrderDO, Long organId) {
// 支付订单的金额
BigDecimal price = payOrderDO.getPrice();
// 订单流水号
String payNo = payOrderDO.getPayNo();
// 1.更新组织资金
OrganRechargeAmountRespVO rechargeAmountRespVO = organAmountService.recharge(organId, price);
// 2.新增收支明细
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
.organId(organId)
.businessNo(payNo)
.tradeType(TradeTypeEnum.RECHARGE.getCode())
.incomeExpenseType(IncomeExpenseTypeEnum.INCOME.getType())
.cashAmountChange(price)
.availableAmount(rechargeAmountRespVO.getAvailableAmount())
.amount(rechargeAmountRespVO.getAmount())
.giftAmount(rechargeAmountRespVO.getGiftAmount())
.build();
incomeExpenseDetailsDO.setCreator(payOrderDO.getCreator());
incomeExpenseDetailsDO.setUpdater(payOrderDO.getUpdater());
incomeExpenseService.createDetail(incomeExpenseDetailsDO);
}
/**
@@ -20,6 +20,7 @@ import com.cf.imes.framework.pay.config.ChenfengAlipayConfig;
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.pay.core.enums.order.PayOrderStatusRespEnum;
import com.cf.imes.module.system.enums.pay.PayConstants;
import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -61,12 +62,7 @@ public class AlipayPcPayHandler extends AbstractPayHandler {
model.setTimeExpire(LocalDateTimeUtil.format(reqDTO.getExpireTime(), NORM_DATETIME_FORMATTER));
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));
model.setPassbackParams(JSON.toJSONString(reqDTO.getPassbackParams()));
// 1.2 构建 AlipayTradePagePayRequest 请求
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
@@ -121,11 +117,15 @@ public class AlipayPcPayHandler extends AbstractPayHandler {
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
Integer status = parseStatus(bodyObj.get("trade_status"));
// 3.解析创建支付时候的个性化参数
String passbackParamsStr = bodyObj.get(PayConstants.PAY_PASSBACK_PARAMS_KEY);
Map<String, Object> passbackParams = JSON.parseObject(passbackParamsStr, Map.class);
Assert.notNull(status, (Supplier<Throwable>) () -> {
throw new IllegalArgumentException(CharSequenceUtil.format("body({}) 的 trade_status 不正确", body));
});
return PayOrderRespDTO.of(status, bodyObj.get("trade_no"), bodyObj.get("seller_id"), parseTime(params.get("gmt_payment")),
bodyObj.get("out_trade_no"), body);
bodyObj.get("out_trade_no"), body, passbackParams);
}
/**
@@ -290,7 +290,7 @@ chenfeng:
appId: 8wb0qYthTUqf9290UJfLejTcOA5CU1wiAhmpLbXmwDCCt+ZakcBPBK1a88E=
privateKey: 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
alipayPublicKey: KbUGE7zsPBKWPx0/zMcAdLRldKrcgy2LRiOrqOBm7y4iPJLXTYtXFJT15p2JMQiyOM8mqiYQR0OEDwfrKxDgHXLtIut/e0EAk9bZwZoLrqVe0pN1huseRC38zEglbWil3yz+tZfHZuAaEq2EzvWJltX/tRKOzNBqAT9l+UvnqNedgCFB9MY2EErqJjbn/at3gYCkV2idt31ghF0z8SEQ5h9ztNYtmTvk8c8ipi0/hIjX8XryN6mlOvGQfyZ4rWrBWBuZ6iD/SyuThwxKmFYU4dhHbHVpBrva5nJWU43CP7RVaJm649nNJnce+Ppj2Tv8+ugSLe5poZWo2zmGe3Xn5WmN6OTZyRI1pnmn2/hGxtOMmTZJX7Gh0pI4v3wmU0gOd/gbRlqlvN+u12T92nyH8MQrHdA7UXuttAtolYUa/Rli/20qHX69QtOtvfZk8mNvDSj9dDvODseGcrFHm07jyfxpaIkQDeN75cQ3NwNgDQEW8urQszaU1IJ7hc+20tt0Z3td4+7MfiVPpGfVxsGE66gwLgpB8wsxEhyBTaBr7osMLqOv
notifyUrl: VvJ5DOVuGOBw39j7tyrvsEEtLSseVM8T3u9kqsWKago+iI5ao8JzoggmeSe+N9LrsYxutK4fkGpmr4jOGJ3JTQFZ7YBXLxf2oLIXOZH9O27EnDtm
notifyUrl: RIEeHCoPHaAryuVN6mEYskBSl+EPvhsxRpjIbuIE7f7jABVxm46bLc01WQUe4Fn9NPYyNTv0xCxM68iRrmFDjAUjQ/hkIVNG+yD/kNsjcYCWa2gtOQPrvWZzDA==
justauth:
enabled: true