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