From 2caf98df09bd38006ca5e590fa2418588bf0a351 Mon Sep 17 00:00:00 2001
From: KKSU <15274802129@163.com>
Date: Wed, 05 Feb 2025 14:31:00 +0800
Subject: [PATCH] feat(pay): 添加 FIUU支付功能

---
 src/main/java/cc/mrbird/febs/common/utils/ValidateEntityUtils.java |  280 ++++++++++++++++++++++++++++++++++++++++
 src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java    |  106 +++++++++++++++
 src/main/java/cc/mrbird/febs/pay/model/FIUUInitPayRequest.java     |   29 ++++
 src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java |    1 
 4 files changed, 416 insertions(+), 0 deletions(-)

diff --git a/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java b/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
index 984c91a..a77631f 100644
--- a/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
+++ b/src/main/java/cc/mrbird/febs/common/configure/WebMvcConfigure.java
@@ -33,6 +33,7 @@
         registration.excludePathPatterns("/api/xcxPay/wxpayCallback");
         registration.excludePathPatterns("/api/xcxPay/rechargeCallBack");
         registration.excludePathPatterns("/api/xcxPay/fapiaoCallBack");
+        registration.excludePathPatterns("/api/fuPay/callback");
 
         // 添加Swagger UI相关路径
         registration.excludePathPatterns("/api/swagger-ui.html");
diff --git a/src/main/java/cc/mrbird/febs/common/utils/ValidateEntityUtils.java b/src/main/java/cc/mrbird/febs/common/utils/ValidateEntityUtils.java
new file mode 100644
index 0000000..83a8b90
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/common/utils/ValidateEntityUtils.java
@@ -0,0 +1,280 @@
+package cc.mrbird.febs.common.utils;
+
+import cc.mrbird.febs.common.exception.FebsException;
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * 实体验证工具类
+ */
+public class ValidateEntityUtils {
+
+    /**
+     * 确保指定列的有效性,并返回相应的实体对象
+     *      selectOne方法
+     *      此方法用于验证数据库中某列的值是否存在,并返回包含该值的实体对象
+     *      如果指定的值不存在或为null,将抛出异常
+     *
+     * @param valueToCheck 需要验证的值
+     * @param columnExtractor 列值提取器,用于指定需要验证的列
+     * @param queryWrapperExtractor 查询包装器提取器,用于执行数据库查询
+     * @param errMsg 错误消息格式字符串
+     * @param columnExtractors 可变参数(varargs)查询字段,可选,如果没有就是查询全部
+     * @param <T> 实体类类型
+     * @param <R> 列值类型
+     * @param <V> 需要验证的值的类型
+     * @return 返回包含指定列值的实体对象
+     * @throws IllegalArgumentException 如果需要验证的值为null
+     * @throws FebsException 如果查询结果为空或列值为null,或查询过程中发生异常
+     */
+    public static <T, R, V> T ensureColumnReturnEntity(
+            V valueToCheck,
+            SFunction<T, R> columnExtractor,
+            SFunction<LambdaQueryWrapper<T>, T> queryWrapperExtractor,
+            String errMsg,
+            SFunction<T, R>... columnExtractors) {
+
+        // 检查输入参数是否为null
+        if (valueToCheck == null) {
+            throw new IllegalArgumentException("The value to check cannot be null while ensureColumnReturnEntity column");
+        }
+        if (columnExtractor == null || queryWrapperExtractor == null) {
+            throw new IllegalArgumentException("Column extractor and query wrapper extractor cannot be null while ensureColumnReturnEntity column");
+        }
+
+        T entity = null;
+        try {
+            // 创建LambdaQueryWrapper并配置查询条件
+            LambdaQueryWrapper<T> wrapper = Wrappers.lambdaQuery();
+            if (columnExtractors != null && columnExtractors.length > 0) {
+                wrapper.select(columnExtractors);
+            }
+            wrapper.eq(columnExtractor, valueToCheck)
+                    .last("limit 1");
+
+            // 执行查询并获取结果实体
+            entity = queryWrapperExtractor.apply(wrapper);
+
+            // 如果查询结果为空,则抛出异常
+            if (entity == null) {
+                throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+            }
+
+            // 提取查询结果中的列值
+            R columnValue = columnExtractor.apply(entity);
+            // 如果列值为null,则抛出异常
+            if (columnValue == null) {
+                throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+            }
+
+        } catch (FebsException e) {
+            // 记录异常日志
+            throw e;
+        } catch (Exception e) {
+            // 记录异常日志
+            throw new FebsException(StrUtil.format("An error occurred while ensuring column return entity: {}", valueToCheck));
+        }
+
+        // 返回查询到的实体类对象
+        return entity;
+    }
+
+    /**
+     * 确保指定列的有效性,并返回相应的实体对象列表
+     *      selectList方法
+     *      此方法用于验证数据库中某列的值是否存在,并返回包含该值的实体对象列表
+     *      如果指定的值不存在或为null,将抛出异常
+     *
+     * @param valueToCheck 需要验证的值
+     * @param columnExtractor 列值提取器,用于指定需要验证的列
+     * @param queryWrapperExtractor 查询包装器提取器,用于执行数据库查询
+     * @param errMsg 错误消息格式字符串
+     * @param columnExtractors 可变参数(varargs)查询字段,可选,如果没有就是查询全部
+     * @param <T> 实体类类型
+     * @param <R> 列值类型
+     * @param <V> 需要验证的值的类型
+     * @return 返回包含指定列值的实体对象列表
+     * @throws IllegalArgumentException 如果需要验证的值为null
+     * @throws FebsException 如果查询结果为空或列值为null,或查询过程中发生异常
+     */
+    public static <T, R, V> List<T> ensureColumnReturnEntityList(
+            V valueToCheck,
+            SFunction<T, R> columnExtractor,
+            SFunction<LambdaQueryWrapper<T>, List<T>> queryWrapperExtractor,
+            String errMsg,
+            SFunction<T, R>... columnExtractors) {
+        // 检查需要验证的值是否为null
+        if (valueToCheck == null) {
+            throw new IllegalArgumentException("The value to check cannot be null while ensureColumnReturnEntityList column");
+        }
+        List<T> entities = new ArrayList<>();
+        try {
+            // 创建LambdaQueryWrapper并配置查询条件
+            LambdaQueryWrapper<T> wrapper = Wrappers.lambdaQuery();
+            if (columnExtractors != null && columnExtractors.length > 0) {
+                wrapper.select(columnExtractors);
+            }
+            wrapper.eq(columnExtractor, valueToCheck);
+
+            // 执行查询并获取结果实体列表
+            entities = queryWrapperExtractor.apply(wrapper);
+        } catch (Exception e) {
+            // 记录异常日志
+            throw new FebsException(StrUtil.format("An error occurred while ensureColumnReturnEntityList column: {}", valueToCheck));
+        }
+        // 如果查询结果为空,则抛出异常
+        if (entities == null || entities.isEmpty()) {
+            throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+        }
+
+        // 返回查询到的实体类对象列表
+        return entities;
+    }
+
+    /**
+     * 确保指定列的值在数据库中是存在的
+     *      selectOne方法
+     *      该方法通过查询数据库来验证给定的列值是否存在如果不存在,则抛出异常
+     *
+     * @param valueToCheck 需要验证的列值
+     * @param columnExtractor 用于提取实体类中列值的函数式接口
+     * @param queryWrapperExtractor 用于构建查询条件并返回实体类的函数式接口
+     * @param errMsg 当列值无效时抛出的异常消息格式字符串
+     * @throws IllegalArgumentException 如果需要验证的值为null
+     * @throws FebsException 如果在数据库中找不到指定的列值或列值为null,或者在查询过程中发生异常
+     */
+    public static <T, R, V> void ensureColumnValid(
+            V valueToCheck,
+            SFunction<T, R> columnExtractor,
+            SFunction<LambdaQueryWrapper<T>, T> queryWrapperExtractor,
+            String errMsg) {
+        // 检查需要验证的值是否为null
+        if (valueToCheck == null) {
+            throw new IllegalArgumentException("The value to check cannot be null while ensureColumnValid column");
+        }
+
+        try {
+            // 创建LambdaQueryWrapper并配置查询条件
+            LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
+            wrapper.select(columnExtractor)
+                    .eq(columnExtractor, valueToCheck)
+                    .last("limit 1");
+
+            // 执行查询并获取结果实体
+            T entity = queryWrapperExtractor.apply(wrapper);
+            // 如果查询结果为空,则抛出异常
+            if (entity == null) {
+                throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+            }
+
+            // 提取查询结果中的列值
+            R columnValue = columnExtractor.apply(entity);
+            // 如果列值为null,则抛出异常
+            if (columnValue == null) {
+                throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+            }
+        } catch (Exception e) {
+            // 记录异常日志
+            throw new FebsException(e.getMessage());
+        }
+    }
+
+    /**
+     * 确保指定值在数据库中是唯一的
+     *      selectCount方法
+     *      该方法通过查询数据库来验证给定的列值是否已经存在,如果存在,则抛出异常,以确保数据的唯一性
+     *
+     * @param valueToCheck 需要检查的值
+     * @param columnExtractor 用于提取实体类字段的函数式接口
+     * @param countWrapperExtractor 用于获取查询条件包装器中记录数的函数式接口
+     * @param errMsg 错误消息模板,当值不唯一时使用
+     * @param <T> 实体类类型
+     * @param <R> 字段类型
+     * @param <V> 需要检查的值的类型
+     * @throws IllegalArgumentException 如果需要检查的值为null,则抛出此异常
+     * @throws FebsException 如果值已存在或在检查过程中发生错误,则抛出此异常
+     */
+    public static <T, R, V> void ensureUnique(
+            V valueToCheck,
+            SFunction<T, R> columnExtractor,
+            SFunction<LambdaQueryWrapper<T>, Integer> countWrapperExtractor,
+            String errMsg) {
+        // 检查输入值是否为null,如果为null,则抛出IllegalArgumentException异常
+        if (valueToCheck == null) {
+            throw new IllegalArgumentException("The value to check cannot be null while ensureUnique column");
+        }
+        if (columnExtractor == null) {
+            throw new IllegalArgumentException("The columnExtractor cannot be null while ensureUnique column");
+        }
+        if (countWrapperExtractor == null) {
+            throw new IllegalArgumentException("The countWrapperExtractor cannot be null while ensureUnique column");
+        }
+
+        int count = 0;
+        try {
+            // 创建LambdaQueryWrapper对象,用于构建查询条件
+            LambdaQueryWrapper<T> wrapper = new LambdaQueryWrapper<>();
+            // 添加等于条件,检查的字段=需要检查的值
+            wrapper.eq(columnExtractor, valueToCheck);
+            // 执行查询并获取结果数量
+            count = countWrapperExtractor.apply(wrapper);
+
+        } catch (Exception e) {
+            // 记录异常日志
+            throw new FebsException(StrUtil.format("An error occurred while ensureUnique column: {}", valueToCheck));
+        }
+
+        // 如果结果数量大于0,说明值已存在,抛出FebsException异常
+        if (count > 0) {
+            throw new FebsException(StrUtil.format(errMsg, valueToCheck));
+        }
+    }
+
+
+    /**
+     * 确保两个参数相等,如果不相等则抛出异常
+     *
+     * @param value1 第一个参数
+     * @param value2 第二个参数
+     * @param errMsg 当两个参数不相等时抛出的异常消息格式字符串
+     * @throws FebsException 如果两个参数不相等
+     */
+    public static <T> void ensureEqual(
+            T value1,
+            T value2,
+            String errMsg) {
+        // 使用 Objects.equals 处理 null 值,避免显式 null 检查
+        if (!Objects.equals(value1, value2)) {
+            // 延迟字符串格式化,只在需要抛出异常时进行
+            throw new FebsException(StrUtil.format(errMsg, value1, value2));
+        }
+    }
+
+
+    /**
+     * 确保两个参数不相等,如果相等则抛出异常
+     *
+     * @param value1 第一个参数
+     * @param value2 第二个参数
+     * @param errMsg 当两个参数相等时抛出的异常消息格式字符串
+     * @throws FebsException 如果两个参数相等
+     */
+    public static <T> void ensureNotEqual(
+            T value1,
+            T value2,
+            String errMsg) {
+        // 使用 Objects.equals 处理 null 值,避免显式 null 检查
+        if (Objects.equals(value1, value2)) {
+            // 延迟字符串格式化,只在需要抛出异常时进行
+            throw new FebsException(StrUtil.format(errMsg, value1, value2));
+        }
+    }
+
+
+}
diff --git a/src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java b/src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java
new file mode 100644
index 0000000..d28300e
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java
@@ -0,0 +1,106 @@
+package cc.mrbird.febs.pay.controller;
+
+import cc.mrbird.febs.common.entity.FebsResponse;
+import cc.mrbird.febs.common.enumerates.OrderDeliveryStateEnum;
+import cc.mrbird.febs.common.enumerates.OrderStatusEnum;
+import cc.mrbird.febs.common.exception.FebsException;
+import cc.mrbird.febs.common.utils.ValidateEntityUtils;
+import cc.mrbird.febs.mall.entity.MallOrderInfo;
+import cc.mrbird.febs.mall.mapper.MallOrderInfoMapper;
+import cc.mrbird.febs.pay.model.FIUUInitPayRequest;
+import cn.hutool.core.date.DateUtil;
+import io.swagger.annotations.ApiOperation;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+@Slf4j
+@RestController
+@RequestMapping(value = "/api/fuPay")
+public class FIUUController {
+
+    @Resource
+    private MallOrderInfoMapper mallOrderInfoMapper;
+    @ApiOperation(value = "初始化FIUU支付信息", notes = "初始化FIUU支付信息")
+    @PostMapping("/initPayment")
+    public FebsResponse initPayment(@RequestBody FIUUInitPayRequest orderRequest) {
+        Long orderId = orderRequest.getOrderId();
+        MallOrderInfo mallOrderInfo = ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在");
+        ValidateEntityUtils.ensureEqual(mallOrderInfo.getPayResult(), "1", "订单已支付");
+        String amount = mallOrderInfo.getAmount().toString();
+        try {
+            String merchantId = "e2umart01";
+            String verifyKey = "4e3a4ed58e62ddbfacf41f6d5ec56bf2";
+            String returnUrl = "https://www.mye2u.com/api/fuPay/callback"; // 支付结果回调地址
+
+            // 生成 vcode(MD5(amount + merchantId + orderId + verifyKey))
+            String vcode = DigestUtils.md5Hex(
+                    amount +
+                            merchantId +
+                            orderRequest.getOrderId() +
+                            verifyKey
+            );
+
+            // 返回支付参数
+            Map<String, String> params = new HashMap<>();
+            params.put("merchant_id", merchantId);
+            params.put("orderid", String.valueOf(orderId));
+            params.put("amount", amount);
+            params.put("bill_name", orderRequest.getBuyerName());
+            params.put("bill_email", orderRequest.getBuyerEmail());
+            params.put("bill_mobile", orderRequest.getBuyerMobile());
+            params.put("bill_desc", mallOrderInfo.getOrderNo());
+            params.put("currency", "MYR"); // 默认 MYR
+            params.put("vcode", vcode);
+            params.put("returnurl", returnUrl);
+
+            return new FebsResponse().success().data(params);
+        } catch (Exception e) {
+            return new FebsResponse().fail().message("支付参数校验失败");
+        }
+    }
+
+    // Java 回调接口
+    @PostMapping("/callback")
+    public FebsResponse handlePaymentCallback(@RequestParam Map<String, String> params) {
+        try {
+            String secretKey = "59c709fc18978a6a83b87f05d37cecbf";
+            String tranID = params.get("tranID");
+            String orderId = params.get("orderid");
+            String status = params.get("status");
+            String domain = params.get("domain");
+            String amount = params.get("amount");
+            String currency = params.get("currency");
+            String paydate = params.get("paydate");
+            String skey = params.get("skey");
+
+            // 计算 skey 验证
+            String preSkey = DigestUtils.md5Hex(tranID + orderId + status + domain + amount + currency);
+            String calculatedSkey = DigestUtils.md5Hex(paydate + domain + preSkey + secretKey);
+
+            if (!calculatedSkey.equals(skey)) {
+                throw new FebsException("订单回调失败,---"+orderId);
+            }
+
+            MallOrderInfo mallOrderInfo = ValidateEntityUtils.ensureColumnReturnEntity(orderId, MallOrderInfo::getId, mallOrderInfoMapper::selectOne, "订单不存在");
+            ValidateEntityUtils.ensureEqual(mallOrderInfo.getPayResult(), "1", "订单已支付");
+            ValidateEntityUtils.ensureEqual(mallOrderInfo.getAmount().toString(), amount, "订单金额异常");
+            // 更新订单状态
+            if ("00".equals(status)) {
+                mallOrderInfo.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue());
+                mallOrderInfo.setPayResult("1");
+                mallOrderInfo.setPayTime(DateUtil.parseDateTime(paydate));
+                mallOrderInfo.setDeliveryState(OrderDeliveryStateEnum.DELIVERY_WAIT.getValue());
+                mallOrderInfo.setPayOrderNo(tranID);
+                mallOrderInfoMapper.updateById(mallOrderInfo);
+            }
+            return new FebsResponse().success().message("OK");
+        } catch (Exception e) {
+            return new FebsResponse().fail().message("Internal Error");
+        }
+    }
+}
diff --git a/src/main/java/cc/mrbird/febs/pay/model/FIUUInitPayRequest.java b/src/main/java/cc/mrbird/febs/pay/model/FIUUInitPayRequest.java
new file mode 100644
index 0000000..73651c9
--- /dev/null
+++ b/src/main/java/cc/mrbird/febs/pay/model/FIUUInitPayRequest.java
@@ -0,0 +1,29 @@
+package cc.mrbird.febs.pay.model;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel(value = "FIUUInitPayRequest", description = "初始化支付信息")
+public class FIUUInitPayRequest {
+
+    @NotNull(message = "参数不能为空")
+    @ApiModelProperty(value = "订单ID")
+    private Long orderId;
+
+    @NotNull(message = "参数不能为空")
+    @ApiModelProperty(value = "买家姓名")
+    private String buyerName;
+
+    @NotNull(message = "参数不能为空")
+    @ApiModelProperty(value = "买家邮箱")
+    private String buyerEmail;
+
+    @NotNull(message = "参数不能为空")
+    @ApiModelProperty(value = "买家手机号")
+    private String buyerMobile;
+
+}

--
Gitblit v1.9.1