全部
常见问题
产品动态
精选推荐
功能建议

分析中 已回复 待规划 {{opt.name}}
分析中 已回复 待规划
CRMEB Java多商户对接汇付支付商家分账代码示例

管理 管理 编辑 删除

一、项目结构


com.crmeb.payment/
├── config/
│   └── HuifuPayConfig.java           # 汇付支付配置类
├── controller/
│   └── HuifuPayController.java       # 支付控制器
├── service/
│   ├── HuifuPayService.java           # 支付服务接口
│   └── impl/
│       └── HuifuPayServiceImpl.java   # 支付服务实现
├── model/
│   ├── request/
│   │   ├── OrderPayRequest.java       # 订单支付请求
│   │   └── OrderSplitRequest.java     # 分账请求
│   └── response/
│       ├── PayResponse.java            # 支付响应
│       └── SplitResponse.java          # 分账响应
├── util/
│   └── HuifuSignUtil.java              # 签名工具
└── task/
    └── SplitQueryTask.java              # 分账查询定时任务

二、核心配置类


package com.crmeb.payment.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import javax.annotation.PostConstruct;

/**
 * 汇付支付配置类
 * 对应功能表:支付配置、商户结算设置
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "huifu.pay")
public class HuifuPayConfig {
    
    /** 汇付分配产品号 */
    private String productId;
    
    /** 汇付分配系统号 */
    private String sysId;
    
    /** 汇付分配私钥 */
    private String privateKey;
    
    /** 汇付分配公钥 */
    private String publicKey;
    
    /** 是否生产环境 */
    private Boolean prodMode = false;
    
    /** 是否开启调试 */
    private Boolean debug = true;
    
    /** 分账冻结时间(天) - 对应功能表:商户结算设置 */
    private Integer splitFreezeDays = 7;
    
    /** 分账开始规则:订单支付后/收货后/完成后 */
    private String splitStartRule = "order_received";
    
    @PostConstruct
    public void init() {
        // 初始化汇付SDK
        BasePay.debug = debug;
        BasePay.prodMode = prodMode;
        
        try {
            MerConfig merConfig = new MerConfig();
            merConfig.setProcutId(productId);
            merConfig.setSysId(sysId);
            merConfig.setRsaPrivateKey(privateKey);
            merConfig.setRsaPublicKey(publicKey);
            
            // 单商户模式初始化
            BasePay.initWithMerConfig(merConfig);
            
            // 如果需要多商户模式,可以添加多个配置
            // BasePay.addMerConfig(merConfig, merchantKey);
            
            log.info("汇付支付初始化成功");
        } catch (Exception e) {
            log.error("汇付支付初始化失败", e);
            throw new RuntimeException("汇付支付初始化失败");
        }
    }
}

三、支付请求/响应模型


package com.crmeb.payment.model.request;

import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;
import java.util.List;

/**
 * 订单支付请求
 * 对应功能表:订单支付、拆单逻辑
 */
@Data
public class OrderPayRequest {
    
    /** 平台订单号 */
    @NotBlank(message = "订单号不能为空")
    private String orderNo;
    
    /** 商户号(平台分配的商户ID) */
    @NotBlank(message = "商户号不能为空")
    private String merchantId;
    
    /** 支付金额(元) */
    @NotNull(message = "支付金额不能为空")
    @DecimalMin(value = "0.01", message = "支付金额最小为0.01元")
    private BigDecimal amount;
    
    /** 商品描述 */
    @Size(max = 128, message = "商品描述不能超过128个字符")
    private String subject;
    
    /** 支付方式:WECHAT-微信 ALIPAY-支付宝 BALANCE-余额 */
    @NotBlank(message = "支付方式不能为空")
    private String payType;
    
    /** 分账标识:true-需要分账 false-不分账 */
    private Boolean needSplit = false;
    
    /** 分账明细 - 对应功能表:成本价给供应商分账 */
    private List<SplitInfo> splitInfos;
    
    /** 异步通知地址 */
    private String notifyUrl;
    
    /** 同步返回地址 */
    private String returnUrl;
    
    /** 客户端IP */
    private String clientIp;
    
    /**
     * 分账明细
     * 对应功能表:成本价给供应商分账、优惠券+成本价
     */
    @Data
    public static class SplitInfo {
        /** 分账方商户号(供应商汇付商户号) */
        @NotBlank(message = "分账方商户号不能为空")
        private String huifuId;
        
        /** 分账金额(元)- 成本价 */
        @NotNull(message = "分账金额不能为空")
        @DecimalMin(value = "0.01", message = "分账金额最小为0.01元")
        private BigDecimal divAmount;
        
        /** 分账描述 */
        private String description;
        
        /** 是否平台手续费 */
        private Boolean isPlatformFee = false;
    }
}
package com.crmeb.payment.model.response;

import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * 支付响应
 */
@Data
public class PayResponse {
    
    /** 是否成功 */
    private Boolean success;
    
    /** 响应码 */
    private String respCode;
    
    /** 响应描述 */
    private String respMsg;
    
    /** 平台订单号 */
    private String orderNo;
    
    /** 汇付交易号 */
    private String tradeNo;
    
    /** 支付金额 */
    private BigDecimal amount;
    
    /** 支付状态:0-待支付 1-支付中 2-支付成功 3-支付失败 */
    private Integer payStatus;
    
    /** 支付时间 */
    private Date payTime;
    
    /** 支付参数(用于小程序/公众号调起支付) */
    private PayParams payParams;
    
    /** 分账结果 */
    private List<SplitResult> splitResults;
    
    @Data
    public static class PayParams {
        /** 小程序appId */
        private String appId;
        
        /** 时间戳 */
        private String timeStamp;
        
        /** 随机串 */
        private String nonceStr;
        
        /** 订单详情扩展字符串 */
        private String packageStr;
        
        /** 签名方式 */
        private String signType;
        
        /** 支付签名 */
        private String paySign;
    }
    
    @Data
    public static class SplitResult {
        /** 分账方商户号 */
        private String huifuId;
        
        /** 分账金额 */
        private BigDecimal divAmount;
        
        /** 分账状态:0-待分账 1-分账成功 2-分账失败 */
        private Integer splitStatus;
        
        /** 分账失败原因 */
        private String failReason;
    }
}

四、支付服务实现


package com.crmeb.payment.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.crmeb.payment.config.HuifuPayConfig;
import com.crmeb.payment.model.request.OrderPayRequest;
import com.crmeb.payment.model.response.PayResponse;
import com.crmeb.payment.service.HuifuPayService;
import com.huifu.pay.sdk.BasePayClient;
import com.huifu.pay.sdk.exception.BasePayException;
import com.huifu.pay.sdk.model.request.AuthCodePayRequest;
import com.huifu.pay.sdk.model.request.JsapiPayRequest;
import com.huifu.pay.sdk.model.request.WxAppPayRequest;
import com.huifu.pay.sdk.model.request.WxMiniPayRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 汇付支付服务实现
 * 对应功能表:订单支付、拆单逻辑、成本价给供应商分账
 */
@Slf4j
@Service
public class HuifuPayServiceImpl implements HuifuPayService {
    
    @Autowired
    private HuifuPayConfig huifuPayConfig;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResponse createOrderPay(OrderPayRequest request) {
        log.info("创建支付订单,请求参数:{}", JSON.toJSONString(request));
        
        PayResponse response = new PayResponse();
        response.setOrderNo(request.getOrderNo());
        response.setSuccess(false);
        
        try {
            // 1. 校验订单金额
            if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                response.setRespCode("PARAM_ERROR");
                response.setRespMsg("支付金额必须大于0");
                return response;
            }
            
            // 2. 校验分账信息
            if (request.getNeedSplit() && 
                (request.getSplitInfos() == null || request.getSplitInfos().isEmpty())) {
                response.setRespCode("SPLIT_ERROR");
                response.setRespMsg("分账订单必须提供分账明细");
                return response;
            }
            
            // 3. 校验分账金额总和
            if (request.getNeedSplit()) {
                BigDecimal totalSplit = request.getSplitInfos().stream()
                    .map(OrderPayRequest.SplitInfo::getDivAmount)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
                
                if (totalSplit.compareTo(request.getAmount()) > 0) {
                    response.setRespCode("SPLIT_ERROR");
                    response.setRespMsg("分账金额总和不能大于支付金额");
                    return response;
                }
            }
            
            // 4. 根据支付方式调用不同接口
            Map<String, Object> payResult;
            switch (request.getPayType()) {
                case "WECHAT_MINI":  // 微信小程序支付
                    payResult = doWxMiniPay(request);
                    break;
                case "WECHAT_JS":    // 微信公众号支付
                    payResult = doWxJsPay(request);
                    break;
                case "WECHAT_APP":   // 微信APP支付
                    payResult = doWxAppPay(request);
                    break;
                case "ALIPAY":       // 支付宝支付
                    payResult = doAlipay(request);
                    break;
                case "BALANCE":       // 余额支付
                    payResult = doBalancePay(request);
                    break;
                default:
                    response.setRespCode("PAY_TYPE_ERROR");
                    response.setRespMsg("不支持的支付方式");
                    return response;
            }
            
            // 5. 处理支付结果
            String subRespCode = (String) payResult.get("sub_resp_code");
            if ("00000000".equals(subRespCode)) {
                response.setSuccess(true);
                response.setRespCode("SUCCESS");
                response.setRespMsg("支付成功");
                response.setTradeNo((String) payResult.get("trade_no"));
                response.setPayStatus(2); // 支付成功
                response.setPayTime(new Date());
                
                // 解析支付参数(用于小程序/公众号调起支付)
                response.setPayParams(parsePayParams(payResult, request.getPayType()));
                
                log.info("支付成功,订单号:{},交易号:{}", 
                    request.getOrderNo(), payResult.get("trade_no"));
                
                // 6. 如果需要分账,记录分账信息
                if (request.getNeedSplit()) {
                    saveSplitInfo(request);
                }
                
            } else if ("00000100".equals(subRespCode)) {
                // 支付处理中
                response.setSuccess(true);
                response.setRespCode("PROCESSING");
                response.setRespMsg("支付处理中");
                response.setPayStatus(1); // 支付中
                log.info("支付处理中,订单号:{}", request.getOrderNo());
            } else {
                String subRespDesc = (String) payResult.get("sub_resp_desc");
                response.setRespCode(subRespCode);
                response.setRespMsg(subRespDesc != null ? subRespDesc : "支付失败");
                response.setPayStatus(3); // 支付失败
                log.error("支付失败,订单号:{},错误码:{},错误信息:{}", 
                    request.getOrderNo(), subRespCode, subRespDesc);
            }
            
        } catch (BasePayException e) {
            log.error("汇付支付异常", e);
            response.setRespCode("SYSTEM_ERROR");
            response.setRespMsg("支付系统异常:" + e.getMessage());
        } catch (Exception e) {
            log.error("支付处理异常", e);
            response.setRespCode("SYSTEM_ERROR");
            response.setRespMsg("系统异常:" + e.getMessage());
        }
        
        return response;
    }
    
    /**
     * 微信小程序支付
     */
    private Map<String, Object> doWxMiniPay(OrderPayRequest request) throws BasePayException {
        String orderNo = request.getOrderNo();
        String amount = request.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        String openId = getWxOpenId(request.getMerchantId()); // 获取用户openId
        
        WxMiniPayRequest payRequest = new WxMiniPayRequest(
            getReqSeqId(),      // 请求流水号
            orderNo,            // 商户订单号
            amount,             // 支付金额
            openId,             // 用户openId
            request.getSubject() // 商品描述
        );
        
        // 设置分账标识 - 对应功能表:成本价给供应商分账
        if (request.getNeedSplit()) {
            payRequest.setNeedSplit("Y");
            payRequest.setSplitInfo(buildSplitInfo(request.getSplitInfos()));
        }
        
        // 设置异步通知地址
        payRequest.addExtendInfo("notify_url", 
            "virgo://" + request.getNotifyUrl());
        
        return BasePayClient.request(payRequest);
    }
    
    /**
     * 微信公众号支付
     */
    private Map<String, Object> doWxJsPay(OrderPayRequest request) throws BasePayException {
        String orderNo = request.getOrderNo();
        String amount = request.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        String openId = getWxOpenId(request.getMerchantId());
        
        JsapiPayRequest payRequest = new JsapiPayRequest(
            getReqSeqId(),
            orderNo,
            amount,
            "WECHAT",           // 支付类型
            openId,
            request.getSubject()
        );
        
        if (request.getNeedSplit()) {
            payRequest.setNeedSplit("Y");
            payRequest.setSplitInfo(buildSplitInfo(request.getSplitInfos()));
        }
        
        payRequest.addExtendInfo("notify_url", 
            "virgo://" + request.getNotifyUrl());
        
        return BasePayClient.request(payRequest);
    }
    
    /**
     * 微信APP支付
     */
    private Map<String, Object> doWxAppPay(OrderPayRequest request) throws BasePayException {
        String orderNo = request.getOrderNo();
        String amount = request.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        
        WxAppPayRequest payRequest = new WxAppPayRequest(
            getReqSeqId(),
            orderNo,
            amount,
            request.getSubject()
        );
        
        if (request.getNeedSplit()) {
            payRequest.setNeedSplit("Y");
            payRequest.setSplitInfo(buildSplitInfo(request.getSplitInfos()));
        }
        
        payRequest.addExtendInfo("notify_url", 
            "virgo://" + request.getNotifyUrl());
        
        return BasePayClient.request(payRequest);
    }
    
    /**
     * 支付宝支付
     */
    private Map<String, Object> doAlipay(OrderPayRequest request) throws BasePayException {
        String orderNo = request.getOrderNo();
        String amount = request.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString();
        
        // 使用通用接口调用
        Map<String, Object> params = new HashMap<>();
        params.put("trade_no", orderNo);
        params.put("trade_amt", amount);
        params.put("goods_desc", request.getSubject());
        params.put("notify_url", request.getNotifyUrl());
        
        if (request.getNeedSplit()) {
            params.put("need_split", "Y");
            params.put("split_info", buildSplitInfo(request.getSplitInfos()));
        }
        
        return BasePayClient.request(params, "ALIPAY_JS_PAY");
    }
    
    /**
     * 余额支付
     * 对应功能表:支付类型包括余额
     */
    private Map<String, Object> doBalancePay(OrderPayRequest request) {
        Map<String, Object> result = new HashMap<>();
        
        try {
            // 1. 校验用户余额
            BigDecimal balance = getUserBalance(request.getMerchantId());
            if (balance.compareTo(request.getAmount()) < 0) {
                result.put("sub_resp_code", "BALANCE_INSUFFICIENT");
                result.put("sub_resp_desc", "余额不足");
                return result;
            }
            
            // 2. 扣减余额
            deductBalance(request.getMerchantId(), request.getAmount());
            
            // 3. 生成交易记录
            String tradeNo = generateTradeNo();
            saveBalancePayRecord(request, tradeNo);
            
            result.put("sub_resp_code", "00000000");
            result.put("trade_no", tradeNo);
            
            // 4. 如果需要分账,处理分账逻辑
            if (request.getNeedSplit()) {
                processBalanceSplit(request, tradeNo);
            }
            
        } catch (Exception e) {
            log.error("余额支付异常", e);
            result.put("sub_resp_code", "SYSTEM_ERROR");
            result.put("sub_resp_desc", "余额支付异常");
        }
        
        return result;
    }
    
    /**
     * 构建分账信息
     * 对应功能表:成本价给供应商分账、优惠券+成本价
     * 参考汇付分账串格式:[citation:2]
     */
    private String buildSplitInfo(List<OrderPayRequest.SplitInfo> splitInfos) {
        List<Map<String, Object>> splitList = new ArrayList<>();
        
        for (OrderPayRequest.SplitInfo info : splitInfos) {
            Map<String, Object> splitItem = new HashMap<>();
            splitItem.put("huifu_id", info.getHuifuId());
            splitItem.put("div_amt", info.getDivAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString());
            splitList.add(splitItem);
        }
        
        Map<String, Object> splitInfo = new HashMap<>();
        splitInfo.put("acct_infos", splitList);
        
        return JSON.toJSONString(splitInfo);
    }
    
    /**
     * 解析支付参数
     */
    private PayResponse.PayParams parsePayParams(Map<String, Object> payResult, String payType) {
        PayResponse.PayParams params = new PayResponse.PayParams();
        
        if ("WECHAT_MINI".equals(payType)) {
            // 小程序支付参数
            params.setAppId((String) payResult.get("appid"));
            params.setTimeStamp((String) payResult.get("timeStamp"));
            params.setNonceStr((String) payResult.get("nonceStr"));
            params.setPackageStr((String) payResult.get("package"));
            params.setSignType((String) payResult.get("signType"));
            params.setPaySign((String) payResult.get("paySign"));
        } else if ("WECHAT_JS".equals(payType)) {
            // 公众号支付参数
            params.setAppId((String) payResult.get("appId"));
            params.setTimeStamp((String) payResult.get("timeStamp"));
            params.setNonceStr((String) payResult.get("nonceStr"));
            params.setPackageStr((String) payResult.get("package"));
            params.setSignType((String) payResult.get("signType"));
            params.setPaySign((String) payResult.get("paySign"));
        }
        
        return params;
    }
    
    /**
     * 保存分账信息
     * 对应功能表:成本价给供应商分账、商户结算设置
     */
    private void saveSplitInfo(OrderPayRequest request) {
        // 保存分账记录到数据库
        for (OrderPayRequest.SplitInfo info : request.getSplitInfos()) {
            // 插入分账记录表
            String sql = "INSERT INTO eb_split_record (" +
                "order_no, merchant_id, huifu_id, div_amount, " +
                "split_status, freeze_days, create_time) VALUES (?, ?, ?, ?, ?, ?, ?)";
            
            // 计算解冻时间 - 对应功能表:商户结算设置
            Date freezeEndTime = new Date();
            Calendar cal = Calendar.getInstance();
            cal.setTime(freezeEndTime);
            cal.add(Calendar.DAY_OF_MONTH, huifuPayConfig.getSplitFreezeDays());
            
            // 执行插入操作
            // jdbcTemplate.update(sql, ...);
        }
    }
    
    /**
     * 处理余额分账
     */
    private void processBalanceSplit(OrderPayRequest request, String tradeNo) {
        for (OrderPayRequest.SplitInfo info : request.getSplitInfos()) {
            // 1. 给供应商加余额
            addSupplierBalance(info.getHuifuId(), info.getDivAmount());
            
            // 2. 记录分账流水
            saveSplitFlow(request.getOrderNo(), tradeNo, info);
            
            // 3. 如果是平台手续费,记录到平台收益
            if (info.getIsPlatformFee() != null && info.getIsPlatformFee()) {
                savePlatformIncome(request.getOrderNo(), info.getDivAmount());
            }
        }
    }
    
    /**
     * 生成请求流水号
     */
    private String getReqSeqId() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return sdf.format(new Date()) + UUID.randomUUID().toString().substring(0, 4);
    }
    
    /**
     * 生成交易号
     */
    private String generateTradeNo() {
        return "TR" + System.currentTimeMillis() + 
            UUID.randomUUID().toString().substring(0, 6);
    }
    
    // 以下为需要实现的业务方法(根据您的业务逻辑实现)
    private String getWxOpenId(String merchantId) { return ""; }
    private BigDecimal getUserBalance(String merchantId) { return BigDecimal.ZERO; }
    private void deductBalance(String merchantId, BigDecimal amount) {}
    private void addSupplierBalance(String huifuId, BigDecimal amount) {}
    private void saveBalancePayRecord(OrderPayRequest request, String tradeNo) {}
    private void saveSplitFlow(String orderNo, String tradeNo, OrderPayRequest.SplitInfo info) {}
    private void savePlatformIncome(String orderNo, BigDecimal amount) {}
}

五、支付控制器


package com.crmeb.payment.controller;

import com.crmeb.payment.model.request.OrderPayRequest;
import com.crmeb.payment.model.response.PayResponse;
import com.crmeb.payment.service.HuifuPayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

/**
 * 汇付支付控制器
 * 对应功能表:订单支付、退款、分账
 */
@Slf4j
@Api(tags = "汇付支付接口")
@RestController
@RequestMapping("/api/pay/huifu")
public class HuifuPayController {
    
    @Autowired
    private HuifuPayService huifuPayService;
    
    /**
     * 创建支付订单
     * 对应功能表:下单与支付
     */
    @ApiOperation("创建支付订单")
    @PostMapping("/create")
    public PayResponse createPayOrder(@Valid @RequestBody OrderPayRequest request) {
        return huifuPayService.createOrderPay(request);
    }
    
    /**
     * 支付结果通知回调
     * 对应功能表:支付结果同步
     */
    @ApiOperation("支付结果回调")
    @PostMapping("/notify")
    public String payNotify(@RequestParam Map<String, String> params) {
        log.info("收到支付回调:{}", params);
        
        // 验签
        boolean verifySign = verifyNotifySign(params);
        if (!verifySign) {
            return "fail";
        }
        
        // 处理订单状态
        String orderNo = params.get("order_no");
        String tradeStatus = params.get("trade_status");
        
        if ("SUCCESS".equals(tradeStatus)) {
            // 更新订单为已支付
            updateOrderPaid(orderNo, params.get("trade_no"));
            
            // 如果有分账,触发分账处理
            if (params.containsKey("need_split")) {
                triggerSplit(orderNo);
            }
        }
        
        return "success";
    }
    
    /**
     * 查询支付结果
     * 对应功能表:支付结果同步
     */
    @ApiOperation("查询支付结果")
    @GetMapping("/query/{orderNo}")
    public PayResponse queryPayResult(@PathVariable String orderNo) {
        // 调用汇付查询接口
        Map<String, Object> params = new HashMap<>();
        params.put("order_no", orderNo);
        
        try {
            Map<String, Object> result = BasePayClient.request(params, "QUERY_TRADE");
            return buildQueryResponse(result);
        } catch (Exception e) {
            log.error("查询支付结果异常", e);
            PayResponse response = new PayResponse();
            response.setSuccess(false);
            response.setRespCode("QUERY_ERROR");
            response.setRespMsg("查询失败");
            return response;
        }
    }
    
    /**
     * 订单退款
     * 对应功能表:订单全额退款、订单部分退款
     */
    @ApiOperation("订单退款")
    @PostMapping("/refund")
    public RefundResponse refund(@Valid @RequestBody RefundRequest request) {
        log.info("退款请求:{}", JSON.toJSONString(request));
        
        try {
            // 校验商户余额 - 对应功能表:没有余额提示账号余额不足
            if (!checkMerchantBalance(request.getMerchantId(), request.getAmount())) {
                RefundResponse response = new RefundResponse();
                response.setSuccess(false);
                response.setRespCode("BALANCE_INSUFFICIENT");
                response.setRespMsg("商户账户余额不足");
                return response;
            }
            
            // 调用汇付退款接口
            Map<String, Object> refundParams = new HashMap<>();
            refundParams.put("orig_order_no", request.getOrderNo());
            refundParams.put("refund_order_no", generateRefundNo());
            refundParams.put("refund_amt", request.getAmount().setScale(2, BigDecimal.ROUND_HALF_UP).toString());
            refundParams.put("refund_desc", request.getRefundReason());
            
            Map<String, Object> result = BasePayClient.request(refundParams, "REFUND");
            
            // 处理退款结果
            return buildRefundResponse(result);
            
        } catch (BasePayException e) {
            log.error("退款异常", e);
            RefundResponse response = new RefundResponse();
            response.setSuccess(false);
            response.setRespCode("REFUND_ERROR");
            response.setRespMsg("退款处理异常");
            return response;
        }
    }
    
    // 私有方法
    private boolean verifyNotifySign(Map<String, String> params) { return true; }
    private void updateOrderPaid(String orderNo, String tradeNo) {}
    private void triggerSplit(String orderNo) {}
    private PayResponse buildQueryResponse(Map<String, Object> result) { return new PayResponse(); }
    private boolean checkMerchantBalance(String merchantId, BigDecimal amount) { return true; }
    private String generateRefundNo() { return "REF" + System.currentTimeMillis(); }
    private RefundResponse buildRefundResponse(Map<String, Object> result) { return new RefundResponse(); }
}

六、退款请求/响应模型


package com.crmeb.payment.model.request;

import lombok.Data;
import javax.validation.constraints.*;
import java.math.BigDecimal;

/**
 * 退款请求
 * 对应功能表:订单全额退款、订单部分退款
 */
@Data
public class RefundRequest {
    
    /** 原订单号 */
    @NotBlank(message = "原订单号不能为空")
    private String orderNo;
    
    /** 商户号 */
    @NotBlank(message = "商户号不能为空")
    private String merchantId;
    
    /** 退款金额 */
    @NotNull(message = "退款金额不能为空")
    @DecimalMin(value = "0.01", message = "退款金额最小为0.01元")
    private BigDecimal amount;
    
    /** 退款原因 */
    private String refundReason;
    
    /** 退款类型:FULL-全额退款 PART-部分退款 */
    @NotBlank(message = "退款类型不能为空")
    private String refundType;
    
    /** 退款商品ID列表(部分退款时需要) */
    private String productIds;
}
package com.crmeb.payment.model.response;

import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;

/**
 * 退款响应
 */
@Data
public class RefundResponse {
    
    /** 是否成功 */
    private Boolean success;
    
    /** 响应码 */
    private String respCode;
    
    /** 响应描述 */
    private String respMsg;
    
    /** 原订单号 */
    private String orderNo;
    
    /** 退款单号 */
    private String refundNo;
    
    /** 退款金额 */
    private BigDecimal refundAmount;
    
    /** 退款状态:0-处理中 1-成功 2-失败 */
    private Integer refundStatus;
    
    /** 退款时间 */
    private Date refundTime;
}

七、分账查询定时任务


package com.crmeb.payment.task;

import com.alibaba.fastjson.JSON;
import com.crmeb.payment.config.HuifuPayConfig;
import com.huifu.pay.sdk.BasePayClient;
import com.huifu.pay.sdk.exception.BasePayException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 分账查询定时任务
 * 对应功能表:成本价给供应商分账、商户结算设置
 */
@Slf4j
@Component
public class SplitQueryTask {
    
    @Autowired
    private HuifuPayConfig huifuPayConfig;
    
    /**
     * 每小时查询一次待分账订单
     * 对应功能表:订单状态扭转、结算状态
     */
    @Scheduled(cron = "0 0 * * * ?")
    public void queryPendingSplitOrders() {
        log.info("开始查询待分账订单");
        
        // 1. 查询需要分账的订单
        List<SplitOrder> pendingOrders = getPendingSplitOrders();
        
        for (SplitOrder order : pendingOrders) {
            try {
                // 2. 查询分账结果
                Map<String, Object> params = new HashMap<>();
                params.put("order_no", order.getOrderNo());
                
                Map<String, Object> result = BasePayClient.request(params, "QUERY_SPLIT");
                
                // 3. 处理分账结果
                processSplitResult(order, result);
                
            } catch (BasePayException e) {
                log.error("查询分账失败,订单号:{}", order.getOrderNo(), e);
            }
        }
    }
    
    /**
     * 每天凌晨处理解冻分账
     * 对应功能表:商户结算设置(订单支付后/收货后/完成后)
     */
    @Scheduled(cron = "0 0 2 * * ?")
    public void processSplitUnfreeze() {
        log.info("开始处理分账解冻");
        
        // 根据结算规则查询到期分账记录
        String startRule = huifuPayConfig.getSplitStartRule();
        List<SplitRecord> records = getExpiredSplitRecords(startRule);
        
        for (SplitRecord record : records) {
            // 更新分账状态为可结算
            updateSplitSettable(record);
            
            log.info("分账解冻完成,ID:{}", record.getId());
        }
    }
    
    /**
     * 待分账订单
     */
    private static class SplitOrder {
        private String orderNo;
        private String merchantId;
        // getter/setter
    }
    
    /**
     * 分账记录
     */
    private static class SplitRecord {
        private Long id;
        private String orderNo;
        private String huifuId;
        private BigDecimal amount;
        // getter/setter
    }
    
    private List<SplitOrder> getPendingSplitOrders() { return null; }
    private void processSplitResult(SplitOrder order, Map<String, Object> result) {}
    private List<SplitRecord> getExpiredSplitRecords(String rule) { return null; }
    private void updateSplitSettable(SplitRecord record) {}
}

八、application.yml配置


# 汇付支付配置
huifu:
  pay:
    product-id: ${HF_PRODUCT_ID:你的产品号}
    sys-id: ${HF_SYS_ID:你的系统号}
    private-key: ${HF_PRIVATE_KEY:你的私钥}
    public-key: ${HF_PUBLIC_KEY:你的公钥}
    prod-mode: ${HF_PROD_MODE:false}
    debug: ${HF_DEBUG:true}
    # 分账冻结时间(天)
    split-freeze-days: ${HF_SPLIT_FREEZE_DAYS:7}
    # 分账开始规则:order_paid-支付后 order_received-收货后 order_completed-完成后
    split-start-rule: ${HF_SPLIT_START_RULE:order_received}

九、对照表

功能表模块对应代码说明
订单支付HuifuPayController.createPayOrder()创建支付订单,支持微信/支付宝/余额
拆单逻辑OrderPayRequest + 分账明细多商户订单根据商户拆单
成本价给供应商分账buildSplitInfo() + saveSplitInfo()按成本价计算分账金额
优惠券+成本价分账计算逻辑平台优惠券不影响供应商结算
订单全额/部分退款HuifuPayController.refund()支持全额和部分退款
余额不足提示refund()方法中的余额校验商户余额不足时提示
商户结算设置HuifuPayConfig + 定时任务分账冻结时间、开始规则
订单状态扭转payNotify() + queryPayResult()支付结果同步
结算状态SplitQueryTask分账状态查询和解冻

十、Maven依赖


<dependencies>
    <!-- 汇付支付SDK -->
    <dependency>
        <groupId>com.huifu.pay</groupId>
        <artifactId>huifu-pay-sdk-core</artifactId>
        <version>1.0.1</version>
    </dependency>
    
    <!-- 第三方依赖(汇付SDK需要)[citation:1] -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpclient</artifactId>
        <version>4.5.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpmime</artifactId>
        <version>4.5.2</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.70</version>
    </dependency>
</dependencies>

十一、部署注意事项

  1. 环境要求:Java 1.8+,TLSv1.2以上协议-7
  2. 密钥生成:使用OpenSSL生成RSA密钥对,长度1024位-7
  3. 回调地址:异步通知仅支持443或80端口​


{{voteData.voteSum}} 人已参与
支持
反对
请登录后查看

分账汇付斗拱定制开发对接 最后编辑于2026-03-03 16:09:30

快捷回复
回复
回复
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
排序 默认正序 回复倒序 点赞倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level || item.bbs_level }}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推荐': '推荐'}}
{{item.is_suggest == 1? '取消推荐': '推荐'}} 【已收集】
{{item.floor}}# 沙发 板凳 地板 {{item.floor}}# 【已收集】
{{item.user_info.title || '暂无简介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
打赏
已打赏¥{{item.reward_price}}
{{item.like_count}}
分享
{{item.showReply ? '取消回复' : '回复'}}
删除
回复
回复

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
打赏
已打赏¥{{itemc.reward_price}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
回复
回复
收起 展开更多
查看更多
打赏
已打赏¥{{reward_price}}
45
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

快速安全登录

使用微信扫码登录
回复
回复
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定
打赏金额
当前余额:¥{{rewardUserInfo.reward_price}}
{{item.price}}元
请输入 0.1-{{reward_max_price}} 范围内的数值
打赏成功
¥{{price}}
完成 确认打赏

微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服
CRMEB咨询热线 400-8888-794

扫码领取产品资料

功能清单
思维导图
安装教程
CRMEB开源商城下载 源码下载 CRMEB帮助文档 帮助文档
返回顶部 返回顶部
CRMEB客服