KKSU
2025-02-06 dbdc1ed6a3ecdcb20981b9e84200c69424444d6d
feat(payment): 集成 FIUU 支付方式并优化退款流程

- 新增 FIUU支付工具类 FiuuUtil,实现退款接口 comRefund
- 在 ApiMallTeamLeaderServiceImpl 中集成 FiuuUtil,替换原有的微信支付退款逻辑
- 更新 ApplyRefundOrderDto 中退款方式描述,增加 FIUU 支付选项
- 在 FIUUController 中添加支付成功后的订单状态更新逻辑
1 files added
3 files modified
156 ■■■■■ changed files
src/main/java/cc/mrbird/febs/mall/dto/ApplyRefundOrderDto.java 2 ●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallTeamLeaderServiceImpl.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java 1 ●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/pay/util/FiuuUtil.java 144 ●●●●● patch | view | raw | blame | history
src/main/java/cc/mrbird/febs/mall/dto/ApplyRefundOrderDto.java
@@ -20,7 +20,7 @@
    private Long itemId;
    //退款方式 1:微信 2:支付宝 3:其他
    @ApiModelProperty(value = "退款方式 1:微信 2:支付宝 3:余额", example = "描述")
    @ApiModelProperty(value = "退款方式 1:微信 2:支付宝 3:余额 5:FIUU支付", example = "描述")
    private Integer type;
    //退款状态 1:成功 2:失败 3:退款中
src/main/java/cc/mrbird/febs/mall/service/impl/ApiMallTeamLeaderServiceImpl.java
@@ -17,6 +17,7 @@
import cc.mrbird.febs.mall.vo.*;
import cc.mrbird.febs.pay.model.OrderStateDto;
import cc.mrbird.febs.pay.service.IXcxPayService;
import cc.mrbird.febs.pay.util.FiuuUtil;
import cc.mrbird.febs.pay.util.WeixinServiceUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
@@ -57,6 +58,8 @@
    private final MallRefundMapper mallRefundMapper;
    @Autowired
    private WeixinServiceUtil weixinServiceUtil;
    @Autowired
    private FiuuUtil fiuuUtil;
    private final XcxProperties xcxProperties = SpringContextHolder.getBean(XcxProperties.class);
    private final IMallMoneyFlowService mallMoneyFlowService;
    private final IApiMallMemberWalletService memberWalletService;
@@ -421,8 +424,6 @@
            String refundNo = mallRefundEntity.getRefundNo();
            //退款订单金额
            BigDecimal orderAmount = mallOrderInfo.getAmount();
            BigDecimal aa = new BigDecimal(100);
            int orderMoney = orderAmount.multiply(aa).intValue();
            //退款退款金额
//            BigDecimal refundAmount = mallRefundEntity.getAmount();
@@ -488,11 +489,11 @@
            Boolean flag = false;
            Boolean debug = xcxProperties.getDebug();
            if (debug) {
                boolean b = weixinServiceUtil.comRefund(orderNo, refundNo, 1, 1, null);
                boolean b = fiuuUtil.comRefund(orderNo, refundNo, "1");
                flag = b;
            } else {
                log.info("开始调用退款接口。。。退款编号为{}", refundNo);
                boolean b = weixinServiceUtil.comRefund(orderNo, refundNo, orderMoney, refundMoney, null);
                boolean b = fiuuUtil.comRefund(orderNo, refundNo, orderAmount.toString());
                flag = b;
            }
src/main/java/cc/mrbird/febs/pay/controller/FIUUController.java
@@ -146,6 +146,7 @@
        ValidateEntityUtils.ensureEqual(mallOrderInfo.getAmount().toString(), amount, "订单金额异常");
        // 更新订单状态
        if ("00".equals(status)) {
            mallOrderInfo.setPayMethod("FIUU支付");
            mallOrderInfo.setStatus(OrderStatusEnum.WAIT_SHIPPING.getValue());
            mallOrderInfo.setPayResult("1");
            mallOrderInfo.setPayTime(DateUtil.parseDateTime(paydate));
src/main/java/cc/mrbird/febs/pay/util/FiuuUtil.java
New file
@@ -0,0 +1,144 @@
package cc.mrbird.febs.pay.util;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.security.MessageDigest;
import java.util.LinkedHashMap;
import java.util.Map;
@Slf4j
@Service(value="FiuuUtil")
public class FiuuUtil {
    private static final String API_URL = "https://api.fiuu.com/RMS/API/refundAPI/index.php";
    private static final String MERCHANT_ID = "e2umart01";
    private static final String SECRET_KEY = "59c709fc18978a6a83b87f05d37cecbf";
    @Transactional
    public boolean comRefund(String outTradeNo, String outRefundNo,String amount){
            // 退款请求参数
            Map<String, String> params = new LinkedHashMap<>();
            params.put("RefundType", "P"); // P: Partial Refund, F: Full Refund
            params.put("MerchantID", MERCHANT_ID);
            params.put("RefID", outRefundNo); // 商户唯一退款ID
            params.put("TxnID", outTradeNo); // Fiuu原始交易ID
            params.put("Amount", amount); // 退款金额
            // 生成签名
        String signature = null;
        try {
            signature = generateSignature(params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        params.put("Signature", signature);
            // 发送GET请求
        String response = null;
        try {
            response = sendRefundRequest(params);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("退款响应: " + response);
        JSONObject jsonObject = JSONUtil.parseObj(response);
        String status = jsonObject.getStr("status");
        if ("00".equals(status)) {
            return true;
        }else{
            return false;
        }
    }
    public static void main(String[] args) {
        try {
            // 退款请求参数
            Map<String, String> params = new LinkedHashMap<>();
            params.put("RefundType", "P"); // P: Partial Refund, F: Full Refund
            params.put("MerchantID", MERCHANT_ID);
            params.put("RefID", "REF123456"); // 商户唯一退款ID
            params.put("TxnID", "123456789"); // Fiuu原始交易ID
            params.put("Amount", "100.00"); // 退款金额
            params.put("BankCode", "MBBEMYKL"); // 银行代码(可选)
            params.put("BankCountry", "MY"); // 国家代码(可选)
            params.put("BeneficiaryName", "John Doe"); // 收款人姓名(可选)
            params.put("BeneficiaryAccNo", "1234567890"); // 收款账号(可选)
            // 生成签名
            String signature = generateSignature(params);
            params.put("Signature", signature);
            // 发送GET请求
            String response = sendRefundRequest(params);
            System.out.println("退款响应: " + response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 生成MD5签名
     */
    private static String generateSignature(Map<String, String> params) throws Exception {
        // 按顺序拼接参数:RefundType + MerchantID + RefID + TxnID + Amount + SecretKey
        String rawData = params.get("RefundType") +
                params.get("MerchantID") +
                params.get("RefID") +
                params.get("TxnID") +
                params.get("Amount") +
                SECRET_KEY;
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] hashBytes = md.digest(rawData.getBytes("UTF-8"));
        // 转换为十六进制字符串
        StringBuilder hexString = new StringBuilder();
        for (byte b : hashBytes) {
            String hex = Integer.toHexString(0xff & b);
            if (hex.length() == 1) hexString.append('0');
            hexString.append(hex);
        }
        return hexString.toString();
    }
    /**
     * 发送退款请求(GET方式)
     */
    private static String sendRefundRequest(Map<String, String> params) throws Exception {
        StringBuilder urlBuilder = new StringBuilder(API_URL);
        urlBuilder.append("?");
        // 拼接查询参数
        for (Map.Entry<String, String> entry : params.entrySet()) {
            urlBuilder.append(entry.getKey())
                    .append("=")
                    .append(entry.getValue())
                    .append("&");
        }
        String url = urlBuilder.toString().replaceAll("&$", ""); // 去除末尾的&
        // 使用HttpClient发送请求
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpGet httpGet = new HttpGet(url);
            try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
                HttpEntity entity = response.getEntity();
                return EntityUtils.toString(entity);
            }
        }
    }
}