mirror of
http://192.168.1.205:9980/cf_devdept2/cf_imes_server.git
synced 2026-08-12 21:02:08 +08:00
1、新增组织购买产品接口、混合支付和异步回调;2、新增微信支付和回调基础代码;3、组织余额新增有效时长内容;4、新增过期支付单处理类;
This commit is contained in:
+3
-3
@@ -21,7 +21,7 @@ import java.time.LocalDate;
|
||||
public class SnowflakeIdWorker3rd {
|
||||
/** 初始时间 (2024-01-01: 1704038400) */
|
||||
// private final int twepoch = 28400640;// 1704038400000L/1000/60;
|
||||
private final int twepoch = 1704038400;
|
||||
private final int twepoch = 28400640;
|
||||
/** 序列在id中占位数 */
|
||||
private final long sequenceBits = 7L;
|
||||
/** 时间截向左移7bit */
|
||||
@@ -122,8 +122,8 @@ public class SnowflakeIdWorker3rd {
|
||||
* @return 当前时间(分钟)
|
||||
*/
|
||||
protected int timeGen() {
|
||||
// String timestamp = String.valueOf(System.currentTimeMillis() / 1000 / 60);
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
String timestamp = String.valueOf(System.currentTimeMillis() / 1000 / 60);
|
||||
// String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
return Integer.valueOf(timestamp);
|
||||
}
|
||||
|
||||
|
||||
+45
@@ -1,9 +1,14 @@
|
||||
package com.cf.imes.framework.pay.config;
|
||||
|
||||
import com.cf.imes.framework.common.util.encrypt.AesUtils;
|
||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||
import lombok.Data;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* @author Gqr
|
||||
* @since 2025/4/15 16:52
|
||||
@@ -11,4 +16,44 @@ import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
@ConfigurationProperties(prefix = "chenfeng.pay.wechat")
|
||||
@Data
|
||||
public class ChenfengWechatPayConfig extends WxPayConfig {
|
||||
@Value("${chenfeng.encrypt.publicKey:}")
|
||||
private String publicKey;
|
||||
|
||||
@Override
|
||||
public void setAppId(String appId) {
|
||||
if (StringUtils.isNotEmpty(publicKey)) {
|
||||
super.setAppId(AesUtils.decrypt(appId, publicKey));
|
||||
} else {
|
||||
super.setAppId(appId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMchId(String mchId) {
|
||||
if (StringUtils.isNotEmpty(publicKey)) {
|
||||
super.setMchId(AesUtils.decrypt(mchId, publicKey));
|
||||
} else {
|
||||
super.setMchId(mchId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMchKey(String mchKey) {
|
||||
if (StringUtils.isNotEmpty(publicKey)) {
|
||||
super.setMchKey(AesUtils.decrypt(mchKey, publicKey));
|
||||
} else {
|
||||
super.setMchKey(mchKey);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setKeyContent(byte[] keyContent) {
|
||||
if (StringUtils.isNotEmpty(publicKey)) {
|
||||
String encrypted = new String(keyContent, StandardCharsets.UTF_8);
|
||||
String decrypted = AesUtils.decrypt(encrypted, publicKey);
|
||||
super.setKeyContent(decrypted.getBytes(StandardCharsets.UTF_8));
|
||||
} else {
|
||||
super.setKeyContent(keyContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -434,6 +434,7 @@ public class ErrorCodeConstants {
|
||||
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, "组织充值失败,请检查组织资金数据");
|
||||
public static final ErrorCode ORGAN_PRODUCT_PAY_AMOUNT_CHANGE_ERROR = new ErrorCode(1_002_043_025, "发起产品支付失败,组织资金已发生改变,请刷新或稍后再发起支付");
|
||||
|
||||
//=========== 支付相关 1-002-048-000 ============
|
||||
public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_048_000, "暂不支持的支付渠道,请使用其他支付渠道");
|
||||
@@ -446,6 +447,9 @@ public class ErrorCodeConstants {
|
||||
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:【{}】,请检查支付渠道");
|
||||
public static final ErrorCode PAY_PRODUCT_BALANCE_INSUFFICIENT = new ErrorCode(1_002_048_010, "现金余额不足,请选择其他支付方式!");
|
||||
public static final ErrorCode PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT = new ErrorCode(1_002_048_010, "赠送金余额不足,请选择其他支付方式!");
|
||||
public static final ErrorCode PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT = new ErrorCode(1_002_048_010, "可用金(现金余额+赠送金)余额不足,请选择其他支付方式!");
|
||||
|
||||
//=========== 产品相关 1-002-045-000 ============
|
||||
public static final ErrorCode PRODUCTS_NO_EXIST = new ErrorCode(1_002_045_001, "当前产品不存在");
|
||||
|
||||
+6
@@ -48,4 +48,10 @@ public enum DurationUnitEnum implements IntArrayValuable {
|
||||
return Objects.equals(DAY.name, name);
|
||||
}
|
||||
|
||||
public static DurationUnitEnum fromStatus(Integer status) {
|
||||
return Arrays.stream(values())
|
||||
.filter(bean -> Objects.equals(bean.status, status))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
<groupId>com.cf.imes</groupId>
|
||||
<artifactId>cf-spring-boot-starter-biz-ip</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.cf.imes</groupId>
|
||||
<artifactId>cf-spring-boot-starter-biz-id</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Web 相关 -->
|
||||
|
||||
@@ -244,6 +248,10 @@
|
||||
<groupId>com.cf.imes</groupId>
|
||||
<artifactId>cf-spring-boot-starter-biz-ip</artifactId>
|
||||
</include>
|
||||
<include>
|
||||
<groupId>com.cf.imes</groupId>
|
||||
<artifactId>cf-spring-boot-starter-biz-id</artifactId>
|
||||
</include>
|
||||
<include>
|
||||
<groupId>com.cf.imes</groupId>
|
||||
<artifactId>cf-spring-boot-starter-biz-operatelog</artifactId>
|
||||
|
||||
+15
@@ -21,5 +21,20 @@ public class OrganAmountRespVO {
|
||||
|
||||
@Schema(description = "产品有效期")
|
||||
private String productEfficientDate;
|
||||
|
||||
/**
|
||||
* 正常时间,不加赠送时间
|
||||
*/
|
||||
@Schema(description = "产品时长")
|
||||
private String duration;
|
||||
|
||||
@Schema(description = "产品时长单位")
|
||||
private Integer durationUnit;
|
||||
|
||||
@Schema(description = "赠送时长")
|
||||
private String giftDuration;
|
||||
|
||||
@Schema(description = "赠送时长单位")
|
||||
private Integer giftDurationUnit;
|
||||
}
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.cf.imes.module.system.controller.admin.funds.organamount.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* @author Gqr
|
||||
* @since 2025/8/14 15:20
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class OrganPayAmountRespVO {
|
||||
/**
|
||||
* 可用余额
|
||||
*/
|
||||
private BigDecimal availableAmount;
|
||||
|
||||
/**
|
||||
* 现金余额
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 赠送金余额
|
||||
*/
|
||||
private BigDecimal giftAmount;
|
||||
}
|
||||
-2
@@ -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.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
@@ -10,7 +9,6 @@ import java.math.BigDecimal;
|
||||
* @author Gqr
|
||||
* @since 2025/8/13 16:46
|
||||
*/
|
||||
@Schema(description = "管理后台 - 组织充值后余额信息 Response VO")
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class OrganRechargeAmountRespVO {
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package com.cf.imes.module.system.controller.admin.funds.products.vo.product;
|
||||
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author Gqr
|
||||
* @since 2025/8/14 15:22
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class ProductProcessorRespVO {
|
||||
/**
|
||||
* 购买记录
|
||||
*/
|
||||
private PurchaseRecordDO purchaseRecordDO;
|
||||
|
||||
/**
|
||||
* 收支明细
|
||||
*/
|
||||
private IncomeExpenseDetailsDO incomeExpenseDetailsDO;
|
||||
|
||||
/**
|
||||
* 外部支付信息
|
||||
*/
|
||||
private PayOrderDO payOrderDO;
|
||||
|
||||
/**
|
||||
* 组织资金
|
||||
*/
|
||||
private OrganAmountDO organAmountDO;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.cf.imes.module.system.controller.admin.funds.purchase;
|
||||
|
||||
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
|
||||
/**
|
||||
* @author 软件购买接口
|
||||
*/
|
||||
public interface PurchaseService {
|
||||
/**
|
||||
* 查询购买记录
|
||||
*
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
PurchaseRecordDO getRecord(Long id);
|
||||
|
||||
/**
|
||||
* 创建购买记录
|
||||
*
|
||||
* @param purchaseRecordDO
|
||||
*/
|
||||
void createRecord(PurchaseRecordDO purchaseRecordDO);
|
||||
|
||||
/**
|
||||
* 更新购买记录为可开票
|
||||
*
|
||||
* @param purchaseRecordId
|
||||
* @param organId
|
||||
*/
|
||||
void updateRecordInvoicable(Long purchaseRecordId, Long organId);
|
||||
|
||||
/**
|
||||
* 查询组织下最早购买产品定价规则
|
||||
*
|
||||
* @param organId
|
||||
* @return
|
||||
*/
|
||||
OrgEarliestPurchaseProductRespVO getOrgEarliestProductDetail(Long organId);
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.cf.imes.module.system.controller.admin.funds.purchase;
|
||||
|
||||
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.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsDetailMapper;
|
||||
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
|
||||
import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author 软件购买接口 实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PurchaseServiceImpl implements PurchaseService {
|
||||
|
||||
@Resource
|
||||
private PurchaseRecordMapper purchaseRecordMapper;
|
||||
|
||||
@Resource
|
||||
private ProductsDetailMapper productsDetailMapper;
|
||||
|
||||
@Override
|
||||
public PurchaseRecordDO getRecord(Long id) {
|
||||
return purchaseRecordMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createRecord(PurchaseRecordDO purchaseRecordDO) {
|
||||
purchaseRecordMapper.insert(purchaseRecordDO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateRecordInvoicable(Long purchaseRecordId, Long organId) {
|
||||
purchaseRecordMapper.update(new LambdaUpdateWrapper<PurchaseRecordDO>()
|
||||
.eq(PurchaseRecordDO::getId, purchaseRecordId)
|
||||
.eq(PurchaseRecordDO::getOrganId, organId)
|
||||
.set(PurchaseRecordDO::getIsInvocing, InvoiceStatusEnum.INVOICABLE.getStatus()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrgEarliestPurchaseProductRespVO getOrgEarliestProductDetail(Long organId) {
|
||||
// 查询购买记录中还有效的最早一条
|
||||
PurchaseRecordDO purchaseRecordDO = purchaseRecordMapper.selectOne(new LambdaQueryWrapper<PurchaseRecordDO>()
|
||||
.eq(PurchaseRecordDO::getOrganId, organId)
|
||||
.gt(PurchaseRecordDO::getEndTime, LocalDateTime.now())
|
||||
.orderByAsc(PurchaseRecordDO::getCreateTime)
|
||||
.last("limit 1"));
|
||||
if (ObjectUtil.isNotNull(purchaseRecordDO)) {
|
||||
return new OrgEarliestPurchaseProductRespVO(purchaseRecordDO, productsDetailMapper.selectById(purchaseRecordDO.getProductDetailsId()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.cf.imes.module.system.controller.admin.funds.purchase.vo;
|
||||
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 查询组织下最早购买产品记录和关联定价规则 response vo
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/8/19 17:10
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class OrgEarliestPurchaseProductRespVO {
|
||||
/**
|
||||
* 购买记录
|
||||
*/
|
||||
private PurchaseRecordDO purchaseRecordDO;
|
||||
|
||||
/**
|
||||
* 产品定价规则
|
||||
*/
|
||||
private ProductsDetailDO productsDetailDO;
|
||||
}
|
||||
+7
-6
@@ -2,6 +2,7 @@ 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;
|
||||
@@ -44,12 +45,12 @@ 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) {
|
||||
PayOrderRespDTO payOrderRespDTO = payOrderService.productPay(payProductOrderUnifiedReqVO);
|
||||
return success(payOrderRespDTO);
|
||||
}
|
||||
|
||||
@GetMapping("/order/{channelCode}/{outTradeNo}")
|
||||
@Operation(summary = "查询支付订单")
|
||||
|
||||
+7
-4
@@ -20,11 +20,14 @@ public class PayProductOrderUnifiedReqVO {
|
||||
* 产品定价规则编号
|
||||
*/
|
||||
@Schema(description = "产品定价规则编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
@NotEmpty(message = "产品定价规则编号不能为空")
|
||||
@NotNull(message = "产品定价规则编号不能为空")
|
||||
private Long productDetailsId;
|
||||
|
||||
@Schema(description = "支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@Schema(description = "产品支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@PayProductChannelCodeValid
|
||||
@NotNull(message = "支付渠道编码不能为空")
|
||||
private Integer channelCode;
|
||||
@NotNull(message = "产品支付渠道编码不能为空")
|
||||
private Integer productChannelCode;
|
||||
|
||||
@Schema(description = "机构编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||
private Long organId;
|
||||
}
|
||||
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
package com.cf.imes.module.system.dal.dataobject.funds.purchase;
|
||||
|
||||
|
||||
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;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* @author 购买记录表
|
||||
*/
|
||||
@TableName("purchase_record")
|
||||
@KeySequence("purchase_record_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@ToString(callSuper = true)
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class PurchaseRecordDO extends BaseDO {
|
||||
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId
|
||||
private Long id;
|
||||
|
||||
|
||||
/**
|
||||
* 组织ID
|
||||
*/
|
||||
private Long organId;
|
||||
|
||||
|
||||
// /**
|
||||
// * 用户ID-发票申请人ID
|
||||
// */
|
||||
// private Long userId;
|
||||
//
|
||||
//
|
||||
// /**
|
||||
// * 用户姓名
|
||||
// */
|
||||
// private Long userName;
|
||||
|
||||
|
||||
/**
|
||||
* 产品ID
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
|
||||
/**
|
||||
* 产品明细ID
|
||||
*/
|
||||
private Long productDetailsId;
|
||||
|
||||
|
||||
/**
|
||||
* 产品名称
|
||||
*/
|
||||
private String productName;
|
||||
|
||||
|
||||
/**
|
||||
* 购买时长
|
||||
*/
|
||||
private String purchaseDuration;
|
||||
|
||||
/**
|
||||
* 购买时长单位
|
||||
*/
|
||||
private Integer purchaseDurationUnit;
|
||||
|
||||
/**
|
||||
* 赠送时长
|
||||
*/
|
||||
private String giftDuration;
|
||||
|
||||
/**
|
||||
* 赠送时间单位
|
||||
*/
|
||||
private Integer giftDurationUnit;
|
||||
|
||||
|
||||
/**
|
||||
* 产品开始时间
|
||||
*/
|
||||
private LocalDateTime startTime;
|
||||
|
||||
|
||||
/**
|
||||
* 产品结束时间
|
||||
*/
|
||||
private LocalDateTime endTime;
|
||||
|
||||
|
||||
/**
|
||||
* 支付方式
|
||||
*/
|
||||
private Integer paymentMethod;
|
||||
|
||||
|
||||
/**
|
||||
* 订单总额
|
||||
*/
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
|
||||
/**
|
||||
* 现金余额支出
|
||||
*/
|
||||
private BigDecimal accountBalanceSpent;
|
||||
|
||||
|
||||
/**
|
||||
* 支付宝支出
|
||||
*/
|
||||
private BigDecimal alipaySpent;
|
||||
|
||||
|
||||
/**
|
||||
* 微信支出
|
||||
*/
|
||||
private BigDecimal wechatSpent;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 赠送金
|
||||
*/
|
||||
private BigDecimal giftMoneySpent;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 货币类型
|
||||
*/
|
||||
private Integer currency;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 货币单位
|
||||
*/
|
||||
private String currencyUnit;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 是否开票
|
||||
*/
|
||||
private Integer isInvocing;
|
||||
|
||||
|
||||
}
|
||||
+1
-1
@@ -78,7 +78,7 @@ public class IncomeExpenseDetailsDO extends BaseDO {
|
||||
/**
|
||||
* 支出/入账赠送金金额
|
||||
*/
|
||||
private BigDecimal bonusAmountChange;
|
||||
private BigDecimal giftAmountChange;
|
||||
|
||||
|
||||
/**
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.cf.imes.module.system.dal.mysql.funds.purchase;
|
||||
|
||||
import com.cf.imes.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* 购买记录 Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PurchaseRecordMapper extends BaseMapperX<PurchaseRecordDO> {
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
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 InvoiceStatusEnum {
|
||||
|
||||
|
||||
/**
|
||||
* 可开票
|
||||
*/
|
||||
INVOICABLE(0, "可开票"),
|
||||
/**
|
||||
* 待开票
|
||||
*/
|
||||
PENDINGINVOICING(1, "待开票"),
|
||||
/**
|
||||
* 已开票
|
||||
*/
|
||||
INVOICINGSUCCESSFUL(2,"已开票"),
|
||||
/**
|
||||
* 开票失败
|
||||
*/
|
||||
INVOICINGFAILED(3,"开票失败");
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private final Integer status;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
|
||||
|
||||
public static InvoiceStatusEnum fromType(Integer status) {
|
||||
if (status == null) {
|
||||
return null;
|
||||
}
|
||||
for (InvoiceStatusEnum value : values()) {
|
||||
if (ObjectUtil.equal(value.getStatus(), status)) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+6
@@ -2,10 +2,16 @@ 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
|
||||
|
||||
public static final String PAY_ORDER_PREFIX_FORMAT = "P%dO%s%s";
|
||||
|
||||
public static final String PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY = "purchaseRecordId"; //支付passback 购买记录key
|
||||
}
|
||||
+1
-1
@@ -61,6 +61,6 @@ public enum PayOrderStatusEnum {
|
||||
* @return 是否支付关闭
|
||||
*/
|
||||
public static boolean isClosed(Integer status) {
|
||||
return Objects.equals(status, SUCCESS.getStatus()) || Objects.equals(status, FAIL.getStatus());
|
||||
return Objects.equals(status, FAIL.getStatus());
|
||||
}
|
||||
}
|
||||
|
||||
+48
-10
@@ -14,34 +14,62 @@ import lombok.Getter;
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum PayProductChannelCodeEnum {
|
||||
/**
|
||||
* 账户余额
|
||||
*/
|
||||
BALANCE_ONLY(0),
|
||||
|
||||
/**
|
||||
* 赠送金
|
||||
*/
|
||||
GIFT_ONLY(1),
|
||||
|
||||
/**
|
||||
* 支付宝
|
||||
*/
|
||||
ALIPAY_PC(0),
|
||||
ALIPAY_PC(2),
|
||||
/**
|
||||
* 微信
|
||||
*/
|
||||
WECHAT_NATIVE(1),
|
||||
WECHAT_NATIVE(3),
|
||||
|
||||
/**
|
||||
* 账户余额
|
||||
* 现金+赠送金
|
||||
*/
|
||||
BALANCE_ONLY(2),
|
||||
BALANCE_AND_GIFT(4),
|
||||
|
||||
/**
|
||||
* 账户余额+支付宝
|
||||
* 现金+支付宝
|
||||
*/
|
||||
BALANCE_AND_ALIPAY(3),
|
||||
BALANCE_AND_ALIPAY(5),
|
||||
|
||||
/**
|
||||
* 账户余额+微信
|
||||
* 现金+微信
|
||||
*/
|
||||
BALANCE_AND_WECHAT(4);
|
||||
BALANCE_AND_WECHAT(6),
|
||||
|
||||
/**
|
||||
* 赠送金+支付宝
|
||||
*/
|
||||
GIFT_AND_ALIPAY(7),
|
||||
|
||||
/**
|
||||
* 赠送金+微信
|
||||
*/
|
||||
GIFT_AND_WECHAT(8),
|
||||
|
||||
/**
|
||||
* 现金+赠送金+支付宝
|
||||
*/
|
||||
BALANCE_AND_GIFT_AND_ALIPAY(9),
|
||||
|
||||
/**
|
||||
* 现金+赠送金+微信
|
||||
*/
|
||||
BALANCE_AND_GIFT_AND_WECHAT(10);
|
||||
|
||||
@EnumValue
|
||||
private final Integer code;
|
||||
|
||||
@JsonCreator
|
||||
public static PayProductChannelCodeEnum fromType(Integer type) {
|
||||
for (PayProductChannelCodeEnum value : values()) {
|
||||
if (value.getCode().equals(type)) {
|
||||
@@ -50,4 +78,14 @@ public enum PayProductChannelCodeEnum {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 产品购买渠道是否包含支付宝支付方式
|
||||
*
|
||||
* @param code
|
||||
* @return
|
||||
*/
|
||||
public static boolean isAliPay(Integer code) {
|
||||
return code.equals(ALIPAY_PC.code) || code.equals(BALANCE_AND_ALIPAY.code) || code.equals(GIFT_AND_ALIPAY.code) || code.equals(BALANCE_AND_GIFT_AND_ALIPAY.code);
|
||||
}
|
||||
}
|
||||
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* 属于 system 模块的 framework 封装
|
||||
*
|
||||
* @author 晨丰科技
|
||||
*/
|
||||
package com.cf.imes.module.system.framework;
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.cf.imes.module.system.framework.snowflake.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* mybatis id策略配置类
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/8/18 17:33
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "mybatis-plus.global-config.db-config")
|
||||
public class MybatisIdProperties {
|
||||
private long workerId;
|
||||
private long datacenterId;
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* 属于 system 模块的 framework.snowflake 封装
|
||||
*
|
||||
* @author 晨丰科技
|
||||
*/
|
||||
package com.cf.imes.module.system.framework.snowflake;
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package com.cf.imes.module.system.job.pay;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.cf.imes.module.system.service.pay.PayOrderService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 支付订单的过期 Job
|
||||
*
|
||||
* 支付超过过期时间时,支付渠道是不会通知进行过期,所以需要定时进行过期关闭。
|
||||
*
|
||||
* @author
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class PayOrderExpireJob {
|
||||
|
||||
@Resource
|
||||
private PayOrderService payOrderService;
|
||||
|
||||
// @Scheduled(cron = "0 0/1 * * * ?")
|
||||
public String execute() {
|
||||
int count = payOrderService.expirePayOrder();
|
||||
log.info("[execute][支付过期 ({}) 个]", count);
|
||||
return CharSequenceUtil.format("支付过期 ({}) 个",count);
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@ import com.cf.imes.framework.common.exception.ServiceException;
|
||||
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;
|
||||
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
|
||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.cf.imes.module.system.controller.admin.funds.advertisement.vo.*;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.advertisement.AdvertisementDO;
|
||||
@@ -13,7 +14,6 @@ import com.cf.imes.module.system.dal.mysql.funds.advertisement.AdvertisementTask
|
||||
import com.cf.imes.module.system.enums.advertisement.AdvertisementStatusEnum;
|
||||
import com.cf.imes.module.system.mq.producer.advertisement.AdvertisementProducer;
|
||||
import com.cf.imes.module.system.service.dict.DictDataService;
|
||||
import com.cf.imes.module.system.util.SnowflakeIdWorker3rd;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -86,7 +86,7 @@ public class AdvertisementServiceImpl implements AdvertisementService{
|
||||
|
||||
AssertUtils.empty(aDo,ADVERTISEMENT_NAME_IS_EXIST);
|
||||
|
||||
Long id = idWorker.nextId();
|
||||
long id = idWorker.nextId();
|
||||
|
||||
String filePath = "";
|
||||
if(createReqVO.getAdImageFile() != null) {
|
||||
|
||||
+18
-1
@@ -21,7 +21,7 @@ public interface OrganAmountService {
|
||||
OrganAmountDO validOrganAmount(Long organId);
|
||||
|
||||
/**
|
||||
* 更新机构余额
|
||||
* 机构充值
|
||||
*
|
||||
* @param organId
|
||||
* @param amount 充值金额
|
||||
@@ -29,6 +29,23 @@ public interface OrganAmountService {
|
||||
*/
|
||||
OrganRechargeAmountRespVO recharge(Long organId, BigDecimal amount);
|
||||
|
||||
/**
|
||||
* 组织购买产品
|
||||
*
|
||||
* @param organAmountDO
|
||||
* @return
|
||||
*/
|
||||
void productPay(OrganAmountDO organAmountDO);
|
||||
|
||||
/**
|
||||
* 产品外部支付失败更新余额/赠送金
|
||||
*
|
||||
* @param organId
|
||||
* @param balance
|
||||
* @param gift
|
||||
*/
|
||||
OrganRechargeAmountRespVO refundProductOrganAmountBalanceAndGift(Long organId, BigDecimal balance, BigDecimal gift);
|
||||
|
||||
OrganAmountRespVO getOrganAmount(Long organId);
|
||||
|
||||
|
||||
|
||||
+74
-1
@@ -1,15 +1,23 @@
|
||||
package com.cf.imes.module.system.service.funds.organamount;
|
||||
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
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.exception.ServiceException;
|
||||
import com.cf.imes.framework.common.util.Assert.AssertUtils;
|
||||
import com.cf.imes.framework.common.util.date.DateUtils;
|
||||
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.controller.admin.funds.purchase.PurchaseService;
|
||||
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import com.cf.imes.module.system.dal.mysql.funds.organamount.OrganAmountMapper;
|
||||
import com.cf.imes.module.system.service.organ.OrganService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -19,8 +27,10 @@ import jakarta.annotation.Resource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
|
||||
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_PRODUCT_PAY_AMOUNT_CHANGE_ERROR;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORGAN_RECHARGE_AMOUNT_UPDATE_ERROR;
|
||||
|
||||
/**
|
||||
@@ -37,10 +47,11 @@ public class OrganAmountServiceImpl implements OrganAmountService {
|
||||
@Resource
|
||||
private OrganService organService;
|
||||
|
||||
|
||||
@Resource
|
||||
private ExecutorFundsApi executorFundsApi;
|
||||
|
||||
@Resource
|
||||
private PurchaseService purchaseService;
|
||||
|
||||
@Override
|
||||
public OrganAmountDO validOrganAmount(Long organId) {
|
||||
@@ -85,6 +96,54 @@ public class OrganAmountServiceImpl implements OrganAmountService {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void productPay(OrganAmountDO organAmountDO) {
|
||||
int updated = organAmountMapper.update(new LambdaUpdateWrapper<OrganAmountDO>()
|
||||
.eq(OrganAmountDO::getOrganId, organAmountDO.getOrganId())
|
||||
.eq(OrganAmountDO::getVersion, organAmountDO.getVersion())
|
||||
.set(OrganAmountDO::getAmount, organAmountDO.getAmount())
|
||||
.set(OrganAmountDO::getGiftAmount, organAmountDO.getGiftAmount())
|
||||
.set(OrganAmountDO::getVersion, organAmountDO.getVersion() + 1));
|
||||
if (updated == 0) {
|
||||
throw new ServiceException(ORGAN_PRODUCT_PAY_AMOUNT_CHANGE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganRechargeAmountRespVO refundProductOrganAmountBalanceAndGift(Long organId, BigDecimal balance, BigDecimal gift) {
|
||||
OrganAmountDO organAmountDO = organAmountMapper.selectOne(new LambdaQueryWrapper<OrganAmountDO>().eq(OrganAmountDO::getOrganId, organId));
|
||||
BigDecimal newAmount = organAmountDO.getAmount().add(balance);
|
||||
BigDecimal newGift = organAmountDO.getGiftAmount().add(gift);
|
||||
|
||||
int updated = organAmountMapper.update(new LambdaUpdateWrapper<OrganAmountDO>()
|
||||
.eq(OrganAmountDO::getOrganId, organId)
|
||||
.eq(OrganAmountDO::getVersion, organAmountDO.getVersion())
|
||||
.set(OrganAmountDO::getAmount, newAmount)
|
||||
.set(OrganAmountDO::getGiftAmount, newGift)
|
||||
.set(OrganAmountDO::getVersion, organAmountDO.getVersion() + 1));
|
||||
// 1. 首次更新失败,认为出现并发,再查一次再更新
|
||||
if (updated == 0) {
|
||||
organAmountDO = organAmountMapper.selectOne(new LambdaQueryWrapper<OrganAmountDO>().eq(OrganAmountDO::getOrganId, organId));
|
||||
newAmount = organAmountDO.getAmount().add(balance);
|
||||
newGift = organAmountDO.getGiftAmount().add(gift);
|
||||
|
||||
updated = organAmountMapper.update(new LambdaUpdateWrapper<OrganAmountDO>()
|
||||
.eq(OrganAmountDO::getOrganId, organId)
|
||||
.eq(OrganAmountDO::getVersion, organAmountDO.getVersion())
|
||||
.set(OrganAmountDO::getAmount, newAmount)
|
||||
.set(OrganAmountDO::getGiftAmount, newGift)
|
||||
.set(OrganAmountDO::getVersion, organAmountDO.getVersion() + 1));
|
||||
// 2. 第二次更新失败,资金表可能不存在
|
||||
if (updated == 0) {
|
||||
throw exception(ORGAN_RECHARGE_AMOUNT_UPDATE_ERROR);
|
||||
} else {
|
||||
return new OrganRechargeAmountRespVO(newAmount.add(newGift), newAmount, newGift);
|
||||
}
|
||||
} else {
|
||||
return new OrganRechargeAmountRespVO(newAmount.add(newGift), newAmount, newGift);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public OrganAmountRespVO getOrganAmount(Long organId) {
|
||||
|
||||
@@ -94,6 +153,20 @@ public class OrganAmountServiceImpl implements OrganAmountService {
|
||||
|
||||
OrganAmountRespVO organAmountRespVO = BeanUtils.toBean(organAmountDO, OrganAmountRespVO.class);
|
||||
|
||||
// 查询组织最早还有效的产品购买记录
|
||||
OrgEarliestPurchaseProductRespVO orgEarliestProductDetail = purchaseService.getOrgEarliestProductDetail(organId);
|
||||
if (ObjectUtil.isNotNull(orgEarliestProductDetail)) {
|
||||
// 购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = orgEarliestProductDetail.getPurchaseRecordDO();
|
||||
// 购买记录关联的定价规则
|
||||
ProductsDetailDO productsDetailDO = orgEarliestProductDetail.getProductsDetailDO();
|
||||
|
||||
organAmountRespVO.setProductEfficientDate(LocalDateTimeUtil.format(purchaseRecordDO.getEndTime(), NORM_DATETIME_FORMATTER));
|
||||
organAmountRespVO.setDuration(productsDetailDO.getDuration());
|
||||
organAmountRespVO.setDurationUnit(productsDetailDO.getDurationUnit());
|
||||
organAmountRespVO.setGiftDuration(productsDetailDO.getGiftDuration());
|
||||
organAmountRespVO.setGiftDurationUnit(productsDetailDO.getGiftDurationUnit());
|
||||
}
|
||||
return organAmountRespVO;
|
||||
|
||||
}
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ package com.cf.imes.module.system.service.funds.products;
|
||||
import com.cf.imes.framework.common.pojo.PageResult;
|
||||
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsPageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsSaveReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
@@ -65,5 +66,5 @@ public interface ProductsDetailService {
|
||||
*
|
||||
* @param productId
|
||||
*/
|
||||
void validateProductExist(Long productId);
|
||||
ProductsDO validateProductExist(Long productId);
|
||||
}
|
||||
|
||||
+3
-1
@@ -170,7 +170,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
|
||||
* @param productId
|
||||
*/
|
||||
@Override
|
||||
public void validateProductExist(Long productId) {
|
||||
public ProductsDO validateProductExist(Long productId) {
|
||||
ProductsDO productsDO = productsMapper.selectOne(new LambdaQueryWrapperX<ProductsDO>()
|
||||
.eq(ProductsDO::getId, productId)
|
||||
.eq(ProductsDO::getDeleted, false));
|
||||
@@ -180,6 +180,8 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
|
||||
if (productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) {
|
||||
throw exception(PRODUCTS_DETAIL_NO_UPDATE);
|
||||
}
|
||||
|
||||
return productsDO;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -3,12 +3,12 @@ package com.cf.imes.module.system.service.funds.recharge;
|
||||
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;
|
||||
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
|
||||
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargePageReqVO;
|
||||
import com.cf.imes.module.system.controller.admin.funds.recharge.vo.RechargeSaveReqVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.recharge.GiftMoneyDetailsDO;
|
||||
import com.cf.imes.module.system.dal.mysql.funds.recharge.GiftMoneyDetailsMapper;
|
||||
import com.cf.imes.module.system.util.SnowflakeIdWorker3rd;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
@@ -36,7 +36,7 @@ public class RechargeActiveServiceImpl implements RechargeActiveService{
|
||||
@Override
|
||||
public Long create(RechargeSaveReqVO createReqVO) {
|
||||
|
||||
Long id = idWorker.nextId();
|
||||
long id = idWorker.nextId();
|
||||
|
||||
GiftMoneyDetailsDO giftMoneyDetailsDO = giftMoneyDetailsMapper.selectOne(new LambdaQueryWrapperX<GiftMoneyDetailsDO>()
|
||||
.eq(GiftMoneyDetailsDO::getDeleted, false)
|
||||
|
||||
+8
-1
@@ -18,7 +18,7 @@ public interface PayOrderService {
|
||||
*
|
||||
* @param payProductOrderUnifiedReqVO
|
||||
*/
|
||||
void productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO);
|
||||
PayOrderRespDTO productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO);
|
||||
|
||||
/**
|
||||
* 账户充值支付
|
||||
@@ -44,4 +44,11 @@ public interface PayOrderService {
|
||||
* @return
|
||||
*/
|
||||
void doOrderNotify(Integer channelCode, Map<String, String> params, String body);
|
||||
|
||||
/**
|
||||
* 处理过期订单
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int expirePayOrder();
|
||||
}
|
||||
|
||||
+191
-9
@@ -15,6 +15,7 @@ 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;
|
||||
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
|
||||
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;
|
||||
@@ -23,16 +24,22 @@ 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.funds.organamount.vo.OrganRechargeAmountRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO;
|
||||
import com.cf.imes.module.system.controller.admin.funds.purchase.PurchaseService;
|
||||
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.organamount.OrganAmountDO;
|
||||
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.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
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.PayProductChannelCodeEnum;
|
||||
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
|
||||
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
|
||||
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;
|
||||
@@ -52,6 +59,7 @@ import jakarta.validation.Validator;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -77,6 +85,9 @@ import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_RECHARGE_OR
|
||||
@Slf4j
|
||||
public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
// 外部payNo正则
|
||||
private static final Pattern pattern = Pattern.compile("P(\\d+)O");
|
||||
|
||||
@Resource
|
||||
private PayHandlerFactory payHandlerFactory;
|
||||
|
||||
@@ -86,8 +97,6 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
@Resource
|
||||
private Validator validator;
|
||||
|
||||
private static final String PAY_ORDER_PREFIX_FORMAT = "P%dO%s%s";
|
||||
|
||||
@Resource
|
||||
private ChenfengAlipayConfig alipayConfig;
|
||||
|
||||
@@ -103,18 +112,78 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
@Resource
|
||||
private IncomeExpenseService incomeExpenseService;
|
||||
|
||||
@Resource
|
||||
private PurchaseService purchaseService;
|
||||
|
||||
@Resource
|
||||
private SnowflakeIdWorker3rd snowflakeIdWorker3rd;
|
||||
|
||||
@Resource
|
||||
private MybatisIdProperties mybatisIdProperties;
|
||||
|
||||
@Override
|
||||
public void productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public PayOrderRespDTO productPay(PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
|
||||
Long organId = payProductOrderUnifiedReqVO.getOrganId();
|
||||
organId = ObjectUtil.isNull(organId) ? SecurityFrameworkUtils.getUserOrganId() : organId;
|
||||
|
||||
// 主动校验一次入参
|
||||
ValidationUtils.validate(validator, payProductOrderUnifiedReqVO);
|
||||
|
||||
// 校验产品和产品明细
|
||||
ProductsDetailDO productsDetailDO = productsDetailService.validateProductDetailsExists(payProductOrderUnifiedReqVO.getProductDetailsId());
|
||||
productsDetailService.validateProductExist(productsDetailDO.getProductId());
|
||||
ProductsDO productsDO = productsDetailService.validateProductExist(productsDetailDO.getProductId());
|
||||
|
||||
// 查询组织余额信息
|
||||
OrganAmountRespVO organAmount = organAmountService.getOrganAmount(SecurityFrameworkUtils.getUserOrganId());
|
||||
OrganAmountDO organAmount = organAmountService.validOrganAmount(organId);
|
||||
|
||||
// 根据购买类型做价格处理
|
||||
PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, organAmount, organId, snowflakeIdWorker3rd, payConfig, mybatisIdProperties);
|
||||
ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode()));
|
||||
|
||||
// 记录入库,如果有外部支付发起请求二维码
|
||||
// 购买记录入库
|
||||
purchaseService.createRecord(productProcessorRespVO.getPurchaseRecordDO());
|
||||
// 收支明细入库
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = productProcessorRespVO.getIncomeExpenseDetailsDO();
|
||||
if (ObjectUtil.isNotNull(incomeExpenseDetailsDO)) {
|
||||
incomeExpenseService.createDetail(incomeExpenseDetailsDO);
|
||||
}
|
||||
|
||||
// 更新组织资金,纯第三方支付不需要更新余额
|
||||
OrganAmountDO finalOrganAmountDO = productProcessorRespVO.getOrganAmountDO();
|
||||
if (ObjectUtil.isNotNull(finalOrganAmountDO)) {
|
||||
organAmountService.productPay(finalOrganAmountDO);
|
||||
}
|
||||
|
||||
// 如果有外部支付,发起请求
|
||||
PayOrderDO payOrder = productProcessorRespVO.getPayOrderDO();
|
||||
if(ObjectUtil.isNotNull(payOrder)) {
|
||||
payOrderMapper.insert(payOrder);
|
||||
|
||||
// 拼接充值个性化参数
|
||||
Map<String, Object> passbackParams = new HashMap<>();
|
||||
passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_TRADE_TYPE_KEY, TradeTypeEnum.PRODUCT.getCode());// 交易类型
|
||||
passbackParams.put(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY, productProcessorRespVO.getPurchaseRecordDO().getId());// 购买记录id,用于更新开票状态
|
||||
|
||||
// 发起渠道方支付
|
||||
PayOrderUnifiedReqDTO payOrderUnifiedReqDTO = null;
|
||||
Integer channelCode = PayProductChannelCodeEnum.isAliPay(payProductOrderUnifiedReqVO.getProductChannelCode()) ? PayChannelCodeEnum.ALIPAY_PC.getCode() : PayChannelCodeEnum.WECHAT_NATIVE.getCode();
|
||||
try {
|
||||
// 获取渠道方处理器
|
||||
AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode);
|
||||
|
||||
payOrderUnifiedReqDTO = createPayOrderUnifiedReqDTO(payOrder, passbackParams);
|
||||
return payHandler.unifiedOrder(payOrderUnifiedReqDTO);
|
||||
} catch (Throwable e) {
|
||||
// 系统异常,则包装成 PayException 异常抛出
|
||||
log.error("[unifiedOrder][客户端({}) request({}) 发起支付异常]",
|
||||
channelCode, JSON.toJSONString(payOrderUnifiedReqDTO), e);
|
||||
throw new PayException(PAY_ORDER_SUBMIT_CHANNEL_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,11 +233,11 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
private PayOrderDO createPayOrder(BigDecimal price, String subject, Integer channelCode) {
|
||||
PayOrderDO payOrderDO = new PayOrderDO();
|
||||
|
||||
Long payOrderId = IdUtil.getSnowflake(0, 0).nextId();
|
||||
Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).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.setPayNo(String.format(PayConstants.PAY_ORDER_PREFIX_FORMAT, organId, payOrderId, DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN)));
|
||||
payOrderDO.setStatus(PayOrderStatusEnum.WAITING);
|
||||
payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime()));
|
||||
payOrderDO.setPrice(price);
|
||||
@@ -276,7 +345,6 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
* @return
|
||||
*/
|
||||
private Long getOrganIdFromPayNo(String outTradeNo) {
|
||||
Pattern pattern = Pattern.compile("P(\\d+)O");
|
||||
Matcher matcher = pattern.matcher(outTradeNo);
|
||||
|
||||
if (matcher.find()) {
|
||||
@@ -333,6 +401,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
notifyRechargeOrderSuccess(order, organId);
|
||||
break;
|
||||
case PRODUCT:
|
||||
notifyProductPayOrderSuccess(organId, passbackParams);
|
||||
break;
|
||||
default:
|
||||
throw ServiceExceptionUtil.exception(PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR, passbackObj.toString());
|
||||
@@ -341,6 +410,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
|
||||
/**
|
||||
* 充值成功回调
|
||||
*
|
||||
* @param payOrderDO
|
||||
* @param organId
|
||||
*/
|
||||
@@ -369,6 +439,19 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
incomeExpenseService.createDetail(incomeExpenseDetailsDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 产品购买成功回调
|
||||
* @param organId
|
||||
* @param passbackParams
|
||||
*/
|
||||
private void notifyProductPayOrderSuccess(Long organId, Map<String, Object> passbackParams) {
|
||||
// 1、更新购买记录的开票状态
|
||||
Object purchaseRecordIdObj = passbackParams.get(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY);
|
||||
if (ObjectUtil.isNotNull(purchaseRecordIdObj)) {
|
||||
purchaseService.updateRecordInvoicable(NumberUtil.parseLong(purchaseRecordIdObj.toString()), organId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付失败回调处理
|
||||
*
|
||||
@@ -413,6 +496,64 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
throw exception(PAY_ORDER_STATUS_IS_NOT_WAITING);
|
||||
}
|
||||
log.debug("[notifyOrderClosed][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:
|
||||
notifyRechargeOrderFail(order, organId);
|
||||
break;
|
||||
case PRODUCT:
|
||||
notifyProductPayOrderFail(organId, passbackParams);
|
||||
break;
|
||||
default:
|
||||
throw ServiceExceptionUtil.exception(PAY_NOTIFY_PASSBACK_TRADETYPE_NOT_SUPPORT_ERROR, passbackObj.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 充值成功回调
|
||||
*
|
||||
* @param payOrderDO
|
||||
* @param organId
|
||||
*/
|
||||
private void notifyRechargeOrderFail(PayOrderDO payOrderDO, Long organId) {
|
||||
// todo 充值成功回调
|
||||
}
|
||||
|
||||
/**
|
||||
* 产品购买失败回调
|
||||
*
|
||||
* @param organId
|
||||
* @param passbackParams
|
||||
*/
|
||||
private void notifyProductPayOrderFail(Long organId, Map<String, Object> passbackParams) {
|
||||
Object purchaseRecordIdObj = passbackParams.get(PayConstants.PAY_PASSBACK_PARAMS_PURCHASE_RECORD_ID_KEY);
|
||||
if (ObjectUtil.isNotNull(purchaseRecordIdObj)) {
|
||||
PurchaseRecordDO record = purchaseService.getRecord(NumberUtil.parseLong(purchaseRecordIdObj.toString()));
|
||||
|
||||
// 1、混合支付失败,退回扣除的现金和赠送金
|
||||
BigDecimal accountBalanceSpent = record.getAccountBalanceSpent();
|
||||
BigDecimal giftMoneySpent = record.getGiftMoneySpent();
|
||||
OrganRechargeAmountRespVO organRefundProductRespVO = organAmountService.refundProductOrganAmountBalanceAndGift(organId, accountBalanceSpent, giftMoneySpent);
|
||||
|
||||
// 2、新增退回的收支记录
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.INCOME.getType())
|
||||
.cashAmountChange(accountBalanceSpent)
|
||||
.giftAmountChange(giftMoneySpent)
|
||||
.availableAmount(organRefundProductRespVO.getAvailableAmount())
|
||||
.amount(organRefundProductRespVO.getAmount())
|
||||
.giftAmount(organRefundProductRespVO.getGiftAmount())
|
||||
.build();
|
||||
incomeExpenseDetailsDO.setCreator(record.getCreator());
|
||||
incomeExpenseDetailsDO.setUpdater(record.getUpdater());
|
||||
incomeExpenseService.createDetail(incomeExpenseDetailsDO);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -428,4 +569,45 @@ public class PayOrderServiceImpl implements PayOrderService {
|
||||
}
|
||||
return organId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int expirePayOrder() {
|
||||
// 1、查询过期的待支付订单
|
||||
List<PayOrderDO> expireOrders = payOrderMapper.selectList(new LambdaQueryWrapper<PayOrderDO>()
|
||||
.eq(PayOrderDO::getStatus, PayOrderStatusEnum.WAITING.getStatus())
|
||||
.lt(PayOrderDO::getExpireTime, LocalDateTime.now()));
|
||||
|
||||
// 2、遍历处理
|
||||
int count = 0;
|
||||
for (PayOrderDO order : expireOrders) {
|
||||
count += expireOrder(order) ? 1 : 0;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步单个支付单
|
||||
*
|
||||
* @param order 支付单
|
||||
* @return 是否已过期
|
||||
*/
|
||||
private boolean expireOrder(PayOrderDO order) {
|
||||
try {
|
||||
// 1、获取支付渠道
|
||||
PayChannelCodeEnum channelCode = order.getChannelCode();
|
||||
AbstractPayHandler payHandler = payHandlerFactory.getPayHandler(channelCode.getCode());
|
||||
|
||||
// 2、调用三方接口,查询支付单状态,是不是已支付
|
||||
PayOrderRespDTO payOrderRespDTO = payHandler.queryPayOrder(order.getPayNo());
|
||||
if (PayOrderStatusEnum.isSuccess(payOrderRespDTO.getStatus())) {
|
||||
notifyOrder(payOrderRespDTO);
|
||||
return false;
|
||||
}
|
||||
log.info("[expireOrder][order({}) 更新为支付关闭失败]", order.getId());
|
||||
return true;
|
||||
} catch (Throwable e) {
|
||||
log.error("[expireOrder][order({}) 过期订单异常]", order.getId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+627
@@ -0,0 +1,627 @@
|
||||
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 com.cf.imes.framework.common.exception.ServiceException;
|
||||
import com.cf.imes.framework.id.core.util.SnowflakeIdWorker3rd;
|
||||
import com.cf.imes.framework.pay.config.ChenfengPayConfig;
|
||||
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
|
||||
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO;
|
||||
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
|
||||
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.dataobject.funds.purchase.PurchaseRecordDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
|
||||
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO;
|
||||
import com.cf.imes.module.system.enums.pay.IncomeExpenseTypeEnum;
|
||||
import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum;
|
||||
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.PayOrderStatusEnum;
|
||||
import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum;
|
||||
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
|
||||
import com.cf.imes.module.system.enums.products.DurationUnitEnum;
|
||||
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_BALANCE_INSUFFICIENT;
|
||||
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT;
|
||||
|
||||
/**
|
||||
* 产品购买类型处理器
|
||||
*
|
||||
* @author Gqr
|
||||
* @since 2025/8/14 10:54
|
||||
*/
|
||||
public class PayProductProcessor {
|
||||
|
||||
private ProductsDO productsDO;
|
||||
|
||||
private ProductsDetailDO productsDetailDO;
|
||||
|
||||
private OrganAmountDO organAmountDO;
|
||||
|
||||
private Long organId;
|
||||
|
||||
private SnowflakeIdWorker3rd snowflakeIdWorker3rd;
|
||||
|
||||
private ChenfengPayConfig payConfig;
|
||||
|
||||
private MybatisIdProperties mybatisIdProperties;
|
||||
|
||||
public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, OrganAmountDO organAmountDO,
|
||||
Long organId, SnowflakeIdWorker3rd snowflakeIdWorker3rd, ChenfengPayConfig payConfig,
|
||||
MybatisIdProperties mybatisIdProperties) {
|
||||
this.productsDO = productsDO;
|
||||
this.productsDetailDO = productsDetailDO;
|
||||
this.organAmountDO = organAmountDO;
|
||||
this.organId = organId;
|
||||
this.snowflakeIdWorker3rd = snowflakeIdWorker3rd;
|
||||
this.payConfig = payConfig;
|
||||
this.mybatisIdProperties = mybatisIdProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据产品支付类型解析出对应的信息,
|
||||
*
|
||||
* @param payType
|
||||
*/
|
||||
public ProductProcessorRespVO processPayment(PayProductChannelCodeEnum payType) {
|
||||
// 需要支付的价格
|
||||
BigDecimal price = productsDetailDO.getCurrentPrice();
|
||||
// 当前组织的余额
|
||||
BigDecimal balance = organAmountDO.getAmount();
|
||||
// 当前组织的赠送金
|
||||
BigDecimal gift = organAmountDO.getGiftAmount();
|
||||
|
||||
switch (payType) {
|
||||
case BALANCE_ONLY:
|
||||
return payByBalance(price, balance);
|
||||
case GIFT_ONLY:
|
||||
return payByGift(price, gift);
|
||||
case ALIPAY_PC:
|
||||
return payByAlipay(price);
|
||||
case WECHAT_NATIVE:
|
||||
return payByWechat(price);
|
||||
case BALANCE_AND_GIFT:
|
||||
return payByBalanceAndGift(price, gift, balance);
|
||||
case BALANCE_AND_ALIPAY:
|
||||
return payByBalanceAndExternal(price, balance, PayChannelCodeEnum.ALIPAY_PC);
|
||||
case BALANCE_AND_WECHAT:
|
||||
return payByBalanceAndExternal(price, balance, PayChannelCodeEnum.WECHAT_NATIVE);
|
||||
case GIFT_AND_ALIPAY:
|
||||
return payByGiftAndExternal(price, gift, PayChannelCodeEnum.ALIPAY_PC);
|
||||
case GIFT_AND_WECHAT:
|
||||
return payByGiftAndExternal(price, gift, PayChannelCodeEnum.WECHAT_NATIVE);
|
||||
case BALANCE_AND_GIFT_AND_ALIPAY:
|
||||
return payByBalanceAndGiftAndExternal(price, gift, balance, PayChannelCodeEnum.ALIPAY_PC);
|
||||
case BALANCE_AND_GIFT_AND_WECHAT:
|
||||
return payByBalanceAndGiftAndExternal(price, gift, balance, PayChannelCodeEnum.WECHAT_NATIVE);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用现金余额支付
|
||||
*
|
||||
* @param price
|
||||
* @param balance
|
||||
*/
|
||||
private ProductProcessorRespVO payByBalance(BigDecimal price, BigDecimal balance) {
|
||||
if (balance.compareTo(price) < 0) {
|
||||
throw new ServiceException(PAY_PRODUCT_BALANCE_INSUFFICIENT);
|
||||
}
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
|
||||
.totalAmount(price)
|
||||
.accountBalanceSpent(price)
|
||||
.isInvocing(InvoiceStatusEnum.INVOICABLE.getStatus())
|
||||
.build();
|
||||
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
|
||||
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
|
||||
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
|
||||
// 新增收支明细
|
||||
// 现金余额减去购买金额
|
||||
BigDecimal newBalance = balance.subtract(price);
|
||||
BigDecimal giftAmount = organAmountDO.getGiftAmount();
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.cashAmountChange(price)
|
||||
.availableAmount(newBalance.add(giftAmount))
|
||||
.amount(newBalance)
|
||||
.giftAmount(giftAmount)
|
||||
.build();
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(newBalance);
|
||||
organAmountDO.setGiftAmount(giftAmount);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用赠送金余额支付
|
||||
*
|
||||
* @param price
|
||||
* @param gift
|
||||
*/
|
||||
private ProductProcessorRespVO payByGift(BigDecimal price, BigDecimal gift) {
|
||||
if (gift.compareTo(price) < 0) {
|
||||
throw new ServiceException(PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT);
|
||||
}
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
|
||||
.totalAmount(price)
|
||||
.accountBalanceSpent(price)
|
||||
.isInvocing(InvoiceStatusEnum.INVOICABLE.getStatus())
|
||||
.build();
|
||||
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
|
||||
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
|
||||
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
|
||||
// 新增收支明细
|
||||
// 赠送金余额减去购买金额
|
||||
BigDecimal newGift = gift.subtract(price);
|
||||
BigDecimal amount = organAmountDO.getAmount();
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.giftAmountChange(price)
|
||||
.availableAmount(amount.add(newGift))
|
||||
.amount(amount)
|
||||
.giftAmount(newGift)
|
||||
.build();
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(amount);
|
||||
organAmountDO.setGiftAmount(newGift);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用支付宝支付
|
||||
*
|
||||
* @param price
|
||||
* @return
|
||||
*/
|
||||
private ProductProcessorRespVO payByAlipay(BigDecimal price) {
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
|
||||
.totalAmount(price)
|
||||
.alipaySpent(price)
|
||||
.build();
|
||||
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
|
||||
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
|
||||
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
|
||||
|
||||
// 新增第三方支付订单
|
||||
PayOrderDO payOrderDO = goExternalPay(price, PayChannelCodeEnum.ALIPAY_PC);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用微信支付
|
||||
*
|
||||
* @param price
|
||||
* @return
|
||||
*/
|
||||
private ProductProcessorRespVO payByWechat(BigDecimal price) {
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
|
||||
.totalAmount(price)
|
||||
.wechatSpent(price)
|
||||
.build();
|
||||
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
|
||||
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
|
||||
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
|
||||
|
||||
// 新增第三方支付订单
|
||||
PayOrderDO payOrderDO = goExternalPay(price, PayChannelCodeEnum.WECHAT_NATIVE);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用现在余额+赠送金余额支付
|
||||
*
|
||||
* @param price
|
||||
* @param gift
|
||||
* @param balance
|
||||
*/
|
||||
private ProductProcessorRespVO payByBalanceAndGift(BigDecimal price, BigDecimal gift, BigDecimal balance) {
|
||||
BigDecimal remaining = price;
|
||||
BigDecimal usedGift = BigDecimal.ZERO;
|
||||
BigDecimal usedBalance = BigDecimal.ZERO;
|
||||
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
|
||||
.totalAmount(price)
|
||||
.isInvocing(InvoiceStatusEnum.INVOICABLE.getStatus())
|
||||
.build();
|
||||
|
||||
// 先用赠送金
|
||||
if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedGift = remaining.min(gift);
|
||||
remaining = remaining.subtract(usedGift);
|
||||
purchaseRecordDO.setGiftMoneySpent(usedGift);
|
||||
}
|
||||
|
||||
// 再用余额
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0 && balance != null && balance.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedBalance = remaining.min(balance);
|
||||
remaining = remaining.subtract(usedBalance);
|
||||
purchaseRecordDO.setAccountBalanceSpent(usedBalance);
|
||||
}
|
||||
|
||||
// 剩下走第三方
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0) {
|
||||
throw new ServiceException(PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT);
|
||||
}
|
||||
|
||||
// 新增收支明细
|
||||
// 减去用掉的得到剩下的
|
||||
BigDecimal newAmounta = balance.subtract(usedBalance);
|
||||
BigDecimal newGift = gift.subtract(usedGift);
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.cashAmountChange(usedBalance)
|
||||
.giftAmountChange(usedGift)
|
||||
.availableAmount(newAmounta.add(newGift))
|
||||
.amount(newAmounta)
|
||||
.giftAmount(newGift)
|
||||
.build();
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(newAmounta);
|
||||
organAmountDO.setGiftAmount(newGift);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用现金余额+第三方
|
||||
*
|
||||
* @param price
|
||||
* @param balance
|
||||
*/
|
||||
private ProductProcessorRespVO payByBalanceAndExternal(BigDecimal price, BigDecimal balance, PayChannelCodeEnum payChannelCodeEnum) {
|
||||
BigDecimal remaining = price;
|
||||
BigDecimal usedBalance = BigDecimal.ZERO;
|
||||
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(payChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode())
|
||||
.totalAmount(price)
|
||||
.build();
|
||||
|
||||
// 先用余额
|
||||
if (balance != null && balance.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedBalance = remaining.min(balance);
|
||||
remaining = remaining.subtract(usedBalance);
|
||||
purchaseRecordDO.setAccountBalanceSpent(usedBalance);
|
||||
}
|
||||
|
||||
// 新增收支明细
|
||||
// 减去用掉的得到剩下的
|
||||
BigDecimal newAmounta = balance.subtract(usedBalance);
|
||||
BigDecimal giftAmount = organAmountDO.getGiftAmount();
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.cashAmountChange(usedBalance)
|
||||
.availableAmount(newAmounta.add(giftAmount))
|
||||
.amount(newAmounta)
|
||||
.giftAmount(giftAmount)
|
||||
.build();
|
||||
|
||||
// 剩下走第三方
|
||||
PayOrderDO payOrderDO = null;
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0) {
|
||||
payOrderDO = goExternalPay(remaining, payChannelCodeEnum);
|
||||
// 记录第三方支付支出
|
||||
if (PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum)) {
|
||||
purchaseRecordDO.setAlipaySpent(remaining);
|
||||
} else {
|
||||
purchaseRecordDO.setWechatSpent(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(newAmounta);
|
||||
organAmountDO.setGiftAmount(giftAmount);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用赠送金余额+第三方
|
||||
*
|
||||
* @param price
|
||||
* @param gift
|
||||
*/
|
||||
private ProductProcessorRespVO payByGiftAndExternal(BigDecimal price, BigDecimal gift, PayChannelCodeEnum payChannelCodeEnum) {
|
||||
BigDecimal remaining = price;
|
||||
BigDecimal usedGift = BigDecimal.ZERO;
|
||||
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(payChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode())
|
||||
.totalAmount(price)
|
||||
.build();
|
||||
|
||||
|
||||
// 先用余额
|
||||
if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedGift = remaining.min(gift);
|
||||
remaining = remaining.subtract(usedGift);
|
||||
purchaseRecordDO.setGiftMoneySpent(usedGift);
|
||||
}
|
||||
|
||||
// 新增收支明细
|
||||
// 减去用掉的得到剩下的
|
||||
BigDecimal newGift = gift.subtract(usedGift);
|
||||
BigDecimal amount = organAmountDO.getAmount();
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.giftAmountChange(usedGift)
|
||||
.availableAmount(amount.add(newGift))
|
||||
.amount(amount)
|
||||
.giftAmount(newGift)
|
||||
.build();
|
||||
|
||||
// 剩下走第三方
|
||||
PayOrderDO payOrderDO = null;
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0) {
|
||||
goExternalPay(remaining, payChannelCodeEnum);
|
||||
// 记录第三方支付支出
|
||||
if (PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum)) {
|
||||
purchaseRecordDO.setAlipaySpent(remaining);
|
||||
} else {
|
||||
purchaseRecordDO.setWechatSpent(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(amount);
|
||||
organAmountDO.setGiftAmount(newGift);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用现在余额+赠送金余额支付
|
||||
*
|
||||
* @param price
|
||||
* @param gift
|
||||
* @param balance
|
||||
*/
|
||||
private ProductProcessorRespVO payByBalanceAndGiftAndExternal(BigDecimal price, BigDecimal gift, BigDecimal balance, PayChannelCodeEnum payChannelCodeEnum) {
|
||||
BigDecimal remaining = price;
|
||||
BigDecimal usedGift = BigDecimal.ZERO;
|
||||
BigDecimal usedBalance = BigDecimal.ZERO;
|
||||
|
||||
// 新增购买记录
|
||||
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
|
||||
.id(IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId())
|
||||
.organId(organId)
|
||||
.productId(productsDO.getId())
|
||||
.productName(productsDO.getProductName())
|
||||
.productDetailsId(productsDetailDO.getId())
|
||||
.purchaseDuration(productsDetailDO.getDuration())
|
||||
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
|
||||
.giftDuration(productsDetailDO.getGiftDuration())
|
||||
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
|
||||
.startTime(LocalDateTime.now())
|
||||
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
|
||||
.totalAmount(price)
|
||||
.build();
|
||||
|
||||
// 先用赠送金
|
||||
if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedGift = remaining.min(gift);
|
||||
remaining = remaining.subtract(usedGift);
|
||||
purchaseRecordDO.setGiftMoneySpent(usedGift);
|
||||
}
|
||||
|
||||
// 再用余额
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0 && balance != null && balance.compareTo(BigDecimal.ZERO) > 0) {
|
||||
usedBalance = remaining.min(balance);
|
||||
remaining = remaining.subtract(usedBalance);
|
||||
purchaseRecordDO.setAccountBalanceSpent(usedBalance);
|
||||
}
|
||||
|
||||
// 新增收支明细
|
||||
// 减去用掉的得到剩下的
|
||||
BigDecimal newAmounta = balance.subtract(usedBalance);
|
||||
BigDecimal newGift = gift.subtract(usedGift);
|
||||
IncomeExpenseDetailsDO incomeExpenseDetailsDO = IncomeExpenseDetailsDO.builder()
|
||||
.organId(organId)
|
||||
.purchaseId(purchaseRecordDO.getId())
|
||||
.tradeType(TradeTypeEnum.PRODUCT.getCode())
|
||||
.incomeExpenseType(IncomeExpenseTypeEnum.EXPENSES.getType())
|
||||
.cashAmountChange(usedBalance)
|
||||
.giftAmountChange(usedGift)
|
||||
.availableAmount(newAmounta.add(newGift))
|
||||
.amount(newAmounta)
|
||||
.giftAmount(newGift)
|
||||
.build();
|
||||
|
||||
// 剩下走第三方
|
||||
PayOrderDO payOrderDO = null;
|
||||
if (remaining.compareTo(BigDecimal.ZERO) > 0) {
|
||||
payOrderDO = goExternalPay(remaining, payChannelCodeEnum);
|
||||
// 记录第三方支付支出
|
||||
if (PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum)) {
|
||||
purchaseRecordDO.setAlipaySpent(remaining);
|
||||
} else {
|
||||
purchaseRecordDO.setWechatSpent(remaining);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新余额
|
||||
organAmountDO.setAmount(newAmounta);
|
||||
organAmountDO.setGiftAmount(newGift);
|
||||
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部支付
|
||||
*
|
||||
* @param price
|
||||
* @param payChannelCodeEnum
|
||||
* @return
|
||||
*/
|
||||
private PayOrderDO goExternalPay(BigDecimal price, PayChannelCodeEnum payChannelCodeEnum) {
|
||||
PayOrderDO payOrderDO = new PayOrderDO();
|
||||
|
||||
Long payOrderId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId();
|
||||
Long organId = SecurityFrameworkUtils.getUserOrganId();
|
||||
|
||||
payOrderDO.setId(payOrderId);
|
||||
payOrderDO.setPayNo(String.format(PayConstants.PAY_ORDER_PREFIX_FORMAT, organId, payOrderId, DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATETIME_PATTERN)));
|
||||
payOrderDO.setStatus(PayOrderStatusEnum.WAITING);
|
||||
payOrderDO.setExpireTime(LocalDateTime.now().plus(payConfig.getPayExpireTime()));
|
||||
payOrderDO.setPrice(price);
|
||||
payOrderDO.setSubject(productsDO.getProductName());
|
||||
payOrderDO.setChannelCode(payChannelCodeEnum);
|
||||
payOrderDO.setOrganId(organId);
|
||||
return payOrderDO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算结束时间
|
||||
*
|
||||
* @param startTime
|
||||
* @param duration
|
||||
* @param durationUnit
|
||||
* @param giftDuration
|
||||
* @param giftDurationUnit
|
||||
* @return
|
||||
*/
|
||||
private static LocalDateTime calculateEndTime(LocalDateTime startTime, String duration, Integer durationUnit,
|
||||
String giftDuration, Integer giftDurationUnit) {
|
||||
|
||||
// 加正常时间
|
||||
if (StringUtils.isNotEmpty(duration) && durationUnit != null) {
|
||||
startTime = addTime(startTime, Integer.parseInt(duration), durationUnit);
|
||||
}
|
||||
|
||||
// 加赠送时间
|
||||
if (StringUtils.isNotEmpty(giftDuration) && giftDurationUnit != null) {
|
||||
startTime = addTime(startTime, Integer.parseInt(giftDuration), giftDurationUnit);
|
||||
}
|
||||
|
||||
return startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于时长和单位累加time
|
||||
*
|
||||
* @param time
|
||||
* @param duration
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
private static LocalDateTime addTime(LocalDateTime time, int duration, int unit) {
|
||||
switch (DurationUnitEnum.fromStatus(unit)) {
|
||||
case YEAR:
|
||||
return time.plusYears(duration);
|
||||
case MONTH:
|
||||
return time.plusMonths(duration);
|
||||
case DAY:
|
||||
return time.plusDays(duration);
|
||||
default:
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+59
-4
@@ -1,12 +1,31 @@
|
||||
package com.cf.imes.module.system.service.pay.factory.impl.wechat;
|
||||
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.date.TemporalAccessorUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.cf.imes.framework.common.util.json.JsonUtils;
|
||||
import com.cf.imes.framework.pay.config.ChenfengWechatPayConfig;
|
||||
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.PayOrderDisplayModeEnum;
|
||||
import com.cf.imes.module.system.enums.pay.PayOrderStatusEnum;
|
||||
import com.cf.imes.module.system.service.pay.factory.impl.AbstractPayHandler;
|
||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||
import com.github.binarywang.wxpay.bean.order.WxPayAppOrderResult;
|
||||
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
|
||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||
import com.github.binarywang.wxpay.service.WxPayService;
|
||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
|
||||
|
||||
/**
|
||||
* 微信native支付处理器
|
||||
@@ -18,9 +37,32 @@ import java.util.Map;
|
||||
@Slf4j
|
||||
public class WechatNativePayHandler extends AbstractPayHandler {
|
||||
|
||||
@Resource
|
||||
private ChenfengWechatPayConfig chenfengWechatPayConfig;
|
||||
|
||||
@Override
|
||||
public PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
|
||||
return null;
|
||||
public PayOrderRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
|
||||
// 构建 WxPayUnifiedOrderRequest 对象
|
||||
WxPayUnifiedOrderRequest request = WxPayUnifiedOrderRequest.newBuilder()
|
||||
.outTradeNo(reqDTO.getOutTradeNo())
|
||||
.body(reqDTO.getSubject())
|
||||
.detail(reqDTO.getBody())
|
||||
.totalFee(reqDTO.getPrice()) // 单位分
|
||||
.timeExpire(TemporalAccessorUtil.format(reqDTO.getExpireTime().atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN))
|
||||
.spbillCreateIp(reqDTO.getUserIp())
|
||||
.notifyUrl(reqDTO.getNotifyUrl())
|
||||
.attach(JSON.toJSONString(reqDTO.getPassbackParams()))
|
||||
.build();
|
||||
|
||||
WxPayService client = new WxPayServiceImpl();
|
||||
client.setConfig(chenfengWechatPayConfig);
|
||||
|
||||
// 执行请求
|
||||
WxPayAppOrderResult response = client.createOrder(request);
|
||||
|
||||
// 转换结果
|
||||
return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), JsonUtils.toJsonString(response),
|
||||
reqDTO.getOutTradeNo(), response);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,7 +71,20 @@ public class WechatNativePayHandler extends AbstractPayHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) {
|
||||
return null;
|
||||
public PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body) throws WxPayException {
|
||||
// 1. 解析回调
|
||||
WxPayService client = new WxPayServiceImpl();
|
||||
client.setConfig(chenfengWechatPayConfig);
|
||||
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
|
||||
// 2. 构建结果
|
||||
// V2 微信支付的回调,只有 SUCCESS 支付成功、CLOSED 支付失败两种情况,无需像支付宝一样解析的比较复杂
|
||||
Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
|
||||
PayOrderStatusEnum.SUCCESS.getStatus() : PayOrderStatusEnum.FAIL.getStatus();
|
||||
return PayOrderRespDTO.of(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()),
|
||||
response.getOutTradeNo(), body);
|
||||
}
|
||||
|
||||
static LocalDateTime parseDateV2(String time) {
|
||||
return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
|
||||
}
|
||||
}
|
||||
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
package com.cf.imes.module.system.util;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
|
||||
@Component
|
||||
public class SnowflakeIdWorker3rd {
|
||||
/** 初始时间 (2024-01-01 毫秒数: 1704038400000L) */
|
||||
private final long twepoch = 1704038400000L;
|
||||
/** 序列号占位数 */
|
||||
private final long sequenceBits = 12L;
|
||||
/** 时间戳左移位数 */
|
||||
private final long timestampLeftShift = 22L; // 12位序列号 + 10位预留
|
||||
/** 序列号掩码 */
|
||||
private final long sequenceMask = -1L ^ (-1L << sequenceBits);
|
||||
/** 当前序列号 */
|
||||
private long sequence = 0L;
|
||||
/** 上次生成ID的时间戳 */
|
||||
private long lastTimestamp = -1L;
|
||||
|
||||
public synchronized long nextId() {
|
||||
long timestamp = timeGen();
|
||||
if (timestamp < lastTimestamp) {
|
||||
throw new RuntimeException("时钟回拨,无法生成ID。回拨时间:" + (lastTimestamp - timestamp) + "ms");
|
||||
}
|
||||
|
||||
// 同一毫秒内生成序列号
|
||||
if (timestamp == lastTimestamp) {
|
||||
sequence = (sequence + 1) & sequenceMask;
|
||||
// 序列号溢出,等待下一毫秒
|
||||
if (sequence == 0) {
|
||||
timestamp = tilNextMillis(lastTimestamp);
|
||||
}
|
||||
} else {
|
||||
sequence = 0L;
|
||||
}
|
||||
|
||||
lastTimestamp = timestamp;
|
||||
// 组合时间戳和序列号生成ID
|
||||
return ((timestamp - twepoch) << timestampLeftShift) | sequence;
|
||||
}
|
||||
|
||||
/** 等待至下一毫秒 */
|
||||
private long tilNextMillis(long lastTimestamp) {
|
||||
long timestamp = timeGen();
|
||||
while (timestamp <= lastTimestamp) {
|
||||
timestamp = timeGen();
|
||||
}
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/** 获取当前毫秒时间 */
|
||||
private long timeGen() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SnowflakeIdWorker3rd idWorker = new SnowflakeIdWorker3rd();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
System.out.println(idWorker.nextId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,6 +291,12 @@ chenfeng:
|
||||
privateKey: H/XEvo3RBcn9PFtuV6R94kMDyLkhAyVP0TrY6LNZPDKSpt6m47265Q5wZJzlcZ2FO1ChVaqd4SNQQuQiSkTwzS3dlHtJuZO4SDzSzwDfCQCgAbGkMNjJyMue9WOjtumxC19vDZVQvmQHP+iFis/psw5MMppp+GwtyVGm64kwhKAlUvbSOJWN38H3z0i67KdcRC07tdoD5NTh5vWQOljRK52ISHlxssaK8+oC9rTSsI6mvkNL1OH/MktfLfmWEfZTX8tuSIrLkWikU9NFfXjD+IWrkC7qRIxdDeWHwCHTU86KSkYteRkMaltLmvWXBYycieM18iRIf2QvT6/gymkT8qosvderqKS0wz5gCIq9LVEZVdYsNKcXAZvN56lVlbb3zsHIFCtZBTfwSHJ5xy3r4Xn3kor8QqeCMk+ACZoxTaORxJ/phPEltCgIEVhAXCMdRmpJKd11NsjAHAP0P633MOhl/i2fzeagJWhV1wk1S8N0m4hfvchn//JEjxsqU90S3fuQokvj7KnDfXUsu9RgOAySAyZbkuMM7q/s7ZPNd0y41WmBoK3gVesu3qKfNzeap9vRD/N8ccyiRG1HwhH3NJhJ8Uo158KNFlJydFsq1Lllm0MsokeJEwEQuVUX07RfM9sPbdFfluXLTP1Xi817lKO8duIqwTmHcesv9Zlb2DX9prMtuTWB/CokboGhXh1L1gAtPXMCIHcjNpz+ACIOhxdQEP5hW9zRWnhF6LMc9lReUPnkKj7Lx08FLTsX8O00gvzX7plR8UyrMJc7a7MMRjN98L1XBKDzHg3gonIJXdGJxJP1j0elyILjr6DUDB8H4fYVmndm9FcuCMTXx84poIK39BAey5PJPvFTm1l8l6vw3gXjajffLhgdqCqn60nwG20464FvZusdMzmx/7s5HPB5wGfCNQZ0J+Q5FctITR92juHTUe6BsBbo7ZlGkWBlJByGy0kw4m81jCFzQpymTmpWaxCi56ysHnMXRnbeMyPe7v/6pHvNqwMLOBJzKBZi72I3o0gEuBnxR87F4r1GAVljlmSthxYFqWe4Jt9zG+dnUrscWJ+/pvGaRz40exLl8ahywPNX63sm9uXqt96PAqosOB1Il9vwY31xa+OeSOp4gT5thKKbbUtTPQIu92vp82HdFCTLLwDYOa3NQPrvxZjWH8cR6sYM7D1AUizzHNc6WfiU1YutDOHQVev6U9RKqwsZKgj94ySa1bH+24CiglXojLyAQ1uU99Hkth3JwCX2hPpCyNHJ34mhsMoe0XZZRrpzmtxRBkhVHtyF44pVekCH/SU3qZkeNuk5ETk+13GpZ7vhbmE9dBpLR4LsiPEfGJHqNH8D624cEKbcXcAL4tWfYiBqQAHlqnnd3dLHLawaAbXGr6xM0/hXVT/Z9Cb1Yh//pPtGnuHPZVCKSzdL14NLDlhJ3IDDYcmDW8pjbCRXNsfi75eZgNJwju2A6+I7j3Ovl0ma8kHrA73SAy2e8ldx25t7kvuw3s/kcYLCuGo9d4Fq0RpTk3YzF7XlevoHxrfXjxf/tGoZCrttURFjaWVtNnOmo2He894V3mSpl3IuaPwvo2VrPPlSFQ3aNkK0/Et2Gmt7OWGRHWgzPKWH378zMxPMo5cYcktmKXvOBvGyrMrHbKEl1lMMM/cAZh6pMdtcqU5J3kCKUbickAkf10KgULzqenPku+7rFB34Uv0Emw5s+l42bW9pcia58FyY+3km6Fl3dkPe2nm7YRvWSfisCAyCi/CHqxHecgchCParCJ9EcGoMADQfzkfsJInIuOT5c8IQXYzwqpSpHMk9n5JjqcyfCNPWEO1/YQOYTExkeDp4SENYIf6TZxvPQCb5cOnYzZcct2YNSrPzKahmzoTwpYmTxxMYuunFlWmyineV/Y+1sGoQ/Lzk+M83uf3UCHG9Pbsp5gFj9t21U95Yukw4Qs45fG8bVPPYfU97K/DrWDd4tfroFUyResqAP2HsXKNd0k+59rpBzEg2TrJH0XlU9zBdU0Oe93HSC/0TggZrKtd717uQXKqo5hJ65lV0JqbZInl92qK2t6BqZE/sKj4hMdeHnAebxROfLOojLFtJB5se6Dn9XnG+sf/cExSMmJqgDIGsOyGt7aA1dV0tDbzgio2V6jjOE2U9kx4luHuaTd2aWJ0oIAuWo6v2U3kMcLH8z+KqtnWAgGS0mEgpr7iz27w=
|
||||
alipayPublicKey: KbUGE7zsPBKWPx0/zMcAdLRldKrcgy2LRiOrqOBm7y4iPJLXTYtXFJT15p2JMQiyOM8mqiYQR0OEDwfrKxDgHXLtIut/e0EAk9bZwZoLrqVe0pN1huseRC38zEglbWil3yz+tZfHZuAaEq2EzvWJltX/tRKOzNBqAT9l+UvnqNedgCFB9MY2EErqJjbn/at3gYCkV2idt31ghF0z8SEQ5h9ztNYtmTvk8c8ipi0/hIjX8XryN6mlOvGQfyZ4rWrBWBuZ6iD/SyuThwxKmFYU4dhHbHVpBrva5nJWU43CP7RVaJm649nNJnce+Ppj2Tv8+ugSLe5poZWo2zmGe3Xn5WmN6OTZyRI1pnmn2/hGxtOMmTZJX7Gh0pI4v3wmU0gOd/gbRlqlvN+u12T92nyH8MQrHdA7UXuttAtolYUa/Rli/20qHX69QtOtvfZk8mNvDSj9dDvODseGcrFHm07jyfxpaIkQDeN75cQ3NwNgDQEW8urQszaU1IJ7hc+20tt0Z3td4+7MfiVPpGfVxsGE66gwLgpB8wsxEhyBTaBr7osMLqOv
|
||||
notifyUrl: RIEeHCoPHaAryuVN6mEYskBSl+EPvhsxRpjIbuIE7f7jABVxm46bLc01WQUe4Fn9NPYyNTv0xCxM68iRrmFDjAUjQ/hkIVNG+yD/kNsjcYCWa2gtOQPrvWZzDA==
|
||||
# wechat:
|
||||
# payBaseUrl: https://api.mch.weixin.qq.com/xdc/apiv2sandbox
|
||||
# appId: wxd0b9c0b5d0b9c0b5
|
||||
# mchId: 1725093395
|
||||
# mchKey: Cf100261028910026102891002610289
|
||||
|
||||
|
||||
justauth:
|
||||
enabled: true
|
||||
|
||||
@@ -69,6 +69,8 @@ mybatis-plus:
|
||||
map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
|
||||
global-config:
|
||||
db-config:
|
||||
worker-id: 0
|
||||
datacenter-id: 0
|
||||
id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
|
||||
# id-type: AUTO # 自增 ID,适合 MySQL 等直接自增的数据库
|
||||
# id-type: INPUT # 用户输入 ID,适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库
|
||||
|
||||
Reference in New Issue
Block a user