diff --git a/cf-framework/cf-spring-boot-starter-biz-id/src/main/java/com/cf/imes/framework/id/core/util/SnowflakeIdWorker3rd.java b/cf-framework/cf-spring-boot-starter-biz-id/src/main/java/com/cf/imes/framework/id/core/util/SnowflakeIdWorker3rd.java
index 517948856..add3a73f8 100644
--- a/cf-framework/cf-spring-boot-starter-biz-id/src/main/java/com/cf/imes/framework/id/core/util/SnowflakeIdWorker3rd.java
+++ b/cf-framework/cf-spring-boot-starter-biz-id/src/main/java/com/cf/imes/framework/id/core/util/SnowflakeIdWorker3rd.java
@@ -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);
}
diff --git a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java
index 88a8b0e3d..01df2fbab 100644
--- a/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java
+++ b/cf-framework/cf-spring-boot-starter-biz-pay/src/main/java/com/cf/imes/framework/pay/config/ChenfengWechatPayConfig.java
@@ -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);
+ }
+ }
}
diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java
index 4d853e604..0868a3041 100644
--- a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java
+++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java
@@ -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, "当前产品不存在");
diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/products/DurationUnitEnum.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/products/DurationUnitEnum.java
index 4af5feb30..b96a34a4b 100644
--- a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/products/DurationUnitEnum.java
+++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/products/DurationUnitEnum.java
@@ -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);
+ }
}
diff --git a/cf-module-system/cf-module-system-biz/pom.xml b/cf-module-system/cf-module-system-biz/pom.xml
index 234fad088..da102c821 100644
--- a/cf-module-system/cf-module-system-biz/pom.xml
+++ b/cf-module-system/cf-module-system-biz/pom.xml
@@ -49,6 +49,10 @@
com.cf.imes
cf-spring-boot-starter-biz-ip
+
+ com.cf.imes
+ cf-spring-boot-starter-biz-id
+
@@ -244,6 +248,10 @@
com.cf.imes
cf-spring-boot-starter-biz-ip
+
+ com.cf.imes
+ cf-spring-boot-starter-biz-id
+
com.cf.imes
cf-spring-boot-starter-biz-operatelog
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java
index 68bfcb788..136396944 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java
@@ -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;
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganPayAmountRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganPayAmountRespVO.java
new file mode 100644
index 000000000..b1bd3d2e2
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganPayAmountRespVO.java
@@ -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;
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganRechargeAmountRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganRechargeAmountRespVO.java
index f6b9936c2..19820c7ed 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganRechargeAmountRespVO.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganRechargeAmountRespVO.java
@@ -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 {
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java
new file mode 100644
index 000000000..238b45178
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java
@@ -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;
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseService.java
new file mode 100644
index 000000000..c8c8048d3
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseService.java
@@ -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);
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseServiceImpl.java
new file mode 100644
index 000000000..4c422721f
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseServiceImpl.java
@@ -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()
+ .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()
+ .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;
+ }
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/OrgEarliestPurchaseProductRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/OrgEarliestPurchaseProductRespVO.java
new file mode 100644
index 000000000..18380bd05
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/purchase/vo/OrgEarliestPurchaseProductRespVO.java
@@ -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;
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java
index 70be38d9f..1e963666d 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/PayController.java
@@ -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 initiatePay(@RequestBody PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
-// payOrderService.productPay(payProductOrderUnifiedReqVO);
-// return success(null);
-// }
+ @PostMapping("/product")
+ @Operation(summary = "创建产品支付订单")
+ public CommonResult initiatePay(@RequestBody PayProductOrderUnifiedReqVO payProductOrderUnifiedReqVO) {
+ PayOrderRespDTO payOrderRespDTO = payOrderService.productPay(payProductOrderUnifiedReqVO);
+ return success(payOrderRespDTO);
+ }
@GetMapping("/order/{channelCode}/{outTradeNo}")
@Operation(summary = "查询支付订单")
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java
index cd9e49a2a..59bbb5681 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/pay/vo/PayProductOrderUnifiedReqVO.java
@@ -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;
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java
new file mode 100644
index 000000000..4fda9730f
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java
@@ -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;
+
+
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/IncomeExpenseDetailsDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/IncomeExpenseDetailsDO.java
index 02bfda3f3..ee6f532d8 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/IncomeExpenseDetailsDO.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/incomeandexpense/IncomeExpenseDetailsDO.java
@@ -78,7 +78,7 @@ public class IncomeExpenseDetailsDO extends BaseDO {
/**
* 支出/入账赠送金金额
*/
- private BigDecimal bonusAmountChange;
+ private BigDecimal giftAmountChange;
/**
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java
new file mode 100644
index 000000000..e998401e7
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/purchase/PurchaseRecordMapper.java
@@ -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 {
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/InvoiceStatusEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/InvoiceStatusEnum.java
new file mode 100644
index 000000000..0be96ca46
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/InvoiceStatusEnum.java
@@ -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;
+ }
+
+
+
+
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java
index 0656d674f..7aadead3b 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayConstants.java
@@ -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
}
\ No newline at end of file
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java
index 4f35cdc3a..d5623e017 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayOrderStatusEnum.java
@@ -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());
}
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnum.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnum.java
index 753a0a7df..ec739f8ab 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnum.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/enums/pay/PayProductChannelCodeEnum.java
@@ -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);
+ }
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/package-info.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/package-info.java
deleted file mode 100644
index ac3e1ae74..000000000
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/package-info.java
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * 属于 system 模块的 framework 封装
- *
- * @author 晨丰科技
- */
-package com.cf.imes.module.system.framework;
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/config/MybatisIdProperties.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/config/MybatisIdProperties.java
new file mode 100644
index 000000000..031630baf
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/config/MybatisIdProperties.java
@@ -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;
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/package-info.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/package-info.java
new file mode 100644
index 000000000..f234a3964
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/snowflake/package-info.java
@@ -0,0 +1,6 @@
+/**
+ * 属于 system 模块的 framework.snowflake 封装
+ *
+ * @author 晨丰科技
+ */
+package com.cf.imes.module.system.framework.snowflake;
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/job/pay/PayOrderExpireJob.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/job/pay/PayOrderExpireJob.java
new file mode 100644
index 000000000..abdea2a0d
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/job/pay/PayOrderExpireJob.java
@@ -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);
+ }
+
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/advertisement/AdvertisementServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/advertisement/AdvertisementServiceImpl.java
index 32995e280..e81719392 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/advertisement/AdvertisementServiceImpl.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/advertisement/AdvertisementServiceImpl.java
@@ -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) {
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountService.java
index c8614c82d..4f2b9b9b0 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountService.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountService.java
@@ -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);
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java
index 8ccd83361..79983cb6b 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/organamount/OrganAmountServiceImpl.java
@@ -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()
+ .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().eq(OrganAmountDO::getOrganId, organId));
+ BigDecimal newAmount = organAmountDO.getAmount().add(balance);
+ BigDecimal newGift = organAmountDO.getGiftAmount().add(gift);
+
+ int updated = organAmountMapper.update(new LambdaUpdateWrapper()
+ .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().eq(OrganAmountDO::getOrganId, organId));
+ newAmount = organAmountDO.getAmount().add(balance);
+ newGift = organAmountDO.getGiftAmount().add(gift);
+
+ updated = organAmountMapper.update(new LambdaUpdateWrapper()
+ .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;
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailService.java
index 64f091af7..85e2dd5d7 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailService.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailService.java
@@ -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);
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java
index aa94b24a4..d6dafa83d 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java
@@ -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()
.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;
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/recharge/RechargeActiveServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/recharge/RechargeActiveServiceImpl.java
index defea1374..7014e2a1a 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/recharge/RechargeActiveServiceImpl.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/recharge/RechargeActiveServiceImpl.java
@@ -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()
.eq(GiftMoneyDetailsDO::getDeleted, false)
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java
index 0bbf8ce29..166e821ac 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderService.java
@@ -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 params, String body);
+
+ /**
+ * 处理过期订单
+ *
+ * @return
+ */
+ int expirePayOrder();
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java
index 7eda3460c..f3a4e67cd 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java
@@ -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 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 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 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 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 expireOrders = payOrderMapper.selectList(new LambdaQueryWrapper()
+ .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;
+ }
+ }
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java
new file mode 100644
index 000000000..663edf8f2
--- /dev/null
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java
@@ -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;
+ }
+ }
+
+}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java
index 9f45ee3f4..f0ea7f8ba 100644
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java
+++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/factory/impl/wechat/WechatNativePayHandler.java
@@ -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 params, String body) {
- return null;
+ public PayOrderRespDTO doParseOrderNotify(Map 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);
}
}
diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/util/SnowflakeIdWorker3rd.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/util/SnowflakeIdWorker3rd.java
deleted file mode 100644
index 6ab065221..000000000
--- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/util/SnowflakeIdWorker3rd.java
+++ /dev/null
@@ -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());
- }
- }
-}
\ No newline at end of file
diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml
index c1d7b6a57..fa79e5dd7 100644
--- a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml
+++ b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml
@@ -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
diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml
index 45b589dc4..38dd24293 100644
--- a/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml
+++ b/cf-module-system/cf-module-system-biz/src/main/resources/application.yaml
@@ -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 数据库