退款调试

This commit is contained in:
Jack 2025-06-17 11:38:00 +08:00
parent 108ab7443f
commit c4791fd6e9
5 changed files with 153 additions and 21 deletions

View File

@ -10,6 +10,7 @@ package com.suisung.mall.pay.service;
import cn.hutool.json.JSONObject;
import com.suisung.mall.common.api.CommonResult;
import org.springframework.data.util.Pair;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -62,6 +63,19 @@ public interface LakalaPayService {
*/
JSONObject refund(Integer storeId, String out_trade_no, String origin_trade_no, String refund_amount, String refund_reason, String requestIP);
/**
* 执行内部拉卡拉交易退款
* 参考地址https://o.lakala.com/#/home/document/detail?id=113
*
* @param storeId 店铺ID
* @param outTradeNo 外部交易订单号
* @param originTradeNo 原拉卡拉交易流水号
* @param refundAmount 退款金额单位
* @param refundReason 退款原因
* @return Pair<Boolean, String>包含退款是否成功以及消息
*/
Pair<Boolean, String> innerLklRefund(Integer storeId, String outTradeNo, String originTradeNo, String refundAmount, String refundReason);
/**
* 账户余额查询
* 参考https://o.lakala.com/#/home/document/detail?id=364

View File

@ -37,7 +37,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -50,6 +53,8 @@ import java.util.List;
@Service
public class LakalaPayServiceImpl implements LakalaPayService {
private static final boolean init = false;
//### 可选的两个参数不同的店铺商家可以数据库里配置不同的商户号和终端号
@Value("${lakala.merchant_no}")
public String merchantNo; // 拉卡拉分配的商户号
@ -331,6 +336,91 @@ public class LakalaPayServiceImpl implements LakalaPayService {
}
}
/**
* 执行内部拉卡拉交易退款
* 参考地址https://o.lakala.com/#/home/document/detail?id=113
*
* @param storeId 店铺ID
* @param outTradeNo 退货订单号
* @param originTradeNo 原拉卡拉交易流水号
* @param refundAmount 退款金额单位
* @param refundReason 退款原因
* @return Pair<Boolean, String>包含退款是否成功以及消息
*/
@Override
public Pair<Boolean, String> innerLklRefund(Integer storeId, String outTradeNo, String originTradeNo, String refundAmount, String refundReason) {
try {
log.info("开始执行拉卡拉内部退款,参数: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, refundReason={}",
storeId, outTradeNo, originTradeNo, refundAmount, refundReason);
// 1. 获取请求IP
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
log.error("无法获取HttpServletRequest退款失败");
return Pair.of(false, I18nUtil._("系统异常,无法获取请求信息!"));
}
HttpServletRequest request = attributes.getRequest();
String requestIp = IpKit.getRealIp(request);
// 2. 校验参数
if (ObjectUtil.isEmpty(storeId) || org.apache.commons.lang3.StringUtils.isAnyBlank(outTradeNo, refundAmount, originTradeNo)) {
log.warn("退款请求参数不完整: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, requestIp={}", storeId, outTradeNo, originTradeNo, refundAmount, requestIp);
return Pair.of(false, I18nUtil._("缺少必要参数,退款失败!"));
}
if (!refundAmount.matches("\\d+") || Integer.parseInt(refundAmount) <= 0) {
log.warn("退款金额不合法: refundAmount={}", refundAmount);
return Pair.of(false, I18nUtil._("退款金额不合法!"));
}
// 3. 初始化拉卡拉SDK
initLKLSDK();
// 4. 获取店铺的拉卡拉商户号和终端号
ShopStoreBase shopStoreBase = shopService.getLklMerchantNoAndTermNo(storeId);
if (shopStoreBase == null || org.apache.commons.lang3.StringUtils.isAnyBlank(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no())) {
log.error("无法获取店铺的拉卡拉商户号或终端号: storeId={}", storeId);
return Pair.of(false, I18nUtil._("缺少商户号参数,退款失败!"));
}
// 5. 构造退款请求并发送
V3LabsRelationRefundRequest refundRequest = new V3LabsRelationRefundRequest();
refundRequest.setOutTradeNo(outTradeNo);
refundRequest.setMerchantNo(shopStoreBase.getLkl_merchant_no());
refundRequest.setTermNo(shopStoreBase.getLkl_term_no());
refundRequest.setRefundAmount(refundAmount);
refundRequest.setRefundReason(refundReason);
refundRequest.setOriginTradeNo(originTradeNo);
refundRequest.setLocationInfo(new V3LabsTradeLocationInfo(requestIp, null, ""));
log.info("拉卡拉退款请求参数: {}", JSONUtil.toJsonStr(refundRequest));
String responseString = LKLSDK.httpPost(refundRequest);
// 6. 处理响应
if (StrUtil.isBlank(responseString)) {
log.error("拉卡拉退款接口无响应");
return Pair.of(false, I18nUtil._("服务端无返回值,退款失败!"));
}
log.info("拉卡拉退款接口响应: {}", responseString);
JSONObject lakalaResponseJson = JSONUtil.parseObj(responseString);
if (lakalaResponseJson == null) {
log.error("拉卡拉退款接口返回值解析失败: responseString={}", responseString);
return Pair.of(false, I18nUtil._("返回值解析失败,退款失败!"));
}
if (!"BBS00000".equals(lakalaResponseJson.getStr("code"))) {
String errorMessage = lakalaResponseJson.getStr("msg", "未知错误");
log.error("拉卡拉退款失败, 错误信息: {}", errorMessage);
return Pair.of(false, I18nUtil._(errorMessage));
}
log.info("拉卡拉退款成功: outTradeNo={}", outTradeNo);
return Pair.of(true, I18nUtil._("退款成功!"));
} catch (SDKException e) {
log.error("拉卡拉退款SDK异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款SDK异常退款失败") + e.getMessage());
} catch (Exception e) {
log.error("拉卡拉退款发生未知异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款发生未知异常,退款失败!") + e.getMessage());
}
}
/**
* 账户余额查询
* 参考https://o.lakala.com/#/home/document/detail?id=364

View File

@ -25,6 +25,8 @@ import com.suisung.mall.pay.mapper.PayConsumeTradeMapper;
import com.suisung.mall.pay.service.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -71,6 +73,10 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
@Autowired
private PayConsumeTradeMapper payConsumeTradeMapper;
@Lazy
@Autowired
private LakalaPayService lakalaPayService;
@Override
public List<Map> fixConsumeTrade(List<Map> rows, String field_key) {
if (ObjectUtil.isNull(rows)) return new ArrayList<>();
@ -1014,18 +1020,21 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
@Transactional
@Override
public boolean doRefund(List<ShopOrderReturn> returnRows) {
log.info("执行退款操作开始");
log.info("执行退款操作开始...");
if (CollUtil.isEmpty(returnRows)) {
log.warn("退款订单列表为空,停止退款操作");
return false;
}
// log.info("退款订单列表大小: {}", returnRows.size());
// 存储已支付的退货单ID列表
List<String> paidReturnIdList = new ArrayList<>();
// 获取是否原路退回配置默认为 false
boolean refundToOriginal = accountBaseConfigService.getConfig("order_refund_flag", false);
log.info("是否原路退回:{}", refundToOriginal);
// 提取所有订单ID并去重
List<String> orderIdList = returnRows.stream().map(ShopOrderReturn::getOrder_id).distinct().collect(Collectors.toList());
@ -1079,6 +1088,8 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
// 循环处理每个退货单
for (ShopOrderReturn returnRow : returnRows) {
// log.info("处理退货单: {}", returnRow.getReturn_id());
try {
// 获取买家用户ID
Integer buyerUserId = returnRow.getBuyer_user_id();
@ -1203,7 +1214,6 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
buyerConsumeRecordRow.setRecord_time(currentTime);
// 设置买家消费记录的支付方式ID
buyerConsumeRecordRow.setPayment_met_id(PaymentType.PAYMENT_MET_MONEY);
// 增加买家流水
buyerConsumeRecordRow.setRecord_money(waitingRefundAmount); // 佣金问题
// 设置买家消费记录的交易类型ID
@ -1322,7 +1332,6 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
orderDataRow.setOrder_refund_agree_points(NumberUtil.add(orderRefundAgreePoints, refundPoints));
}
// TODO: 优化代码
// 如果需要原路退回
if (refundToOriginal) {
// 读取在线支付信息如果无在线支付信息则余额支付 否则在线支付联合支付判断
@ -1330,6 +1339,8 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
depositQueryWrapper.apply(orderId != null, "FIND_IN_SET ('" + orderId + "', order_id )");
PayConsumeDeposit consumeRow = payConsumeDepositService.findOne(depositQueryWrapper);
// log.info("查询到的在线支付信息: {}", consumeRow);
// 如果存在在线支付信息
if (consumeRow != null) {
// 获取支付渠道ID
@ -1338,17 +1349,33 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
PayPaymentChannel paymentChannelRow = payPaymentChannelService.get(paymentChannelId);
// 获取支付渠道代码
String paymentChannelCode = paymentChannelRow.getPayment_channel_code();
// 获取支付总金额
// 获取支付总金额交易金额
BigDecimal depositTotalFee = consumeRow.getDeposit_total_fee();
log.debug("支付渠道代码: {}, 支付总金额: {}", paymentChannelCode, depositTotalFee);
log.info("支付渠道代码: {}, 支付总金额: {}", paymentChannelCode, depositTotalFee);
// 如果支付渠道编号为支付宝或微信原生支付
if (Arrays.asList("alipay", "wx_native").contains(paymentChannelCode)) {
// 计算买家余额差额退款的金额
BigDecimal moneyDifference = NumberUtil.round(NumberUtil.sub(buyerUserMoney, depositTotalFee), 2);
if (Arrays.asList("alipay", "wx_native", "lakala").contains(paymentChannelCode)) {
// 获取在线支付交易号
String depositTradeNo = consumeRow.getDeposit_trade_no();
// 计算买家余额差额
// BigDecimal moneyDifference = NumberUtil.round(NumberUtil.sub(buyerUserMoney, depositTotalFee), 2);
BigDecimal moneyDifference = buyerUserMoney;
// log.info("买家余额差额: {}", moneyDifference);
// 如果余额差额退款的金额大于 0
if (moneyDifference.compareTo(BigDecimal.ZERO) > 0) {
// log.info("买家余额差额大于 0调用退款接口");
// TODO 支付宝微信金额原路返回调用 拉卡拉的退款接口
Pair<Boolean, String> refundResult = lakalaPayService.innerLklRefund(
storeId,
returnRow.getReturn_id(), // 退货单ID
depositTradeNo,
String.valueOf(moneyDifference.multiply(BigDecimal.valueOf(100)).intValue()), // 单位
returnRow.getReturn_buyer_message()
);
if (!refundResult.getFirst()) {
// 如果原路退款失败直接退回余额
// 获取买家用户资源
PayUserResource payUserResource = payUserResourceService.getById(buyerUserId);
// 设置买家用户余额原余额 + 余额差额
@ -1357,6 +1384,8 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
if (!payUserResourceService.edit(payUserResource)) {
throw new ApiException(I18nUtil._("用户退款失败!"));
}
//throw new ApiException(I18nUtil._("退款原路返回失败!"));
}
}
// 如果买家用户积分大于 0
@ -1367,9 +1396,6 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
}
}
// 获取在线支付交易号
String depositTradeNo = consumeRow.getDeposit_trade_no();
// 创建订单退货单对象
ShopOrderReturn orderReturn = new ShopOrderReturn();
// 设置退货单ID
@ -1389,6 +1415,7 @@ public class PayConsumeTradeServiceImpl extends BaseServiceImpl<PayConsumeTradeM
} else {
// 非微信支付宝支付的情况
// 如果买家用户余额大于 0
log.info("买家用户余额: {}", buyerUserMoney);
if (buyerUserMoney.compareTo(BigDecimal.ZERO) > 0) {
// 获取买家用户资源
PayUserResource payUserResource = payUserResourceService.getById(buyerUserId);

View File

@ -105,6 +105,7 @@ logging:
naming: error
config: error
netflix: error
lkl: error
org: error
io: error
reactor: error

View File

@ -473,7 +473,7 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl<ShopMchEntryMapper,
queryWrapper.orderByDesc("id");
List<ShopMchEntry> recordList = list(queryWrapper);
if (CollectionUtil.isEmpty(recordList)) {
return CommonResult.success(null, "暂无申请记录");
return CommonResult.success(new JSONObject().set("approval_status", CommonConstant.MCH_APPR_STA_NONE), "请求成功");
}
ShopMchEntry record = recordList.get(0);