diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java index 8fea6e7c..d3ce8b57 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java @@ -30,6 +30,10 @@ public class UpdateOrderSeparateJob extends QuartzJobBean { Integer processedCount = lakalaApiService.fixUnSuccessSeparateStatusJob(); logger.info("[分账状态修复定时任务] 执行完成,共处理 {} 条记录", processedCount); + + processedCount = lakalaApiService.fixUnSuccessDrawedStatusJob(); + logger.info("[提现状态修复定时任务] 执行完成,共处理 {} 条记录", processedCount); + } catch (Exception e) { logger.error("[分账状态修复定时任务] 执行过程中发生异常", e); throw new JobExecutionException(e); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LakalaController.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LakalaController.java index 9d0a5539..77ecfd50 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LakalaController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/mobile/LakalaController.java @@ -222,7 +222,7 @@ public class LakalaController extends BaseControllerImpl { @ApiOperation(value = "拉卡拉提现结果通知", notes = "拉卡拉提现结果通知") @RequestMapping(value = "/ewallet/drawNotify", method = RequestMethod.POST) public ResponseEntity ewalletWithDrawNotify(HttpServletRequest request) { - JSONObject resp = lakalaPayService.ewalletWithDrawNotify(request); + JSONObject resp = lakalaPayService.ewalletWithDrawNotify(request, "", ""); if (resp != null && "SUCCESS".equals(resp.get("code"))) { return ResponseEntity.ok(resp); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java index 9f7631f3..ed0e94de 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java @@ -272,6 +272,17 @@ public interface LakalaApiService { */ JSONObject sacsQuery(String merchantNo, String separateNo); + /** + * 提现结果查询 + *

+ * 参考:https://o.lakala.com/#/home/document/detail?id=366 + * + * @param merchantNo 分账方商户号 + * @param drawJnl 提款流水号 + * @return 提现结果数据 + */ + JSONObject ewalletWithdrawQuery(String merchantNo, String drawJnl); + /** * 商户分账业务信息查询 @@ -304,6 +315,15 @@ public interface LakalaApiService { */ Integer fixUnSuccessSeparateStatusJob(); + /** + * 更改已经提现的状态(定时任务用途) + * 该方法用于处理3天前未成功提现的订单记录,通过主动查询拉卡拉提现状态来更新本地记录 + * 每条记录最多处理5次,避免无限重试 + * + * @return 成功处理的记录数量 + */ + Integer fixUnSuccessDrawedStatusJob(); + /** * 检测修复补全商户的商户分账业务信息及分账接收方绑定关系(分账业务申请异步通知的补偿机制) * @@ -346,13 +366,15 @@ public interface LakalaApiService { Boolean ewalletWithDrawD1(String mercId, String merOrderNo, String drawAmt, String orderId, String summary); /** - * 拉卡拉账户D1提现结果通知 + * 拉卡拉账户D1提现结果通知处理(有补偿机制参数) * 参考:https://o.lakala.com/#/home/document/detail?id=367 * - * @param request - * @return + * @param request HTTP请求对象,包含拉卡拉提现结果通知的参数 + * @param merchantNo 商户号 (补偿必选) + * @param drawJnl 提现流水号 (补偿必选) + * @return JSONObject 响应结果对象 */ - JSONObject ewalletWithDrawNotify(HttpServletRequest request); + JSONObject ewalletWithDrawNotify(HttpServletRequest request, String merchantNo, String drawJnl); /** * 商户分账参数计算及评估 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java index f98153b5..a8e0fc56 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderDrawService.java @@ -11,6 +11,9 @@ package com.suisung.mall.shop.lakala.service; import com.suisung.mall.common.modules.lakala.LklOrderDraw; import com.suisung.mall.core.web.service.IBaseService; +import java.util.Date; +import java.util.List; + public interface LklOrderDrawService extends IBaseService { /** @@ -30,6 +33,18 @@ public interface LklOrderDrawService extends IBaseService { */ LklOrderDraw getByByMercIdAndMerOrderNo(String mercId, String merOrderNo); + + /** + * 分页获取未成功提现的记录 + * + * @param beginDate 开始时间(必须) + * @param endDate 结束时间(可选,为空时默认为当前时间) + * @param page 页码(从1开始) + * @param pageSize 每页大小 + * @return 未成功提现的记录列表,参数无效时返回空列表而非null + */ + List getUnSuccessDrawedList(Date beginDate, Date endDate, Integer page, Integer pageSize); + /** * 判断订单是否已经提现完成 * @@ -37,4 +52,13 @@ public interface LklOrderDrawService extends IBaseService { * @return */ Boolean isOrderDrawed(String orderId); + + /** + * 判断订单是否已经提现完成 + * + * @param merchantNo 商户号 + * @param drawJnl 提现流水号 + * @return 如果订单已提现完成返回true,否则返回false + */ + Boolean isOrderDrawed(String merchantNo, String drawJnl); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java index ba7fe700..c56ca586 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java @@ -2110,6 +2110,87 @@ public class LakalaApiServiceImpl implements LakalaApiService { } } + /** + * 提现结果查询 + *

+ * 参考:https://o.lakala.com/#/home/document/detail?id=366 + * + * @param merchantNo 分账方商户号 + * @param drawJnl 提款流水号 + * @return 提现结果数据 + */ + @Override + public JSONObject ewalletWithdrawQuery(String merchantNo, String drawJnl) { + // 1. 参数校验 + if (StrUtil.isBlank(merchantNo) || StrUtil.isBlank(drawJnl)) { + log.warn("[提现结果查询] 参数校验失败:缺少必要参数, merchantNo={}, drawJnl={}", merchantNo, drawJnl); + return null; + } + + try { + // 查询提现表lkl_order_draw是否已经提现完成? + Boolean isDrawed = lklOrderDrawService.isOrderDrawed(merchantNo, drawJnl); + if (isDrawed != null && isDrawed) { + log.warn("[提现结果查询] 已提现无效更新提现数据, merchantNo={}, drawJnl={}", merchantNo, drawJnl); + return null; + } + + // 2. 配置初始化 + log.debug("[提现结果查询] 初始化拉卡拉SDK"); + initLKLSDK(); + + // 3. 装配数据 + log.debug("[提现结果查询] 装配请求参数,merchantNo={}, drawJnl={}", merchantNo, drawJnl); + V2LaepIndustryEwalletWithdrawQueryRequest req = new V2LaepIndustryEwalletWithdrawQueryRequest(); + req.setOrgNo(orgCode); + req.setMerchantNo(merchantNo); + req.setDrawJnl(drawJnl); + + // 4. 发送请求 + log.info("[提现结果查询] 开始查询提现结果,merchantNo={}, drawJnl={}", merchantNo, drawJnl); + String responseStr = LKLSDK.httpPost(req); + + if (StrUtil.isBlank(responseStr)) { + log.error("[提现结果查询] 服务器无返回值, merchantNo={}, drawJnl={}", merchantNo, drawJnl); + return null; + } + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null) { + log.error("[提现结果查询] 响应数据解析失败, merchantNo={}, drawJnl={}, response={}", + merchantNo, drawJnl, responseStr); + return null; + } + + // 5. 检查业务状态码 + String retCode = lakalaRespJSON.getStr("retCode"); + if (!lklSuccessCode.equals(retCode)) { + log.warn("[提现结果查询] 业务处理失败, merchantNo={}, drawJnl={}, retCode={}, retMsg={}", + merchantNo, drawJnl, retCode, lakalaRespJSON.getStr("retMsg")); + return null; + } + + // 6. 检查响应数据 + JSONObject respData = lakalaRespJSON.getJSONObject("respData"); + if (respData == null) { + log.warn("[提现结果查询] 响应数据为空, merchantNo={}, drawJnl={}, response={}", + merchantNo, drawJnl, responseStr); + return null; + } + + log.info("[提现结果查询] 查询成功, merchantNo={}, drawJnl={}", merchantNo, drawJnl); + return respData; + + } catch (SDKException e) { + log.error("[提现结果查询] SDK调用异常, merchantNo={}, drawJnl={}", merchantNo, drawJnl, e); + return null; + } catch (Exception e) { + log.error("[提现结果查询] 查询过程中发生未知异常, merchantNo={}, drawJnl={}", merchantNo, drawJnl, e); + return null; + } + } + + /** * 商户分账业务信息查询 * 参考:https://o.lakala.com/#/home/document/detail?id=381 @@ -3160,6 +3241,126 @@ public class LakalaApiServiceImpl implements LakalaApiService { } + /** + * 更改已经提现的状态(定时任务用途) + * 该方法用于处理3天前未成功提现的订单记录,通过主动查询拉卡拉提现状态来更新本地记录 + * 每条记录最多处理5次,避免无限重试 + * + * @return 成功处理的记录数量 + */ + @Override + public Integer fixUnSuccessDrawedStatusJob() { + log.info("[提现状态修复任务] 开始执行未成功提现记录的状态修复任务"); + + // 获取3天前提现状态未成功的记录 + Date endDate = new Date(); + Date beginDate = DateUtils.addDays(endDate, -3); // 3天前 + + // 分页参数 + int pageSize = 200; + int currentPage = 1; + int totalSuccessCount = 0; + int totalProcessed = 0; + + // 记录处理开始时间 + long startTime = System.currentTimeMillis(); + String redisPrefKey = "lkl:draw:status:retry:"; + + try { + List lklOrderDraws; + do { + // 分页获取未成功提现的记录 + lklOrderDraws = lklOrderDrawService.getUnSuccessDrawedList(beginDate, endDate, currentPage, pageSize); + + if (CollectionUtil.isEmpty(lklOrderDraws)) { + break; + } + + log.info("[提现状态修复任务] 获取到第{}页数据,共{}条记录", currentPage, lklOrderDraws.size()); + + // 按处理次数排序,从未处理过的记录优先处理,处理次数越多优先级越低 + lklOrderDraws.sort((o1, o2) -> { + String redisKey1 = redisPrefKey + o1.getDraw_jnl(); + String redisKey2 = redisPrefKey + o2.getDraw_jnl(); + + int retryCount1 = Convert.toInt(redisService.get(redisKey1), 0); + int retryCount2 = Convert.toInt(redisService.get(redisKey2), 0); + + return Integer.compare(retryCount1, retryCount2); + }); + + // 处理当前页中的记录 + for (LklOrderDraw record : lklOrderDraws) { + // 检查该记录的处理次数是否已达到上限(5次) + String redisKey = redisPrefKey + record.getDraw_jnl(); + int retryCount = Convert.toInt(redisService.get(redisKey), 0); + + if (retryCount >= 5) { + log.warn("[提现状态修复任务] 记录已达到最大重试次数,跳过处理: merchantNo={}, drawJnl={}", + record.getMerc_id(), record.getDraw_jnl()); + continue; + } + + totalProcessed++; + // 每处理10条记录才输出一次详细日志,减少日志量 + if (totalProcessed % 10 == 1) { + log.info("[提现状态修复任务] 正在处理第 {} 条记录: merchantNo={}, drawJnl={}, 已重试{}次", + totalProcessed, record.getMerc_id(), record.getDraw_jnl(), retryCount); + } + + try { + // 增加处理次数计数并设置3天过期时间 + redisService.incr(redisKey, 1); + redisService.expire(redisKey, 3 * 24 * 60 * 60); + + // 调用拉卡拉提现查询接口进行状态补偿 + JSONObject drawResult = ewalletWithDrawNotify(null, record.getMerc_id(), record.getDraw_jnl()); + + // 检查处理结果 + if (drawResult != null && "SUCCESS".equals(drawResult.getStr("code", ""))) { + totalSuccessCount++; + log.debug("[提现状态修复任务] 记录处理成功: merchantNo={}, drawJnl={}", + record.getMerc_id(), record.getDraw_jnl()); + } else { + log.warn("[提现状态修复任务] 记录处理失败: merchantNo={}, drawJnl={}", + record.getMerc_id(), record.getDraw_jnl()); + } + } catch (Exception e) { + log.error("[提现状态修复任务] 处理记录时发生异常: merchantNo={}, drawJnl={}", + record.getMerc_id(), record.getDraw_jnl(), e); + } + } + + log.info("[提现状态修复任务] 第{}页处理完成,已处理 {} 条记录,总成功 {} 条", + currentPage, totalProcessed, totalSuccessCount); + + // 如果当前页数据少于页面大小,说明已经是最后一页 + if (lklOrderDraws.size() < pageSize) { + break; + } + + currentPage++; + + // 添加短暂延迟,避免对系统造成过大压力 + Thread.sleep(100); + + } while (!CollectionUtil.isEmpty(lklOrderDraws)); + + } catch (InterruptedException e) { + log.warn("[提现状态修复任务] 任务被中断"); + Thread.currentThread().interrupt(); + } catch (Exception e) { + log.error("[提现状态修复任务] 任务执行过程中发生异常", e); + } + + long endTime = System.currentTimeMillis(); + log.info("[提现状态修复任务] 任务执行完成,总共处理 {} 条记录,成功处理 {} 条记录,耗时 {} ms", + totalProcessed, totalSuccessCount, (endTime - startTime)); + + return totalSuccessCount; + } + + /** * 检测修复补全商户的商户分账业务信息及分账接收方绑定关系(分账业务申请异步通知的补偿机制) * @@ -3629,29 +3830,40 @@ public class LakalaApiServiceImpl implements LakalaApiService { /** - * 拉卡拉账户D1提现结果通知处理 + * 拉卡拉账户D1提现结果通知处理(有补偿机制参数) * 参考:https://o.lakala.com/#/home/document/detail?id=367 * - * @param request HTTP请求对象,包含拉卡拉提现结果通知的参数 + * @param request HTTP请求对象,包含拉卡拉提现结果通知的参数 + * @param merchantNo 商户号 (补偿必选) + * @param drawJnl 提现流水号 (补偿必选) * @return JSONObject 响应结果对象 */ @Override - public JSONObject ewalletWithDrawNotify(HttpServletRequest request) { + public JSONObject ewalletWithDrawNotify(HttpServletRequest request, String merchantNo, String drawJnl) { log.debug("[拉卡拉D1提现结果通知] 开始处理拉卡拉提现结果通知"); try { - // 1. 验签处理 - 验证通知来源的合法性 - Pair signCheckResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath, false); - if (!signCheckResult.getFirst()) { - log.warn("[拉卡拉D1提现结果通知] 验签失败: {}", signCheckResult.getSecond()); - return JSONUtil.createObj() - .set("code", "FAIL") - .set("message", signCheckResult.getSecond()); + JSONObject paramsJSON; + Pair signCheckResult = null; + if (request != null) { + // 1. 验签处理 - 验证通知来源的合法性 + signCheckResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath, false); + if (signCheckResult != null && !signCheckResult.getFirst()) { + log.warn("[拉卡拉D1提现结果通知] 验签失败: {}", signCheckResult.getSecond()); + return JSONUtil.createObj() + .set("code", "FAIL") + .set("message", signCheckResult.getSecond()); + } + + // 2. 解析回调参数 + paramsJSON = JSONUtil.parseObj(signCheckResult.getSecond()); + } else { + // 补偿机制。主动从拉卡拉上获取提现结果 + paramsJSON = ewalletWithdrawQuery(merchantNo, drawJnl); } - // 2. 解析回调参数 - JSONObject paramsJSON = JSONUtil.parseObj(signCheckResult.getSecond()); - if (paramsJSON == null) { + + if (JSONUtil.isNull(paramsJSON)) { log.warn("[拉卡拉D1提现结果通知] 回调参数解析失败"); return JSONUtil.createObj() .set("code", "FAIL") @@ -3661,16 +3873,25 @@ public class LakalaApiServiceImpl implements LakalaApiService { String drawState = paramsJSON.getStr("drawState"); String mercId = paramsJSON.getStr("mercId"); String merOrderNo = paramsJSON.getStr("merOrderNo"); + String drawJnlRemote = paramsJSON.getStr("drawJnl"); - log.info("[拉卡拉D1提现结果通知] 提现通知参数: drawState={}, mercId={}, merOrderNo={}", drawState, mercId, merOrderNo); + log.info("[拉卡拉D1提现结果通知] 提现通知参数: drawState={}, mercId={}, merOrderNo={},drawJnlRemote={}", drawState, mercId, merOrderNo, drawJnlRemote); - if (StrUtil.hasBlank(mercId, merOrderNo, drawState)) { - log.warn("[拉卡拉提现结果通知] 回调参数缺失: drawState={}, mercId={}, merOrderNo={}", drawState, mercId, merOrderNo); + if (StrUtil.hasBlank(mercId, merOrderNo, drawJnl, drawState)) { + log.warn("[拉卡拉提现结果通知] 回调参数缺失: drawState={}, mercId={}, merOrderNo={}, drawJnlRemote={}", drawState, mercId, merOrderNo, drawJnlRemote); return JSONUtil.createObj() .set("code", "FAIL") .set("message", "回调参数错误"); } + Boolean isCompensate = lklOrderDrawService.isOrderDrawed(mercId, drawJnl); + if (isCompensate) { + log.warn("[拉卡拉D1提现结果通知] 提现结果通知已处理,忽略处理: mercId={}, merOrderNo={}, drawJnl={}", mercId, merOrderNo, drawJnl); + return JSONUtil.createObj() + .set("code", "SUCCESS") + .set("message", "提现结果通知已处理"); + } + // 只处理成功的提现状态 if (!"DRAW.SUCCESS".equals(drawState)) { log.debug("[拉卡拉D1提现结果通知] 提现状态未成功,忽略处理: drawState={}", drawState); diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java index d676e913..a1a1606a 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderDrawServiceImpl.java @@ -2,6 +2,7 @@ package com.suisung.mall.shop.lakala.service.impl; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.suisung.mall.common.modules.lakala.LklOrderDraw; import com.suisung.mall.core.web.service.impl.BaseServiceImpl; import com.suisung.mall.shop.lakala.mapper.LklOrderDrawMapper; @@ -9,6 +10,10 @@ import com.suisung.mall.shop.lakala.service.LklOrderDrawService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import java.util.Collections; +import java.util.Date; +import java.util.List; + @Slf4j @Service public class LklOrderDrawServiceImpl extends BaseServiceImpl implements LklOrderDrawService { @@ -103,6 +108,70 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl getUnSuccessDrawedList(Date beginDate, Date endDate, Integer page, Integer pageSize) { + // 1. 参数校验 + if (beginDate == null) { + log.warn("[分页查询未成功提现记录] 开始时间不能为空"); + return Collections.emptyList(); + } + + if (page == null || page < 1) { + log.warn("[分页查询未成功提现记录] 页码必须大于0,当前页码: {}", page); + return Collections.emptyList(); + } + + if (pageSize == null || pageSize <= 0) { + log.warn("[分页查询未成功提现记录] 页面大小必须大于0,当前大小: {}", pageSize); + return Collections.emptyList(); + } + + // 2. 处理结束时间默认值 + Date actualEndDate = (endDate != null) ? endDate : new Date(); + + // 3. 参数合理性校验 + if (beginDate.after(actualEndDate)) { + log.warn("[分页查询未成功提现记录] 开始时间{}不能晚于结束时间{}", beginDate, actualEndDate); + return Collections.emptyList(); + } + + try { + // 4. 构造查询条件 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.select("id", "merc_id", "draw_jnl", "draw_state", "created_at") + .isNotNull("draw_jnl") + .isNotNull("merc_id") + .ne("draw_state", "DRAW.SUCCESS") + .ge("created_at", beginDate) + .le("created_at", actualEndDate) + .orderByDesc("id"); + + // 5. 执行分页查询 + Page resultPage = lists(queryWrapper, page, pageSize); + List result = resultPage.getRecords(); + + log.info("[分页查询未成功提现记录] 查询完成,开始时间={},结束时间={},页码={},页面大小={},结果数量={}", + beginDate, actualEndDate, page, pageSize, (result != null ? result.size() : 0)); + + return result != null ? result : Collections.emptyList(); + } catch (Exception e) { + log.error("[分页查询未成功提现记录] 查询过程中发生异常,开始时间={},结束时间={},页码={},页面大小={}", + beginDate, endDate, page, pageSize, e); + return Collections.emptyList(); + } + } + + /** * 判断订单是否已经提现完成 * @@ -121,7 +190,7 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl() .eq("order_id", orderId) - .eq("draw_state", "DRAW.SUCCESS") +// .eq("draw_state", "DRAW.SUCCESS") .orderByDesc("id")); // 根据查询结果判断是否已提现 @@ -137,4 +206,42 @@ public class LklOrderDrawServiceImpl extends BaseServiceImpl() + .eq("merc_id", merchantNo) + .eq("draw_jnl", drawJnl) + .eq("draw_state", "DRAW.SUCCESS") + .orderByDesc("id")); + + boolean isDrawed = lklOrderDraw != null; + + log.debug("[LklOrderDraw] 订单提现状态查询完成, merchantNo={}, drawJnl={}, isDrawed={}", + merchantNo, drawJnl, isDrawed); + + return isDrawed; + } catch (Exception e) { + log.error("[LklOrderDraw] 查询订单提现状态异常, merchantNo={}, drawJnl={}", merchantNo, drawJnl, e); + return false; + } + } + }