|
|
@@ -255,12 +255,12 @@ public class WFTOrderService {
|
|
|
|
|
|
public Boolean closeOrder(String orderNo) {
|
|
|
UserOrderInfo userOrderInfo = userOrderInfoMapper.selectOne(Wrappers.lambdaQuery(UserOrderInfo.class).eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
|
|
|
- if(ObjectUtil.isNull(userOrderInfo)){
|
|
|
+ if (ObjectUtil.isNull(userOrderInfo)) {
|
|
|
throw new BusinessException("订单不存在");
|
|
|
}
|
|
|
userOrderInfo.setOrderStatus(SystemConstants.STATUS_THREE);
|
|
|
int i = userOrderInfoMapper.updateById(userOrderInfo);
|
|
|
- if(i > 0){
|
|
|
+ if (i > 0) {
|
|
|
return Boolean.TRUE;
|
|
|
}
|
|
|
return Boolean.FALSE;
|
|
|
@@ -268,7 +268,7 @@ public class WFTOrderService {
|
|
|
|
|
|
/**
|
|
|
* 处理威富通支付通知回调
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 重要说明:
|
|
|
* 1. 接收威富通POST的XML数据流
|
|
|
* 2. 验证签名(支持RSA_1_256、RSA_1_1、MD5)
|
|
|
@@ -277,7 +277,7 @@ public class WFTOrderService {
|
|
|
* 5. 使用数据库锁避免并发问题
|
|
|
* 6. 更新订单状态
|
|
|
* 7. 必须返回纯字符串"success"或"fail"
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 通知重试机制:0/15/15/30/180/1800/1800/1800/1800/3600秒
|
|
|
*
|
|
|
* @param request HTTP请求对象
|
|
|
@@ -518,12 +518,12 @@ public class WFTOrderService {
|
|
|
|
|
|
/**
|
|
|
* 订单是否支付成功查询
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 应用场景:
|
|
|
* 1. 用户支付后前端轮询查询订单状态
|
|
|
* 2. 后台定时任务查询未支付订单
|
|
|
* 3. 支付通知未收到时的补偿查询
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 查询策略建议:
|
|
|
* - 方案一:以订单创建时间为基准,间隔5秒/30秒/1分钟/3分钟/5分钟/10分钟/30分钟查询
|
|
|
* - 方案二:定时任务每30秒查询最近10分钟内未支付订单,最多查询10次
|
|
|
@@ -575,8 +575,8 @@ public class WFTOrderService {
|
|
|
// 6. 解析查询结果
|
|
|
String tradeState = queryResult.get("trade_state");
|
|
|
String payResult = queryResult.get("pay_result");
|
|
|
-
|
|
|
- log.info("订单查询结果: orderNo={}, trade_state={}, pay_result={}",
|
|
|
+
|
|
|
+ log.info("订单查询结果: orderNo={}, trade_state={}, pay_result={}",
|
|
|
orderNo, tradeState, payResult);
|
|
|
|
|
|
// 7. 判断支付状态
|
|
|
@@ -636,14 +636,14 @@ public class WFTOrderService {
|
|
|
|
|
|
// 发送HTTP请求
|
|
|
String reqUrl = swiftpassConfig.getReq_url();
|
|
|
- org.apache.http.client.methods.HttpPost httpPost =
|
|
|
+ org.apache.http.client.methods.HttpPost httpPost =
|
|
|
new org.apache.http.client.methods.HttpPost(reqUrl);
|
|
|
- org.apache.http.entity.StringEntity entityParams =
|
|
|
+ org.apache.http.entity.StringEntity entityParams =
|
|
|
new org.apache.http.entity.StringEntity(XmlUtils.parseXML(queryParams), "utf-8");
|
|
|
httpPost.setEntity(entityParams);
|
|
|
httpPost.setHeader("Content-Type", "text/xml;utf-8");
|
|
|
|
|
|
- org.apache.http.impl.client.CloseableHttpClient client =
|
|
|
+ org.apache.http.impl.client.CloseableHttpClient client =
|
|
|
org.apache.http.impl.client.HttpClients.createDefault();
|
|
|
org.apache.http.client.methods.CloseableHttpResponse response = client.execute(httpPost);
|
|
|
|
|
|
@@ -651,15 +651,15 @@ public class WFTOrderService {
|
|
|
if (response != null && response.getEntity() != null) {
|
|
|
// 解析响应
|
|
|
Map<String, String> resultMap = XmlUtils.toMap(
|
|
|
- org.apache.http.util.EntityUtils.toByteArray(response.getEntity()),
|
|
|
+ org.apache.http.util.EntityUtils.toByteArray(response.getEntity()),
|
|
|
"utf-8");
|
|
|
-
|
|
|
+
|
|
|
log.info("查询响应结果: {}", resultMap);
|
|
|
|
|
|
// 验证签名
|
|
|
String reSign = resultMap.get("sign");
|
|
|
String reSignType = resultMap.get("sign_type");
|
|
|
-
|
|
|
+
|
|
|
if (resultMap.containsKey("sign")) {
|
|
|
boolean signValid = SignUtil.verifySign(reSign, reSignType, resultMap, swiftpassConfig);
|
|
|
if (!signValid) {
|
|
|
@@ -671,8 +671,8 @@ public class WFTOrderService {
|
|
|
}
|
|
|
|
|
|
// 检查通信状态和业务结果
|
|
|
- if ("0".equals(resultMap.get("status")) &&
|
|
|
- "0".equals(resultMap.get("result_code"))) {
|
|
|
+ if ("0".equals(resultMap.get("status")) &&
|
|
|
+ "0".equals(resultMap.get("result_code"))) {
|
|
|
resultMap.put("status", "200");
|
|
|
return resultMap;
|
|
|
} else {
|
|
|
@@ -707,7 +707,7 @@ public class WFTOrderService {
|
|
|
* 同步订单支付状态
|
|
|
* 当查询到订单已支付但本地状态未更新时,同步更新本地订单状态
|
|
|
*
|
|
|
- * @param orderInfo 订单信息
|
|
|
+ * @param orderInfo 订单信息
|
|
|
* @param queryResult 查询结果
|
|
|
*/
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
@@ -744,13 +744,13 @@ public class WFTOrderService {
|
|
|
|
|
|
int updateCount = userOrderInfoMapper.updateById(orderInfo);
|
|
|
if (updateCount > 0) {
|
|
|
- log.info("订单状态同步成功: orderNo={}, transactionId={}",
|
|
|
+ log.info("订单状态同步成功: orderNo={}, transactionId={}",
|
|
|
orderInfo.getOrderNo(), transactionId);
|
|
|
} else {
|
|
|
log.error("订单状态同步失败: orderNo={}", orderInfo.getOrderNo());
|
|
|
}
|
|
|
} else {
|
|
|
- log.error("订单金额不匹配,不同步状态: expected={}, actual={}",
|
|
|
+ log.error("订单金额不匹配,不同步状态: expected={}, actual={}",
|
|
|
expectedFee, totalFeeStr);
|
|
|
}
|
|
|
}
|
|
|
@@ -766,21 +766,22 @@ public class WFTOrderService {
|
|
|
* @return
|
|
|
*/
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public String refundOrder(Long userId,Integer type) throws Exception {
|
|
|
+ public String refundOrder(Long userId, Integer type) throws Exception {
|
|
|
|
|
|
//查询账户余额
|
|
|
UserAccount userAccount =
|
|
|
userAccountService.getOne(Wrappers.<UserAccount>lambdaQuery().eq(UserAccount::getUserId, userId).last("limit 1"));
|
|
|
if (userAccount.getBalance().compareTo(BigDecimal.ZERO) == 0) {
|
|
|
- return "账户余额为 0";
|
|
|
+ return "账户余额为 0";
|
|
|
}
|
|
|
BigDecimal refundMoney = userAccount.getBalance();
|
|
|
|
|
|
- //查询一年内已支付的所有券订单
|
|
|
+ //查询一年内已支付且可退款的所有券订单(已支付和部分退款状态)
|
|
|
List<UserOrderInfo> userOrderInfoList = userOrderInfoMapper.selectList(Wrappers.<UserOrderInfo>lambdaQuery()
|
|
|
.eq(UserOrderInfo::getUserId, userId)
|
|
|
- .in(UserOrderInfo::getOrderStatus, SystemConstants.STATUS_TWO,SystemConstants.STATUS_FIVE)
|
|
|
+ .in(UserOrderInfo::getOrderStatus, SystemConstants.STATUS_TWO, SystemConstants.STATUS_FIVE)
|
|
|
.between(UserOrderInfo::getCreateTime, LocalDateTime.now().minusYears(1), LocalDateTime.now())
|
|
|
+ .orderByAsc(UserOrderInfo::getCreateTime) // 按创建时间升序,优先退早期订单
|
|
|
);
|
|
|
|
|
|
if (CollUtil.isEmpty(userOrderInfoList)) {
|
|
|
@@ -788,17 +789,22 @@ public class WFTOrderService {
|
|
|
throw new BusinessException("无法进行退款操作,请联系客服处理!");
|
|
|
}
|
|
|
for (UserOrderInfo userOrderInfo : userOrderInfoList) {
|
|
|
- if(refundMoney.compareTo(BigDecimal.ZERO) == 0){
|
|
|
+ if (refundMoney.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
break;
|
|
|
}
|
|
|
// 处理退款金额为null的情况,默认为0
|
|
|
BigDecimal alreadyRefundMoney = userOrderInfo.getRefundMoney() == null ? BigDecimal.ZERO : userOrderInfo.getRefundMoney();
|
|
|
BigDecimal canRefundMoney = userOrderInfo.getOrderMoney().subtract(alreadyRefundMoney);
|
|
|
-
|
|
|
+
|
|
|
if (canRefundMoney.compareTo(refundMoney) >= 0) {
|
|
|
- //退款金额大于订单金额,则直接退退款金额
|
|
|
- refundOrder(userOrderInfo,refundMoney, "账户退款", SystemConstants.STATUS_ONE);
|
|
|
- //账户变动及日志记录(退款清空账户余额)
|
|
|
+ //可退款金额大于等于待退款金额,则直接退全部待退款金额
|
|
|
+ refundOrder(userOrderInfo, refundMoney, "账户退款", type);
|
|
|
+
|
|
|
+ //计算本次退款后的累计退款金额
|
|
|
+ BigDecimal totalRefundMoney = alreadyRefundMoney.add(refundMoney);
|
|
|
+ boolean isFullRefund = totalRefundMoney.compareTo(userOrderInfo.getOrderMoney()) >= 0;
|
|
|
+
|
|
|
+ //账户变动及日志记录
|
|
|
userAccountService.updateAccountBalanceAndLog(
|
|
|
userId,
|
|
|
refundMoney,
|
|
|
@@ -806,9 +812,9 @@ public class WFTOrderService {
|
|
|
SystemConstants.ACCOUNT_LOG_REFUND_NOTE,
|
|
|
userOrderInfo.getId()
|
|
|
);
|
|
|
+
|
|
|
//修改订单状态(判断是全额退款还是部分退款)
|
|
|
- BigDecimal totalRefundMoney = alreadyRefundMoney.add(refundMoney);
|
|
|
- if (totalRefundMoney.compareTo(userOrderInfo.getOrderMoney()) >= 0) {
|
|
|
+ if (isFullRefund) {
|
|
|
// 退款金额等于或大于订单金额,全额退款
|
|
|
userOrderInfo.setOrderStatus(SystemConstants.STATUS_FOUR);
|
|
|
} else {
|
|
|
@@ -851,7 +857,7 @@ public class WFTOrderService {
|
|
|
log.info("执行操作的 原支付交易对应的商户订单号:{}", userOrderInfo.getOrderNo());
|
|
|
|
|
|
//退款单号
|
|
|
- String out_refund_no = createOrderNo("TK",userOrderInfo.getId());
|
|
|
+ String out_refund_no = createOrderNo("TK", userOrderInfo.getId());
|
|
|
// 创建退款订单
|
|
|
UserRefundsOrderInfo userRefundsOrderInfo = new UserRefundsOrderInfo();
|
|
|
userRefundsOrderInfo.setOrderId(userOrderInfo.getId());
|
|
|
@@ -862,7 +868,7 @@ public class WFTOrderService {
|
|
|
userRefundsOrderInfo.setType(type);
|
|
|
userRefundsOrderInfo.setCreateTime(LocalDateTime.now());
|
|
|
|
|
|
- SortedMap<String,String> params = new TreeMap<>();
|
|
|
+ SortedMap<String, String> params = new TreeMap<>();
|
|
|
|
|
|
params.put("out_trade_no", userOrderInfo.getOrderNo());//商户订单号
|
|
|
params.put("out_refund_no", out_refund_no);//商户退款单号
|
|
|
@@ -875,16 +881,16 @@ public class WFTOrderService {
|
|
|
Map<String, String> refund = payUtill.refund(params, swiftpassConfig);
|
|
|
|
|
|
log.info("最终拿到的微信支付通知数据:" + refund);
|
|
|
- if (Objects.equals(refund.get("status"), "0")){
|
|
|
+ if (Objects.equals(refund.get("status"), "0")) {
|
|
|
|
|
|
log.info("退款调用成功!");
|
|
|
- if(Objects.equals(refund.get("result_code"), "0")){
|
|
|
+ if (Objects.equals(refund.get("result_code"), "0")) {
|
|
|
log.info("退款成功!");
|
|
|
log.info("订单:{},退款成功!原因:{}", userOrderInfo.getOrderNo(), reason);
|
|
|
//计算累计退款金额
|
|
|
BigDecimal currentRefundMoney = userOrderInfo.getRefundMoney() == null ? BigDecimal.ZERO : userOrderInfo.getRefundMoney();
|
|
|
BigDecimal totalRefundMoney = currentRefundMoney.add(refundAmount);
|
|
|
-
|
|
|
+
|
|
|
//修改订单状态(判断是全额退款还是部分退款)
|
|
|
if (totalRefundMoney.compareTo(userOrderInfo.getOrderMoney()) >= 0) {
|
|
|
// 退款金额等于或大于订单金额,全额退款
|
|
|
@@ -896,7 +902,7 @@ public class WFTOrderService {
|
|
|
userOrderInfo.setRefundMoney(totalRefundMoney);
|
|
|
userOrderInfo.setRefundTime(LocalDateTime.now());
|
|
|
userOrderInfoMapper.updateById(userOrderInfo);
|
|
|
- } else{
|
|
|
+ } else {
|
|
|
log.info("退款处理中");
|
|
|
//修改订单状态
|
|
|
BigDecimal currentRefundMoney = userOrderInfo.getRefundMoney() == null ? BigDecimal.ZERO : userOrderInfo.getRefundMoney();
|
|
|
@@ -912,31 +918,32 @@ public class WFTOrderService {
|
|
|
}
|
|
|
|
|
|
}
|
|
|
+
|
|
|
/**
|
|
|
* 退款查询
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 应用场景:
|
|
|
* 1. 提交退款申请后,通过调用该接口查询退款状态
|
|
|
* 2. 退款有一定延时,请在3个工作日后重新查询退款状态
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 参数优先级:
|
|
|
* refund_id > out_refund_no > transaction_id > out_trade_no
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 退款状态:
|
|
|
* - SUCCESS:退款成功
|
|
|
* - FAIL:退款失败
|
|
|
* - PROCESSING:退款处理中
|
|
|
* - CHANGE:转入代发,退款到银行发现用户的卡作废或者冻结了,
|
|
|
- * 导致原路退款银行卡失败,资金回流到商户的现金帐号,
|
|
|
- * 需要商户人工干预,通过线下或者平台转账的方式进行退款
|
|
|
+ * 导致原路退款银行卡失败,资金回流到商户的现金帐号,
|
|
|
+ * 需要商户人工干预,通过线下或者平台转账的方式进行退款
|
|
|
*
|
|
|
- * @param outTradeNo 商户订单号(四选一)
|
|
|
+ * @param outTradeNo 商户订单号(四选一)
|
|
|
* @param transactionId 平台订单号(四选一)
|
|
|
- * @param outRefundNo 商户退款单号(四选一)
|
|
|
- * @param refundId 平台退款单号(四选一,优先级最高)
|
|
|
+ * @param outRefundNo 商户退款单号(四选一)
|
|
|
+ * @param refundId 平台退款单号(四选一,优先级最高)
|
|
|
* @return 退款查询结果
|
|
|
*/
|
|
|
- public Map<String, Object> queryRefund(String outTradeNo, String transactionId,
|
|
|
+ public Map<String, Object> queryRefund(String outTradeNo, String transactionId,
|
|
|
String outRefundNo, String refundId) {
|
|
|
log.info("========== 开始查询退款状态 ==========");
|
|
|
log.info("查询参数: outTradeNo={}, transactionId={}, outRefundNo={}, refundId={}",
|
|
|
@@ -944,8 +951,8 @@ public class WFTOrderService {
|
|
|
|
|
|
try {
|
|
|
// 1. 参数校验 - 四个参数至少需要一个
|
|
|
- if (outTradeNo == null && transactionId == null &&
|
|
|
- outRefundNo == null && refundId == null) {
|
|
|
+ if (outTradeNo == null && transactionId == null &&
|
|
|
+ outRefundNo == null && refundId == null) {
|
|
|
log.error("退款查询参数不能全为空");
|
|
|
Map<String, Object> errorResult = new HashMap<>();
|
|
|
errorResult.put("status", "500");
|
|
|
@@ -955,7 +962,7 @@ public class WFTOrderService {
|
|
|
|
|
|
// 2. 构建查询请求参数
|
|
|
SortedMap<String, String> queryParams = new TreeMap<>();
|
|
|
-
|
|
|
+
|
|
|
// 按优先级添加参数:refund_id > out_refund_no > transaction_id > out_trade_no
|
|
|
if (refundId != null && !refundId.trim().isEmpty()) {
|
|
|
queryParams.put("refund_id", refundId);
|
|
|
@@ -966,7 +973,7 @@ public class WFTOrderService {
|
|
|
} else if (outTradeNo != null && !outTradeNo.trim().isEmpty()) {
|
|
|
queryParams.put("out_trade_no", outTradeNo);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
queryParams.put("sign_type", "RSA_1_256"); // 签名方式
|
|
|
|
|
|
// 3. 调用威富通退款查询API
|
|
|
@@ -979,7 +986,7 @@ public class WFTOrderService {
|
|
|
if ("200".equals(queryResult.get("status"))) {
|
|
|
@SuppressWarnings("unchecked")
|
|
|
Map<String, String> dataMap = (Map<String, String>) queryResult.get("data");
|
|
|
-
|
|
|
+
|
|
|
if (dataMap == null) {
|
|
|
log.error("退款查询结果数据为空");
|
|
|
queryResult.put("status", "500");
|
|
|
@@ -990,7 +997,7 @@ public class WFTOrderService {
|
|
|
// 5. 检查业务结果
|
|
|
String status = dataMap.get("status");
|
|
|
String resultCode = dataMap.get("result_code");
|
|
|
-
|
|
|
+
|
|
|
if (!"0".equals(status) || !"0".equals(resultCode)) {
|
|
|
log.error("退款查询失败: status={}, result_code={}, err_code={}, err_msg={}",
|
|
|
status, resultCode, dataMap.get("err_code"), dataMap.get("err_msg"));
|
|
|
@@ -1016,11 +1023,11 @@ public class WFTOrderService {
|
|
|
// 8. 解析退款记录列表(支持多笔退款)
|
|
|
if (refundCount > 0) {
|
|
|
java.util.List<Map<String, Object>> refundList = new java.util.ArrayList<>();
|
|
|
-
|
|
|
+
|
|
|
for (int i = 0; i < refundCount; i++) {
|
|
|
Map<String, Object> refundRecord = new HashMap<>();
|
|
|
String suffix = "_" + i;
|
|
|
-
|
|
|
+
|
|
|
// 提取每笔退款的详细信息
|
|
|
refundRecord.put("out_refund_id", dataMap.get("out_refund_id" + suffix));
|
|
|
refundRecord.put("out_refund_no", dataMap.get("out_refund_no" + suffix));
|
|
|
@@ -1031,15 +1038,15 @@ public class WFTOrderService {
|
|
|
refundRecord.put("refund_time", dataMap.get("refund_time" + suffix));
|
|
|
refundRecord.put("refund_status", dataMap.get("refund_status" + suffix));
|
|
|
refundRecord.put("refund_status_info", dataMap.get("refund_status_info" + suffix));
|
|
|
-
|
|
|
+
|
|
|
refundList.add(refundRecord);
|
|
|
-
|
|
|
+
|
|
|
log.info("退款记录[{}]: refund_id={}, refund_status={}, refund_fee={}",
|
|
|
- i, refundRecord.get("refund_id"),
|
|
|
- refundRecord.get("refund_status"),
|
|
|
+ i, refundRecord.get("refund_id"),
|
|
|
+ refundRecord.get("refund_status"),
|
|
|
refundRecord.get("refund_fee"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
result.put("refund_list", refundList);
|
|
|
}
|
|
|
|