一、项目结构
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>十一、部署注意事项
- 环境要求:Java 1.8+,TLSv1.2以上协议-7
- 密钥生成:使用OpenSSL生成RSA密钥对,长度1024位-7
- 回调地址:异步通知仅支持443或80端口

