增加 确认收货 异步通知接收地址,优化 拉卡拉预支付 日志

This commit is contained in:
Jack 2025-09-09 23:51:37 +08:00
parent d55dd93b05
commit 846cc94c0d
13 changed files with 949 additions and 203 deletions

View File

@ -60,22 +60,32 @@ public class ShopOrderLkl implements Serializable {
private String lkl_log_date;
private String lkl_split_log_no;
private String lkl_sub_log_no;
private String lkl_trade_no;
private String lkl_receive_trade_no;
private String lkl_receive_log_no;
private String lkl_merchant_no;
private String lkl_term_no;
private String notify_url;
private String receive_notify_url;
private String lkl_req;
private String lkl_resp;
private String lkl_notify_resp;
private String lkl_receive_notify_resp;
private Integer receive_status;
private Integer status;
private Date created_at;

View File

@ -182,7 +182,7 @@ public class UniCloudPushServiceImpl implements UniCloudPushService {
try {
JSONObject json = JSONUtil.parseObj(body);
log.info("[推送服务] 原始响应: {}", json.toStringPretty());
// log.debug("[推送服务] 原始响应: {}", json.toStringPretty());
// 解析标准响应字段
Integer errCode = json.getInt("errCode", -1);

View File

@ -86,6 +86,7 @@ secure:
- "/shop/sf-express/cancel-order/notify"
- "/shop/sf-express/rider-order-status/notify"
- "/shop/sf-express/order-complete/notify"
- "/mobile/shop/lakala/trans/receive/completeNotify"
- "/shop/sync/third/**"
- "/esProduct/**"
- "/admin/oss/upload/**"

View File

@ -24,44 +24,10 @@ import java.io.IOException;
@RestController
@RequestMapping("/mobile/pay/lakala")
public class LakalaController extends BaseControllerImpl {
//
// @Resource
// private LakalaPayService lakalaPayService;
@ApiOperation(value = "本地文件转base64", notes = "本地文件转base64")
@RequestMapping(value = "/file2base64", method = RequestMethod.POST)
public String file2Base64(@RequestParam("file") MultipartFile file) throws IOException {
String str = Base64Utils.encodeToString(file.getBytes());
return str;
}
// @ApiOperation(value = "商户分账业务开通申请", notes = "商户分账业务开通申请")
// @RequestMapping(value = "/ledger/applyLedgerMer", method = RequestMethod.POST)
// public CommonResult ledgerApplyLedgerMer(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerMer(paramsJSON);
// }
//
// @ApiOperation(value = "商户分账业务开通申请异步回调回调", notes = "商户分账业务开通申请异步回调回调")
// @RequestMapping(value = "/ledger/applyLedgerMerNotify", method = RequestMethod.POST)
// public JSONObject ledgerApplyLedgerMerNotify(HttpServletRequest request) {
// return lakalaPayService.applyLedgerMerNotify(request);
// }
//
// @ApiOperation(value = "分账接收方创建申请", notes = "分账接收方创建申请")
// @RequestMapping(value = "/ledger/applyLedgerReceiver", method = RequestMethod.POST)
// public CommonResult applyLedgerReceiver(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerReceiver(paramsJSON);
// }
//
// @ApiOperation(value = "分账关系绑定申请", notes = "分账关系绑定申请")
// @RequestMapping(value = "/ledger/applyBind", method = RequestMethod.POST)
// public CommonResult applyBind(@RequestBody JSONObject paramsJSON) {
// return lakalaPayService.applyLedgerMerReceiverBind(paramsJSON);
// }
//
// @ApiOperation(value = "分账关系绑定申请异步回调通知", notes = "分账关系绑定申请异步回调通知")
// @RequestMapping(value = "/ledger/applyBindNotify", method = RequestMethod.POST)
// public JSONObject applyBindNotify(HttpServletRequest request) {
// return lakalaPayService.applyLedgerMerReceiverBindNotify(request);
// }
}

View File

@ -111,7 +111,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
/**
* 拉卡拉预下单
* 拉卡拉预下单(废弃)
* 参考https://o.lakala.com/#/home/document/detail?id=110
*
* @param merchantNo 商户号
@ -127,34 +127,38 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param remark 备注
* @return
*/
@Override
public JSONObject lklTransPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) {
// @Override
public JSONObject lklTransPreOrderBak(String merchantNo, String termNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) {
// 1. 配置初始化
initLKLSDK();
if (StrUtil.isBlank(merchantNo) || StrUtil.isBlank(termNo)) {
log.warn("[拉卡拉预下单] 参数校验失败:缺少商户号或终端号, merchantNo={}, termNo={}", merchantNo, termNo);
throw new ApiException(I18nUtil._("缺少商户号或终端号!"));
}
log.info("[拉卡拉预下单] 开始处理请求, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId);
//2. 装配数据
/*** 微信主扫场景示例 */
V3LabsTransPreorderRequest v3LabsTransPreorderWechatReq = new V3LabsTransPreorderRequest();
v3LabsTransPreorderWechatReq.setMerchantNo(merchantNo);
v3LabsTransPreorderWechatReq.setTermNo(termNo);
v3LabsTransPreorderWechatReq.setOutTradeNo(orderId);
v3LabsTransPreorderWechatReq.setSubject(subject);
V3LabsTransPreorderRequest v3LabsTransPreorderRequest = new V3LabsTransPreorderRequest();
v3LabsTransPreorderRequest.setMerchantNo(merchantNo);
v3LabsTransPreorderRequest.setTermNo(termNo);
v3LabsTransPreorderRequest.setOutTradeNo(orderId);
v3LabsTransPreorderRequest.setSubject(subject);
//微信WECHAT 支付宝ALIPAY 银联UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户LKLACC 网联小钱包NUCSPAY 京东钱包JD
v3LabsTransPreorderWechatReq.setAccountType("WECHAT");
v3LabsTransPreorderRequest.setAccountType("WECHAT");
// 41:NATIVEALIPAY云闪付支持京东白条分期51:JSAPI微信公众号支付支付宝服务窗支付银联JS支付翼支付JS支付拉卡拉钱包支付71:微信小程序支付 61:APP支付微信APP支付
v3LabsTransPreorderWechatReq.setTransType("51");
v3LabsTransPreorderWechatReq.setTotalAmount(totalAmount); // 应支付金额单位
v3LabsTransPreorderWechatReq.setSettleType("1"); //0或者空常规结算方式如需接拉卡拉分账通需传1商户未开通分账之前切记不用上送此参数
v3LabsTransPreorderWechatReq.setNotifyUrl(notifyURL);
v3LabsTransPreorderWechatReq.setRemark(remark);
v3LabsTransPreorderRequest.setTransType("51");
v3LabsTransPreorderRequest.setTotalAmount(totalAmount); // 应支付金额单位
v3LabsTransPreorderRequest.setSettleType("1"); //0或者空常规结算方式如需接拉卡拉分账通需传1商户未开通分账之前切记不用上送此参数
v3LabsTransPreorderRequest.setNotifyUrl(notifyURL);
v3LabsTransPreorderRequest.setRemark(remark);
// v3LabsTransPreorderRequest.setCompleteNotifyUrl("https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify");
//地址位置信息
V3LabsTradeLocationInfo v3LabsTradePreorderLocationInfo = new V3LabsTradeLocationInfo(requestIP);
v3LabsTransPreorderWechatReq.setLocationInfo(v3LabsTradePreorderLocationInfo);
v3LabsTransPreorderRequest.setLocationInfo(v3LabsTradePreorderLocationInfo);
//微信主扫场景下 acc_busi_fields 域内容
V3LabsTradePreorderWechatBus wechatBus = new V3LabsTradePreorderWechatBus();
@ -162,35 +166,217 @@ public class LakalaPayServiceImpl implements LakalaPayService {
wechatBus.setUserId(openId); // 微信 openId
wechatBus.setDeviceInfo("WEB"); // 终端设备号(门店号或收银设备ID)注意PC网页或JSAPI支付请传WEB
// wechatBus.setAttach(storeId); // 附加数据商户自定义数据在查询交易结果时原样返回
v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus);
v3LabsTransPreorderRequest.setAccBusiFields(wechatBus);
JSONObject reqParams = JSONUtil.parseObj(v3LabsTransPreorderRequest);
reqParams.set("complete_notify_url", "https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify");
try {
log.info("拉卡拉预下单请求参数:{}", JSONUtil.toJsonStr(v3LabsTransPreorderWechatReq));
log.info("[拉卡拉预下单] 请求参数: {}", JSONUtil.toJsonStr(reqParams));
//3. 发送请求
String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq);
log.info("拉卡拉预下单响应数据:{}", responseStr);
String reqUrl = serverUrl + "/api/v3/labs/trans/preorder";
log.debug("[拉卡拉预下单] 请求URL: {}", reqUrl);
String responseStr = LKLSDK.httpPost(reqUrl, reqParams.toString(), appId);
log.info("[拉卡拉预下单] 响应数据: {}", responseStr);
if (StrUtil.isBlank(responseStr)) {
log.warn("[拉卡拉预下单] 响应为空, orderId={}", orderId);
return null;
}
JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
if (lakalaRespJSON != null && lakalaRespJSON.getStr("code").equals("BBS00000")) {
log.debug("[拉卡拉预下单] 支付成功,保存订单记录, orderId={}", orderId);
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", JSONUtil.parseObj(v3LabsTransPreorderWechatReq));
lklPayReqAndRespJson.put("req", JSONUtil.parseObj(v3LabsTransPreorderRequest));
lklPayReqAndRespJson.put("resp", lakalaRespJSON);
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
}
//4. 响应
log.info("[拉卡拉预下单] 处理完成, orderId={}, responseCode={}", orderId, lakalaRespJSON != null ? lakalaRespJSON.getStr("code") : "NULL");
return lakalaRespJSON;
} catch (SDKException e) {
log.error("拉卡拉支付出错:", e);
log.error("[拉卡拉预下单] SDK异常, orderId=" + orderId, e);
throw new ApiException(I18nUtil._("支付失败!"), e);
} catch (Exception e) {
log.error("[拉卡拉预下单] 系统异常, orderId=" + orderId, e);
throw new ApiException(I18nUtil._("系统异常!"), e);
}
}
/**
* 拉卡拉预下单
* 参考https://o.lakala.com/#/home/document/detail?id=110
*
* @param merchantNo 商户号
* @param termNo 终端号
* @param xcxAppId 小程序appid
* @param openId openid
* @param storeId 店铺号
* @param orderId 订单号
* @param subject 订单标题
* @param totalAmount 订单金额单位
* @param notifyURL 回调地址
* @param requestIP 请求IP
* @param remark 备注
* @return 拉卡拉预下单响应结果
*/
@Override
public JSONObject lklTransPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) {
// 1. 参数校验
if (StrUtil.isBlank(merchantNo)) {
log.warn("[拉卡拉预下单] 参数校验失败:商户号不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("商户号不能为空!"));
}
if (StrUtil.isBlank(termNo)) {
log.warn("[拉卡拉预下单] 参数校验失败:终端号不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("终端号不能为空!"));
}
if (StrUtil.isBlank(orderId)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单号不能为空, merchantNo={}", merchantNo);
throw new ApiException(I18nUtil._("订单号不能为空!"));
}
if (StrUtil.isBlank(subject)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单标题不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("订单标题不能为空!"));
}
if (StrUtil.isBlank(totalAmount)) {
log.warn("[拉卡拉预下单] 参数校验失败:订单金额不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("订单金额不能为空!"));
}
if (StrUtil.isBlank(notifyURL)) {
log.warn("[拉卡拉预下单] 参数校验失败:回调地址不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("回调地址不能为空!"));
}
if (StrUtil.isBlank(requestIP)) {
log.warn("[拉卡拉预下单] 参数校验失败请求IP不能为空, orderId={}", orderId);
throw new ApiException(I18nUtil._("请求IP不能为空"));
}
log.info("[拉卡拉预下单] 开始处理请求, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId);
try {
// 2. 装配请求数据
JSONObject reqData = new JSONObject();
reqData.put("merchant_no", merchantNo);
reqData.put("term_no", termNo);
reqData.put("out_trade_no", orderId);
reqData.put("subject", subject);
// 微信WECHAT 支付宝ALIPAY 银联UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户LKLACC 网联小钱包NUCSPAY 京东钱包JD
reqData.put("account_type", "WECHAT");
// 41:NATIVEALIPAY云闪付支持京东白条分期51:JSAPI微信公众号支付支付宝服务窗支付银联JS支付翼支付JS支付拉卡拉钱包支付71:微信小程序支付 61:APP支付微信APP支付
reqData.put("trans_type", "51");
reqData.put("total_amount", totalAmount); // 应支付金额单位
reqData.put("settle_type", "1"); // "0"或者空常规结算方式如需接拉卡拉分账通需传"1"商户未开通分账之前切记不用上送此参数
reqData.put("notify_url", notifyURL);
reqData.put("remark", remark);
reqData.put("complete_notify_url", "https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify");
// 地址位置信息
JSONObject locationInfo = new JSONObject();
locationInfo.put("request_ip", requestIP);
reqData.put("location_info", locationInfo);
// 微信业务参数
if (StrUtil.isNotBlank(xcxAppId) && StrUtil.isNotBlank(openId)) {
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appId
accBusiFields.put("user_id", openId); // 微信 openId
accBusiFields.put("device_info", "WEB"); // 终端设备号(门店号或收银设备ID)注意PC网页或JSAPI支付请传"WEB"
reqData.put("acc_busi_fields", accBusiFields);
log.debug("[拉卡拉预下单] 已添加微信业务参数, xcxAppId={}, openId={}", xcxAppId, openId);
} else {
log.warn("[拉卡拉预下单] 微信业务参数不完整或不需要, xcxAppId={}, openId={}", xcxAppId, openId);
}
// 3. 构造请求体
JSONObject reqBody = new JSONObject();
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
reqBody.put("version", "3.0");
reqBody.put("req_data", reqData);
log.info("[拉卡拉预下单] 请求参数组装完成, orderId={}", orderId);
log.debug("[拉卡拉预下单] 完整请求参数: {}", JSONUtil.toJsonStr(reqBody));
// 4. 发送请求
String reqUrl = serverUrl + "/api/v3/labs/trans/preorder";
log.info("[拉卡拉预下单] 准备发送请求, orderId={}, url={}", orderId, reqUrl);
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
if (StrUtil.isBlank(authorization)) {
log.error("[拉卡拉预下单] 生成签名失败, orderId={}", orderId);
throw new ApiException("生成请求签名失败!");
}
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
header.put("Content-Type", "application/json");
log.debug("[拉卡拉预下单] 请求头信息生成完成, orderId={}", orderId);
// 使用RestTemplate发送POST请求
ResponseEntity<JSONObject> lakalaRespEntity = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("[拉卡拉预下单] 收到响应, orderId={}, responseStatus={}", orderId, lakalaRespEntity != null ? lakalaRespEntity.getStatusCode() : "NULL");
// 5. 处理响应结果
if (lakalaRespEntity == null) {
log.warn("[拉卡拉预下单] 响应为空, orderId={}", orderId);
return null;
}
JSONObject respBody = lakalaRespEntity.getBody();
log.debug("[拉卡拉预下单] 响应体内容, orderId={}, responseBody={}", orderId, respBody);
if (respBody == null) {
log.warn("[拉卡拉预下单] 响应体为空, orderId={}", orderId);
return null;
}
String responseCode = respBody.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.warn("[拉卡拉预下单] 响应码为空, orderId={}", orderId);
return respBody;
}
// 使用安全的字符串比较方式避免空指针异常
if (!lklPaySuccessCode.equals(responseCode)) {
log.warn("[拉卡拉预下单] 响应码异常, orderId={}, code={}, msg={}", orderId, responseCode, respBody.getStr("msg"));
return respBody;
}
log.info("[拉卡拉预下单] 支付成功,准备保存订单记录, orderId={}", orderId);
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", reqData);
lklPayReqAndRespJson.put("resp", respBody);
try {
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
log.debug("[拉卡拉预下单] 订单记录保存成功, orderId={}", orderId);
} catch (Exception e) {
log.error("[拉卡拉预下单] 保存订单记录失败, orderId={}", orderId, e);
// 不中断主流程仅记录错误
}
// 6. 返回响应结果
log.info("[拉卡拉预下单] 处理完成, orderId={}, responseCode={}", orderId, responseCode);
return respBody;
} catch (Exception e) {
log.error("[拉卡拉预下单] 系统异常, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId, e);
throw new ApiException("拉卡拉预下单出错:" + e.getMessage(), e);
}
}
/**
* 拉卡拉合单预下单主要有运费的订单使用合单
* 参考https://o.lakala.com/#/home/document/detail?id=208
@ -204,41 +390,64 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param storeId 店铺Id
* @param orderId 订单号
* @param subject 订单标题
* @param totalAmount 订单总金额
* @param agentAmount 代理商收取金额
* @param totalAmount 订单总金额单位
* @param agentAmount 代理商收取金额单位
* @param notifyURL 回调地址
* @param requestIP 请求ip
* @param requestIP 请求IP
* @param remark 备注
* @return 拉卡拉合单预下单响应结果
**/
@Override
public JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String agentTermNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark) {
// 2. 参数校验
log.info("[拉卡拉合单预下单] 开始处理请求, merchantNo={}, termNo={}, agentMerchantNo={}, agentTermNo={}, orderId={}",
merchantNo, termNo, agentMerchantNo, agentTermNo, orderId);
// 1. 参数校验
if (StrUtil.isBlank(merchantNo)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:商家商户号不能为空, orderId={}", orderId);
throw new ApiException("商家商户号不能为空!");
}
if (StrUtil.isBlank(termNo)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:终端号不能为空, orderId={}", orderId);
throw new ApiException("终端号不能为空!");
}
// 3. 校验其他必要参数
if (StrUtil.isBlank(orderId)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单号不能为空, merchantNo={}, termNo={}", merchantNo, termNo);
throw new ApiException("订单号不能为空!");
}
if (StrUtil.isBlank(subject)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单标题不能为空, orderId={}", orderId);
throw new ApiException("订单标题不能为空!");
}
if (StrUtil.isBlank(totalAmount)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:订单总金额不能为空, orderId={}", orderId);
throw new ApiException("订单总金额不能为空!");
}
if (StrUtil.isBlank(notifyURL)) {
log.warn("[拉卡拉合单预下单] 参数校验失败:回调地址不能为空, orderId={}", orderId);
throw new ApiException("回调地址不能为空!");
}
if (StrUtil.isBlank(requestIP)) {
log.warn("[拉卡拉合单预下单] 参数校验失败请求IP不能为空, orderId={}", orderId);
throw new ApiException("请求IP不能为空");
}
// 如果不符合合单交易条件则走聚合主扫单笔交易
if (StrUtil.isBlank(agentMerchantNo) || StrUtil.isBlank(agentTermNo) || StrUtil.isBlank(agentAmount) || "0".equals(agentAmount)) {
log.info("不符合合单交易条件,转为单笔交易处理。商家商户号:{},代理商商户号:{},代理商终端号:{},代理商金额:{}",
log.info("[拉卡拉合单预下单] 不符合合单交易条件,转为单笔交易处理。商家商户号:{},代理商商户号:{},代理商终端号:{},代理商金额:{}",
merchantNo, agentMerchantNo, agentTermNo, agentAmount);
return lklTransPreOrder(merchantNo, termNo, xcxAppId, openId, storeId, orderId, subject, totalAmount, notifyURL, requestIP, remark);
}
try {
// 4. 装配请求数据
log.debug("[拉卡拉合单预下单] 开始装配请求数据, orderId={}", orderId);
// 2. 装配请求数据
JSONObject reqData = new JSONObject();
// 基本交易信息
@ -251,6 +460,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
reqData.put("subject", subject); // 订单标题
reqData.put("notify_url", notifyURL); // 异步通知地址
reqData.put("remark", remark); // 备注
reqData.put("complete_notify_url", "https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify"); // 发货类小程序确认收获后通知商户的地址
// 位置信息
JSONObject locationInfo = new JSONObject();
@ -258,10 +468,15 @@ public class LakalaPayServiceImpl implements LakalaPayService {
reqData.put("location_info", locationInfo);
// 微信业务参数
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appid
accBusiFields.put("user_id", openId); // 用户openid
reqData.put("acc_busi_fields", accBusiFields);
if (StrUtil.isNotBlank(xcxAppId) && StrUtil.isNotBlank(openId)) {
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appId
accBusiFields.put("user_id", openId); // 用户openid
reqData.put("acc_busi_fields", accBusiFields);
log.debug("[拉卡拉合单预下单] 已添加微信业务参数, xcxAppId={}, openId={}", xcxAppId, openId);
} else {
log.warn("[拉卡拉合单预下单] 微信业务参数不完整或不需要, xcxAppId={}, openId={}", xcxAppId, openId);
}
// 重要约定订单号规则商品订单ORD_订单号,运费订单DF_订单号
// 分单信息
@ -269,8 +484,20 @@ public class LakalaPayServiceImpl implements LakalaPayService {
goodsSplitInfo.put("out_sub_trade_no", CommonConstant.Sep_GoodsFee_Prefix + orderId); // 商品子订单号
goodsSplitInfo.put("merchant_no", merchantNo); // 分账商户号
goodsSplitInfo.put("term_no", termNo); // 分账终端号
int totalAmountInt = Convert.toInt(totalAmount) - Convert.toInt(agentAmount);
goodsSplitInfo.put("amount", Convert.toStr(totalAmountInt)); // 分账金额
// 安全转换金额避免类型转换异常
int totalAmountInt = 0;
int agentAmountInt = 0;
try {
totalAmountInt = Convert.toInt(totalAmount);
agentAmountInt = Convert.toInt(agentAmount);
} catch (NumberFormatException e) {
log.error("[拉卡拉合单预下单] 金额转换异常, totalAmount={}, agentAmount={}, orderId={}", totalAmount, agentAmount, orderId, e);
throw new ApiException("金额格式错误!");
}
int goodsAmountInt = totalAmountInt - agentAmountInt;
goodsSplitInfo.put("amount", Convert.toStr(goodsAmountInt)); // 分账金额
goodsSplitInfo.put("settle_type", "0"); // "0"或者空常规结算方式
goodsSplitInfo.put("sub_remark", "商品订单金额"); // 子单备注信息
@ -287,50 +514,87 @@ public class LakalaPayServiceImpl implements LakalaPayService {
outSplitInfo.add(goodsSplitInfo);
reqData.put("out_split_info", outSplitInfo);
// 5. 构造请求体
log.info("[拉卡拉合单预下单] 分单信息组装完成, orderId={}, goodsAmount={}分, deliveryAmount={}分",
orderId, goodsAmountInt, agentAmountInt);
// 3. 构造请求体
JSONObject reqBody = new JSONObject();
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
reqBody.put("version", "3.0");
reqBody.put("req_data", reqData);
// 6. 发送请求
log.info("[拉卡拉合单预下单] 请求参数组装完成, orderId={}", orderId);
log.debug("[拉卡拉合单预下单] 完整请求参数: {}", JSONUtil.toJsonStr(reqBody));
// 4. 发送请求
String reqUrl = serverUrl + "/api/v3/labs/trans/merge/preorder";
log.info("拉卡拉合单预下单请求参数:{}", reqBody);
log.info("[拉卡拉合单预下单] 准备发送请求, orderId={}, url={}", orderId, reqUrl);
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
if (StrUtil.isBlank(authorization)) {
log.error("[拉卡拉合单预下单] 生成签名失败, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "生成请求签名失败").set("resp_data", null);
}
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
header.put("Content-Type", "application/json");
// 这里的请求方法对返回的字段进行了处理需要注意
ResponseEntity<JSONObject> lakalaRespJSON = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("拉卡拉合单交易响应参数:{}", lakalaRespJSON);
log.debug("[拉卡拉合单预下单] 请求头信息生成完成, orderId={}", orderId);
// 7. 处理响应结果
if (lakalaRespJSON == null) {
// 发送请求
ResponseEntity<JSONObject> lakalaRespEntity = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class);
log.info("[拉卡拉合单预下单] 收到响应, orderId={}, responseStatus={}", orderId, lakalaRespEntity != null ? lakalaRespEntity.getStatusCode() : "NULL");
// 5. 处理响应结果
if (lakalaRespEntity == null) {
log.warn("[拉卡拉合单预下单] 响应为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易无响应值").set("resp_data", null);
}
JSONObject respBody = lakalaRespJSON.getBody();
JSONObject respBody = lakalaRespEntity.getBody();
log.debug("[拉卡拉合单预下单] 响应体内容, orderId={}, responseBody={}", orderId, respBody);
// 7. 处理响应结果
if (!lklPaySuccessCode.equals(respBody.getStr("code")) || respBody == null) {
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易无响应值").set("resp_data", null);
// 6. 处理响应结果
if (respBody == null) {
log.warn("[拉卡拉合单预下单] 响应体为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易响应体为空").set("resp_data", null);
}
String responseCode = respBody.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.warn("[拉卡拉合单预下单] 响应码为空, orderId={}", orderId);
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易响应码为空").set("resp_data", null);
}
// 使用安全的字符串比较方式避免空指针异常
if (!lklPaySuccessCode.equals(responseCode)) {
log.warn("[拉卡拉合单预下单] 响应码异常, orderId={}, code={}, msg={}", orderId, responseCode, respBody.getStr("msg"));
return new JSONObject().set("code", "BBS00001").set("msg", "拉卡拉合单交易失败:" + respBody.getStr("msg")).set("resp_data", null);
}
log.info("[拉卡拉合单预下单] 支付成功,准备保存订单记录, orderId={}", orderId);
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", reqData);
lklPayReqAndRespJson.put("resp", respBody); // 返回原始响应数据
// 新增 shopOrderLkl 记录
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
try {
// 新增 shopOrderLkl 记录
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
log.debug("[拉卡拉合单预下单] 订单记录保存成功, orderId={}", orderId);
} catch (Exception e) {
log.error("[拉卡拉合单预下单] 保存订单记录失败, orderId={}", orderId, e);
// 不中断主流程仅记录错误
}
// 8. 返回响应结果
// 7. 返回响应结果
log.info("[拉卡拉合单预下单] 处理完成, orderId={}, responseCode={}", orderId, responseCode);
return respBody;
} catch (Exception e) {
log.error("拉卡拉合单交易出错,订单号:{},错误信息:", orderId, e);
throw new ApiException("拉卡拉合单交易出错" + e.getMessage(), e);
log.error("[拉卡拉合单预下单] 系统异常, merchantNo={}, termNo={}, orderId={}", merchantNo, termNo, orderId, e);
return new JSONObject().set("code", "BBS00001").set("msg", "系统异常" + e.getMessage()).set("resp_data", null);
}
}
@ -340,7 +604,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* 参考地址https://o.lakala.com/#/home/document/detail?id=113
*
* @param storeId 店铺ID
* @param outTradeNo 退货订单号 FX-20241214-1
* @param outTradeNo 外部交易订单号
* @param originTradeNo 原拉卡拉交易流水号
* @param refundAmount 退款金额单位
* @param refundReason 退款原因
@ -351,33 +615,58 @@ public class LakalaPayServiceImpl implements LakalaPayService {
@Override
public Pair<Boolean, String> innerLklRefund(Integer storeId, String outTradeNo, String originTradeNo, String refundAmount, String refundReason, String lklMerchantNo, String lklTermNo) {
try {
log.info("开始执行拉卡拉内部退款,参数: storeId={}, outTradeNo={}, originTradeNo={}, refundAmount={}, refundReason={}",
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退款失败");
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 (ObjectUtil.isEmpty(storeId)) {
log.warn("[拉卡拉退款] 店铺ID不能为空: storeId={}", storeId);
return Pair.of(false, I18nUtil._("店铺ID不能为空,退款失败!"));
}
if (StrUtil.isBlank(outTradeNo)) {
log.warn("[拉卡拉退款] 外部交易订单号不能为空: outTradeNo={}", outTradeNo);
return Pair.of(false, I18nUtil._("外部交易订单号不能为空,退款失败!"));
}
if (StrUtil.isBlank(originTradeNo)) {
log.warn("[拉卡拉退款] 原拉卡拉交易流水号不能为空: originTradeNo={}", originTradeNo);
return Pair.of(false, I18nUtil._("原拉卡拉交易流水号不能为空,退款失败!"));
}
if (StrUtil.isBlank(refundAmount)) {
log.warn("[拉卡拉退款] 退款金额不能为空: refundAmount={}", refundAmount);
return Pair.of(false, I18nUtil._("退款金额不能为空,退款失败!"));
}
// 校验退款金额格式
if (!refundAmount.matches("\\d+") || Integer.parseInt(refundAmount) <= 0) {
log.warn("退款金额不合法: refundAmount={}", refundAmount);
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);
if (shopStoreBase == null) {
log.error("[拉卡拉退款] 无法获取店铺信息: storeId={}", storeId);
return Pair.of(false, I18nUtil._("无法获取店铺信息,退款失败!"));
}
if (StrUtil.isBlank(shopStoreBase.getLkl_merchant_no()) || StrUtil.isBlank(shopStoreBase.getLkl_term_no())) {
log.error("[拉卡拉退款] 无法获取店铺的拉卡拉商户号或终端号: storeId={}, merchantNo={}, termNo={}",
storeId, shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no());
return Pair.of(false, I18nUtil._("缺少商户号参数,退款失败!"));
}
@ -396,37 +685,44 @@ public class LakalaPayServiceImpl implements LakalaPayService {
refundRequest.setLocationInfo(new V3LabsTradeLocationInfo(requestIp, null, ""));
log.info("拉卡拉退款请求参数: {}", JSONUtil.toJsonStr(refundRequest));
log.info("[拉卡拉退款] 请求参数: {}", JSONUtil.toJsonStr(refundRequest));
String responseString = LKLSDK.httpPost(refundRequest);
// 6. 处理响应
if (StrUtil.isBlank(responseString)) {
log.error("拉卡拉退款接口无响应");
log.error("[拉卡拉退款] 拉卡拉退款接口无响应");
return Pair.of(false, I18nUtil._("服务端无返回值,退款失败!"));
}
log.info("拉卡拉退款接口响应: {}", responseString);
log.info("[拉卡拉退款] 拉卡拉退款接口响应: {}", responseString);
JSONObject lakalaResponseJson = JSONUtil.parseObj(responseString);
if (lakalaResponseJson == null) {
log.error("拉卡拉退款接口返回值解析失败: responseString={}", responseString);
log.error("[拉卡拉退款] 拉卡拉退款接口返回值解析失败: responseString={}", responseString);
return Pair.of(false, I18nUtil._("返回值解析失败,退款失败!"));
}
if (!"BBS00000".equals(lakalaResponseJson.getStr("code"))) {
String responseCode = lakalaResponseJson.getStr("code");
if (StrUtil.isBlank(responseCode)) {
log.error("[拉卡拉退款] 拉卡拉退款响应码为空: response={}", responseString);
return Pair.of(false, I18nUtil._("返回值格式错误,退款失败!"));
}
if (!"BBS00000".equals(responseCode)) {
String errorMessage = lakalaResponseJson.getStr("msg", "未知错误");
log.error("拉卡拉退款失败, 错误信息: {}", errorMessage);
log.error("[拉卡拉退款] 拉卡拉退款失败, 错误信息: {}, 响应码: {}", errorMessage, responseCode);
return Pair.of(false, I18nUtil._(errorMessage));
}
JSONObject responseData = lakalaResponseJson.getJSONObject("resp_data");
log.info("拉卡拉退款成功: outTradeNo={}", outTradeNo);
log.info("[拉卡拉退款] 拉卡拉退款成功: outTradeNo={}", outTradeNo);
return Pair.of(true, responseData == null ? "" : responseData.toString());
} catch (SDKException e) {
log.error("拉卡拉退款SDK异常: ", e);
log.error("[拉卡拉退款] 拉卡拉退款SDK异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款SDK异常退款失败") + e.getMessage());
} catch (Exception e) {
log.error("拉卡拉退款发生未知异常: ", e);
log.error("[拉卡拉退款] 拉卡拉退款发生未知异常: ", e);
return Pair.of(false, I18nUtil._("拉卡拉退款发生未知异常,退款失败!") + e.getMessage());
}
}

View File

@ -1434,7 +1434,7 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
payConsumeDeposit.setUser_id(userId);
lklNotifyRespJSON.set("out_separate_no", orderId);// 默认非合单主单订单号
lklNotifyRespJSON.set("lkl_split_log_no", lklNotifyRespJSON.getStr("log_no")); // 默认非合单主单的流水号
lklNotifyRespJSON.set("lkl_sub_log_no", lklNotifyRespJSON.getStr("log_no")); // 默认非合单主单的流水号
lklNotifyRespJSON.set("split_amt", lklNotifyRespJSON.getStr("total_amount")); // 默认非合单主单支付金额
// 拉卡拉订单合单信息
if (StrUtil.isNotBlank(outSplitRspInfos)) {
@ -1453,7 +1453,7 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
cn.hutool.json.JSONObject goodsOrderInfo = payConsumeTradeService.getLklCombineSplitRespInfo(outSplitRspInfos, false);
if (goodsOrderInfo != null) {
lklNotifyRespJSON.set("out_separate_no", goodsOrderInfo.getStr("out_sub_trade_no"));// 合单子订单号
lklNotifyRespJSON.set("lkl_split_log_no", lklNotifyRespJSON.getStr("sub_log_no")); // 合单子商品订单的流水号
lklNotifyRespJSON.set("lkl_sub_log_no", lklNotifyRespJSON.getStr("sub_log_no")); // 合单子商品订单的流水号
lklNotifyRespJSON.set("split_amt", lklNotifyRespJSON.getStr("amount")); // 合单子商品订单支付金额
}
}

View File

@ -131,6 +131,18 @@ public class LakalaController extends BaseControllerImpl {
return lakalaPayService.getBankCardBin(paramsJSON.getStr("bankCardNo"));
}
@ApiOperation(value = "发货类交易确认收货通知", notes = "发货类交易确认收货通知 https://o.lakala.com/#/home/document/detail?id=1003")
@RequestMapping(value = "/trans/receive/completeNotify", method = RequestMethod.POST)
public ResponseEntity<JSONObject> receiveCompleteNotify(HttpServletRequest request) {
// 完整地址 https://mall.gpxscs.cn/api/mobile/shop/lakala/trans/receive/completeNotify
JSONObject resp = lakalaPayService.receiveCompleteNotify(request);
if (resp != null && "SUCCESS".equals(resp.get("code"))) {
return ResponseEntity.ok(resp);
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(resp);
}
@ApiOperation(value = "商户入网电子合同申请回调通知", notes = "商户入网电子合同申请回调通知")
@RequestMapping(value = "/ec/applyNotify", method = RequestMethod.POST)
public ResponseEntity<JSONObject> ecApplyNotify(HttpServletRequest request) {

View File

@ -95,6 +95,19 @@ public interface LakalaApiService {
*/
Pair<Boolean, String> innerApplyLedgerMer(String merCupNo);
/**
* 发货类交易确认收货通知
* 参考https://o.lakala.com/#/home/document/detail?id=1003
* 注意
* 1交易通知接口是交易成功完成后会向 complete_notify_url 这个地址主扫交易或者主扫合单请求中的complete_notify_url字段发起交易结果通知拉卡拉系统通知时如果商户的应答没有按照以下响应参考报文示例返回成功状态时则系统认为通知失败系统会通过一定的策略定期重新发起通知
* 2同样的通知可能会多次发送给商户系统商户系统必须能够正确处理重复的通知
* 3在没有收到拉卡拉支付交易通知的情况下建议商户主动调用06查询交易确认交易状态
*
* @param request
* @return
*/
JSONObject receiveCompleteNotify(HttpServletRequest request);
/**
* 商户入网电子合同申请回调通知
* 参考https://o.lakala.com/#/home/document/detail?id=289
@ -172,6 +185,29 @@ public interface LakalaApiService {
*/
Pair<Boolean, String> innerDoOrderSeparate(String orderId, String storeId);
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>
* 该方法用于处理拉卡拉订单的分账逻辑在用户确认收货后系统会根据订单信息执行分账操作
* 将订单金额按照预设比例分配给平台商家和代理商如果存在
* </p>
* <p>
* 分账操作流程
* 1. 参数校验和订单查询
* 2. 检查订单状态是否已确认收货
* 3. 检查是否已分账避免重复处理
* 4. 计算分账金额
* 5. 构建分账请求并发送至拉卡拉
* 6. 保存分账结果
* </p>
*
* @param lklMerchantNo 拉卡拉商户号
* @param receiveTradeNo 收货交易号对应拉卡拉的trade_no
* @param receiveLogNo 收货流水号对应拉卡拉的log_no
* @return Pair<Boolean, String> 处理结果对first为是否成功second为结果描述信息
*/
Pair<Boolean, String> innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo);
/**
* 分账结果通知
* 参考https://o.lakala.com/#/home/document/detail?id=393

View File

@ -675,6 +675,104 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
}
/**
* 发货类交易确认收货通知处理
* <p>
* 参考文档https://o.lakala.com/#/home/document/detail?id=1003
* </p>
* <p>
* 注意事项
* 1交易通知接口是交易成功完成后会向 complete_notify_url 这个地址主扫交易或者主扫合单请求中的complete_notify_url字段发起交易结果通知
* 拉卡拉系统通知时如果商户的应答没有按照以下"响应参考报文"示例返回成功状态时则系统认为通知失败系统会通过一定的策略定期重新发起通知
* 2同样的通知可能会多次发送给商户系统商户系统必须能够正确处理重复的通知
* 3在没有收到拉卡拉支付交易通知的情况下建议商户主动调用06查询交易确认交易状态
* </p>
*
* @param request HTTP请求对象包含拉卡拉确认收货通知的参数
* @return JSONObject 响应结果对象
*/
@Override
public JSONObject receiveCompleteNotify(HttpServletRequest request) {
log.info("[确认收货通知] 开始处理发货类交易确认收货通知");
// 验签
Pair<Boolean, String> checkResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath, false);
if (!checkResult.getFirst()) {
log.warn("[确认收货通知] 验签失败: {}", checkResult.getSecond());
return JSONUtil.createObj().set("code", "FAIL").set("message", checkResult.getSecond());
}
JSONObject paramsJSON = JSONUtil.parseObj(checkResult.getSecond());
if (paramsJSON == null) {
log.warn("[确认收货通知] 参数解析失败: 返回数据转换异常");
return JSONUtil.createObj().set("code", "FAIL").set("message", "返回数据转换异常!");
}
String logNo = paramsJSON.getStr("log_no");
String tradeState = paramsJSON.getStr("trade_state");
String merchantNo = paramsJSON.getStr("merchant_no");
String originTradeNo = paramsJSON.getStr("origin_trade_no");
String originLogNo = paramsJSON.getStr("origin_log_no");
log.info("[确认收货通知] 接收到通知参数: logNo={}, tradeState={}, merchantNo={}, originTradeNo={}, originLogNo={}",
logNo, tradeState, merchantNo, originTradeNo, originLogNo);
if (StrUtil.isBlank(tradeState) || !"SUCCESS".equals(tradeState)) {
log.warn("[确认收货通知] 交易状态未成功,不做任何处理: tradeState={}", tradeState);
return JSONUtil.createObj().set("code", "FAIL").set("message", "交易状态未成功,不做任何处理!");
}
if (StrUtil.isBlank(logNo) || StrUtil.isBlank(merchantNo) || StrUtil.isBlank(originTradeNo) || StrUtil.isBlank(originLogNo)) {
log.warn("[确认收货通知] 关键参数为空: logNo={}, merchantNo={}, originTradeNo={}, originLogNo={}",
logNo, merchantNo, originTradeNo, originLogNo);
return JSONUtil.createObj().set("code", "FAIL").set("message", "关键编号返回空值!");
}
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByMerchantNoAndTradeNoAndSubLogNo(merchantNo, originTradeNo, originLogNo);
if (shopOrderLkl == null) {
log.warn("[确认收货通知] 订单不存在: merchantNo={}, originTradeNo={}, originLogNo={}",
merchantNo, originTradeNo, originLogNo);
return JSONUtil.createObj().set("code", "FAIL").set("message", "订单不存在!");
}
try {
// 更新订单信息
shopOrderLkl.setLkl_receive_log_no(logNo);
shopOrderLkl.setLkl_receive_trade_no(paramsJSON.getStr("trade_no"));
shopOrderLkl.setLkl_receive_notify_resp(checkResult.getSecond());
shopOrderLkl.setReceive_status(CommonConstant.Enable);
log.debug("[确认收货通知] 准备更新订单信息: orderId={}", shopOrderLkl.getOrder_id());
Boolean updateResult = shopOrderLklService.addOrUpdateByStoreOrder(shopOrderLkl);
if (Boolean.FALSE.equals(updateResult)) {
log.error("[确认收货通知] 更新订单信息失败: orderId={}", shopOrderLkl.getOrder_id());
return JSONUtil.createObj().set("code", "FAIL").set("message", "更新订单信息失败!");
}
log.info("[确认收货通知] 订单信息更新成功: orderId={}", shopOrderLkl.getOrder_id());
// 发起分账指令
log.info("[确认收货通知] 开始发起分账指令: merchantNo={}, receiveTradeNo={}, logNo={}",
merchantNo, shopOrderLkl.getLkl_receive_trade_no(), logNo);
Pair<Boolean, String> separateResult = innerDoOrderSeparateByMerchantAndLogNo(merchantNo, shopOrderLkl.getLkl_receive_trade_no(), logNo);
if (!separateResult.getFirst()) {
log.error("[确认收货通知] 发起分账指令失败: orderId={}, reason={}", shopOrderLkl.getOrder_id(), separateResult.getSecond());
return JSONUtil.createObj().set("code", "FAIL").set("message", "发起分账指令失败:" + separateResult.getSecond());
}
log.info("[确认收货通知] 处理完成: orderId={}, log_no={}", shopOrderLkl.getOrder_id(), shopOrderLkl.getLkl_receive_log_no());
JSONObject respData = JSONUtil.createObj();
respData.set("code", "SUCCESS");
respData.set("message", "操作成功!");
return respData;
} catch (Exception e) {
log.error("[确认收货通知] 处理过程中发生异常: orderId=" + shopOrderLkl.getOrder_id(), e);
return JSONUtil.createObj().set("code", "FAIL").set("message", "系统处理异常:" + e.getMessage());
}
}
/**
* 商户入网电子合同申请回调通知
* 参考https://o.lakala.com/#/home/document/detail?id=289
@ -694,7 +792,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
String errMsg = "入网电子合同申请回调:";
JSONObject respData = new JSONObject();
respData.set("code", "FAIL").set("message", "处理失败");
respData.set("code", "FAIL").set("message", "返回数据转换异常");
JSONObject paramsJSON = JSONUtil.parseObj(checkResult.getSecond());
if (paramsJSON == null) {
@ -1601,7 +1699,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
public Pair<Boolean, String> innerDoOrderSeparate(String orderId, String storeId) {
// 1. 输入参数校验
if (StrUtil.isBlank(orderId)) {
log.warn("分账操作参数校验失败:订单号为空");
log.warn("[分账操作] 参数校验失败:订单号为空");
return Pair.of(false, "订单号不能为空");
}
@ -1609,10 +1707,10 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// TODO 检查可分账余额是否足够
// 2. 查询订单信息
log.info("开始执行订单[{}]分账操作", orderId);
log.info("[分账操作] 开始执行订单[{}]分账操作", orderId);
List<ShopOrderLkl> shopOrderLklList = shopOrderLklService.selectByOrderId(orderId, "", storeId);
if (CollectionUtil.isEmpty(shopOrderLklList)) {
log.warn("分账操作失败:订单[{}]不存在", orderId);
log.warn("[分账操作] 失败:订单[{}]不存在", orderId);
return Pair.of(false, "订单不存在");
}
@ -1624,21 +1722,26 @@ public class LakalaApiServiceImpl implements LakalaApiService {
initLKLSDK();
// 4. 遍历处理每个店铺订单的分账
log.info("订单[{}]包含{}个子订单,开始逐一处理", orderId, totalCount);
log.info("[分账操作] 订单[{}]包含{}个子订单,开始逐一处理", orderId, totalCount);
for (ShopOrderLkl shopOrderLkl : shopOrderLklList) {
log.debug("处理子订单storeId={}, splitLogNo={}", shopOrderLkl.getStore_id(), shopOrderLkl.getLkl_split_log_no());
log.debug("[分账操作] 处理子订单storeId={}, subLogNo={}, receive_log_no={}", shopOrderLkl.getStore_id(), shopOrderLkl.getLkl_sub_log_no(), shopOrderLkl.getLkl_receive_log_no());
if (!CommonConstant.Enable.equals(shopOrderLkl.getReceive_status()) || StrUtil.isBlank(shopOrderLkl.getLkl_receive_log_no())) {
log.warn("[分账操作] 订单[{}]对账流水号[{}]未被确认收货,跳过处理", orderId, shopOrderLkl.getLkl_receive_log_no());
continue;
}
// 5. 检查分账状态避免重复处理
LklOrderSeparate lklOrderSeparateExist = lklOrderSeparateService.getByOutTradeNo(shopOrderLkl.getLkl_split_log_no(), orderId);
if (lklOrderSeparateExist != null) {
String status = lklOrderSeparateExist.getStatus();
LklOrderSeparate existingSeparateRecord = lklOrderSeparateService.getByOutTradeNo(shopOrderLkl.getLkl_sub_log_no(), orderId);
if (existingSeparateRecord != null) {
String status = existingSeparateRecord.getStatus();
if ("SUCCESS".equals(status)) {
log.info("订单[{}]子订单[{}]已完成分账,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
log.info("[分账操作] 订单[{}]子订单[{}]已完成分账,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
successCount++;
continue;
}
if ("PROCESSING".equals(status) || "ACCEPTED".equals(status)) {
log.info("订单[{}]子订单[{}]分账处理中或已受理,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
log.info("[分账操作] 订单[{}]子订单[{}]分账处理中或已受理,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
successCount++;
continue;
}
@ -1653,21 +1756,21 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 7. 分账金额校验
if (splitAmount < 1) {
String errorMsg = String.format("店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账",
String errorMsg = String.format("[分账操作] 店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账",
shopOrderLkl.getStore_id(), orderId, splitAmount);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
if (lklOrderSeparateExist != null) {
lklOrderSeparateService.updateRemark(lklOrderSeparateExist.getId(), errorMsg);
if (existingSeparateRecord != null) {
lklOrderSeparateService.updateRemark(existingSeparateRecord.getId(), errorMsg);
}
continue;
}
// 获取分账平台接收方信息
LklLedgerMerReceiverBind platform = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo);
LklLedgerMerReceiverBind platformReceiver = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo);
if (platform == null) {
String errorMsg = String.format("店铺[%s]未绑定平台方接收账户,跳过分账", shopOrderLkl.getStore_id());
if (platformReceiver == null) {
String errorMsg = String.format("[分账操作] 店铺[%s]未绑定平台方接收账户,跳过分账", shopOrderLkl.getStore_id());
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
@ -1677,43 +1780,43 @@ public class LakalaApiServiceImpl implements LakalaApiService {
List<V3SacsSeparateRecvDatas> recvDatas = new ArrayList<>();
// 9. 获取商家分账比例并校验
BigDecimal splitRatioValMch = shopOrderLkl.getSplit_ratio(); // 94 代表94%
BigDecimal merchantSplitRatioRaw = shopOrderLkl.getSplit_ratio(); // 94 代表94%
// 判断商家分账比例是否有效必须在(0, 100]范围内
boolean canSplitForMch = splitRatioValMch != null
&& splitRatioValMch.compareTo(BigDecimal.ZERO) > 0
&& splitRatioValMch.compareTo(new BigDecimal(100)) <= 0;
if (!canSplitForMch) {
String errorMsg = String.format("店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账",
shopOrderLkl.getStore_id(), splitRatioValMch);
boolean canSplitForMerchant = merchantSplitRatioRaw != null
&& merchantSplitRatioRaw.compareTo(BigDecimal.ZERO) > 0
&& merchantSplitRatioRaw.compareTo(new BigDecimal(100)) <= 0;
if (!canSplitForMerchant) {
String errorMsg = String.format("[分账操作] 店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账",
shopOrderLkl.getStore_id(), merchantSplitRatioRaw);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
// 商家分账
BigDecimal mchRatio = splitRatioValMch.divide(new BigDecimal(100)); // 比如94%
BigDecimal merchantSplitRatio = merchantSplitRatioRaw.divide(new BigDecimal(100)); // 比如94%
BigDecimal distributorRatio = BigDecimal.ZERO;
BigDecimal platformRatio = BigDecimal.ONE;
BigDecimal distributorSplitRatio = BigDecimal.ZERO;
BigDecimal platformSplitRatio = BigDecimal.ONE;
// 分账代理商接收方信息
List<LklLedgerMerReceiverBind> distributors = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo);
if (distributors != null && distributors.size() > 0) {
distributorRatio = new BigDecimal("0.8");
platformRatio = new BigDecimal("0.2");
List<LklLedgerMerReceiverBind> distributorReceivers = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo);
if (distributorReceivers != null && !distributorReceivers.isEmpty()) {
distributorSplitRatio = new BigDecimal("0.8");
platformSplitRatio = new BigDecimal("0.2");
}
// 记录关键分账参数便于问题排查
log.info("分账参数信息:订单={}, 商户={}, 总金额={}分, 商家比例={}, 平台比例={}, 代理商比例={}, 是否有代理商={}",
orderId, merchantNo, splitAmount, mchRatio, platformRatio, distributorRatio,
(distributors != null && !distributors.isEmpty()));
log.info("[分账操作] 参数信息:订单={}, 商户={}, 总金额={}分, 商家比例={}, 平台比例={}, 代理商比例={}, 是否有代理商={}",
orderId, merchantNo, splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio,
(distributorReceivers != null && !distributorReceivers.isEmpty()));
// 返回值如下{platformAmount=6, merchantAmount=94, agentAmount=0}
Map<String, Integer> splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, mchRatio, platformRatio, distributorRatio);
Map<String, Integer> splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio);
Integer merchantAmount = splitAmountMap.get("merchantAmount");
Integer platformAmount = splitAmountMap.get("platformAmount");
Integer agentAmount = splitAmountMap.get("agentAmount");
// 记录分账结果便于问题排查
log.info("分账金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分",
log.info("[分账操作] 金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分",
orderId, merchantNo, splitAmount, merchantAmount, platformAmount, agentAmount);
if (merchantAmount > 0) {
@ -1725,55 +1828,55 @@ public class LakalaApiServiceImpl implements LakalaApiService {
if (platformAmount > 0) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(platform.getReceiver_no());
receiver.setRecvNo(platformReceiver.getReceiver_no());
receiver.setSeparateValue(platformAmount.toString());
recvDatas.add(receiver);
}
if (agentAmount > 0 && distributors != null && !distributors.isEmpty()) {
if (agentAmount > 0 && distributorReceivers != null && !distributorReceivers.isEmpty()) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(distributors.get(0).getReceiver_no());
receiver.setRecvNo(distributorReceivers.get(0).getReceiver_no());
receiver.setSeparateValue(agentAmount.toString());
recvDatas.add(receiver);
}
// 14. 构建分账请求对象
V3SacsSeparateRequest request = new V3SacsSeparateRequest();
request.setMerchantNo(merchantNo);
request.setLogNo(shopOrderLkl.getLkl_split_log_no()); // 合单和非合单的流水号保存在此字段
request.setLogDate(shopOrderLkl.getLkl_log_date());
request.setOutSeparateNo(shopOrderLkl.getOut_separate_no());
request.setTotalAmt(splitAmount.toString());
request.setLklOrgNo(orgCode);
request.setCalType("0"); // 0- 按照指定金额1- 按照指定比例默认 0
request.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify");
V3SacsSeparateRequest separateRequest = new V3SacsSeparateRequest();
separateRequest.setMerchantNo(merchantNo);
separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 合单和非合单的流水号保存在此字段
separateRequest.setLogDate(shopOrderLkl.getLkl_log_date());
separateRequest.setOutSeparateNo(shopOrderLkl.getOut_separate_no());
separateRequest.setTotalAmt(splitAmount.toString());
separateRequest.setLklOrgNo(orgCode);
separateRequest.setCalType("0"); // 0- 按照指定金额1- 按照指定比例默认 0
separateRequest.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify");
// 15. 设置分账接收方列表
request.setRecvDatas(recvDatas);
log.info("分账请求参数: 订单={}, 商户={}, 金额={}分, 分账接收方数量={}",
separateRequest.setRecvDatas(recvDatas);
log.info("[分账操作] 请求参数: 订单={}, 商户={}, 金额={}分, 分账接收方数量={}",
orderId, merchantNo, splitAmount, recvDatas.size());
log.debug("分账请求详细参数: {}", JSONUtil.toJsonStr(request));
log.debug("[分账操作] 请求详细参数: {}", JSONUtil.toJsonStr(separateRequest));
// 16. 发送分账请求
log.info("向拉卡拉发送分账请求:订单={}, 商户={}, 分账流水号={}",
orderId, merchantNo, shopOrderLkl.getLkl_split_log_no());
String response = LKLSDK.httpPost(request);
log.info("[分账操作] 向拉卡拉发送分账请求:订单={}, 商户={}, 分账流水号={}",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no());
String response = LKLSDK.httpPost(separateRequest);
if (StrUtil.isBlank(response)) {
String errorMsg = String.format("拉卡拉无响应,订单=%s商户=%s分账流水号=%s",
orderId, merchantNo, shopOrderLkl.getLkl_split_log_no());
String errorMsg = String.format("[分账操作] 拉卡拉无响应,订单=%s商户=%s分账流水号=%s",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no());
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
}
log.debug("分账响应结果: {}", response);
log.debug("[分账操作] 响应结果: {}", response);
// 17. 解析响应结果
JSONObject respJson = JSONUtil.parseObj(response);
if (respJson == null || !lklSacsSuccessCode.equals(respJson.getStr("code")) || respJson.getJSONObject("resp_data") == null) {
String errorMsg = String.format("拉卡拉返回格式异常,订单=%s商户=%s分账流水号=%s响应=%srespJson=%s",
orderId, merchantNo, shopOrderLkl.getLkl_split_log_no(), response, respJson);
String errorMsg = String.format("[分账操作] 拉卡拉返回格式异常,订单=%s商户=%s分账流水号=%s响应=%srespJson=%s",
orderId, merchantNo, shopOrderLkl.getLkl_sub_log_no(), response, respJson);
log.error(errorMsg);
errorMessages.append(errorMsg).append("; ");
continue;
@ -1781,37 +1884,37 @@ public class LakalaApiServiceImpl implements LakalaApiService {
// 18. 保存分账记录
JSONObject respData = respJson.getJSONObject("resp_data");
LklOrderSeparate lklOrderSeparate = new LklOrderSeparate();
lklOrderSeparate.setSeparate_no(respData.getStr("separate_no"));
lklOrderSeparate.setOut_separate_no(request.getOutSeparateNo());
lklOrderSeparate.setMerchant_no(merchantNo);
lklOrderSeparate.setLog_no(request.getLogNo());
lklOrderSeparate.setLog_date(request.getLogDate());
lklOrderSeparate.setOrder_id(shopOrderLkl.getOrder_id());
lklOrderSeparate.setTotal_amt(request.getTotalAmt());
lklOrderSeparate.setNotify_url(request.getNotifyUrl());
lklOrderSeparate.setLkl_org_no(request.getLklOrgNo());
lklOrderSeparate.setRecv_datas(JSONUtil.toJsonStr(request.getRecvDatas()));
lklOrderSeparate.setStatus(respData.getStr("status"));
lklOrderSeparate.setTotal_separate_value(platformAmount + agentAmount);
LklOrderSeparate separateRecord = new LklOrderSeparate();
separateRecord.setSeparate_no(respData.getStr("separate_no"));
separateRecord.setOut_separate_no(separateRequest.getOutSeparateNo());
separateRecord.setMerchant_no(merchantNo);
separateRecord.setLog_no(separateRequest.getLogNo()); // 发货完成交易流水号后14位 分账商户用该流水号发起分账
separateRecord.setLog_date(separateRequest.getLogDate());
separateRecord.setOrder_id(shopOrderLkl.getOrder_id());
separateRecord.setTotal_amt(separateRequest.getTotalAmt());
separateRecord.setNotify_url(separateRequest.getNotifyUrl());
separateRecord.setLkl_org_no(separateRequest.getLklOrgNo());
separateRecord.setRecv_datas(JSONUtil.toJsonStr(separateRequest.getRecvDatas()));
separateRecord.setStatus(respData.getStr("status"));
separateRecord.setTotal_separate_value(platformAmount + agentAmount);
try {
if (lklOrderSeparateService.addOrUpdateByReceiverNo(lklOrderSeparate)) {
log.info("分账记录保存成功:订单={}, 分账单号={}, 状态={}, 分账流水号={}",
orderId, lklOrderSeparate.getSeparate_no(), lklOrderSeparate.getStatus(), lklOrderSeparate.getLog_no());
if (lklOrderSeparateService.addOrUpdateByReceiverNo(separateRecord)) {
log.info("[分账操作] 记录保存成功:订单={}, 分账单号={}, 状态={}, 分账流水号={}",
orderId, separateRecord.getSeparate_no(), separateRecord.getStatus(), separateRecord.getLog_no());
successCount++;
} else {
String errorMsg = String.format("保存分账记录失败,订单=%s分账单号=%s分账流水号=%s",
orderId, lklOrderSeparate.getSeparate_no(), lklOrderSeparate.getLog_no());
String errorMsg = String.format("[分账操作] 保存分账记录失败,订单=%s分账单号=%s分账流水号=%s",
orderId, separateRecord.getSeparate_no(), separateRecord.getLog_no());
log.error(errorMsg);
lklOrderSeparateService.updateRemark(lklOrderSeparate.getLog_no(), lklOrderSeparate.getSeparate_no(), errorMsg);
lklOrderSeparateService.updateRemark(separateRecord.getLog_no(), separateRecord.getSeparate_no(), errorMsg);
errorMessages.append(errorMsg).append("; ");
}
} catch (Exception e) {
String errorMsg = String.format("保存分账记录异常,订单=%s分账单号=%s流水号=%s错误=%s",
String errorMsg = String.format("[分账操作] 保存分账记录异常,订单=%s分账单号=%s流水号=%s错误=%s",
orderId,
lklOrderSeparate.getSeparate_no(),
lklOrderSeparate.getLog_no(),
separateRecord.getSeparate_no(),
separateRecord.getLog_no(),
e.getMessage());
log.error(errorMsg, e);
errorMessages.append(errorMsg).append("; ");
@ -1819,27 +1922,284 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
// 19. 返回最终处理结果
log.info("订单[{}]分账处理完成:总订单数={},成功处理数={}", orderId, totalCount, successCount);
log.info("[分账操作] 处理完成:总订单数={},成功处理数={}", orderId, totalCount, successCount);
if (successCount == 0) {
String result = "分账全部失败: " + errorMessages;
log.warn("订单[{}]分账结果:{}", orderId, result);
log.warn("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(false, result);
} else if (successCount < totalCount) {
String result = "部分分账成功,处理中: " + errorMessages;
log.info("订单[{}]分账结果:{}", orderId, result);
log.info("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(true, result);
} else {
String result = "全部订单分账已提交处理";
log.info("订单[{}]分账结果:{}", orderId, result);
log.info("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(true, result);
}
} catch (Exception e) {
String errorMsg = String.format("分账系统异常,订单=%s错误=%s", orderId, e.getMessage());
String errorMsg = String.format("[分账操作] 系统异常,订单=%s错误=%s", orderId, e.getMessage());
log.error(errorMsg, e);
return Pair.of(false, "系统异常,请稍后重试");
}
}
/**
* 根据商户号交易号和收货流水号执行订单分账操作
* <p>
* 该方法用于处理拉卡拉订单的分账逻辑在用户确认收货后系统会根据订单信息执行分账操作
* 将订单金额按照预设比例分配给平台商家和代理商如果存在
* </p>
* <p>
* 分账操作流程
* 1. 参数校验和订单查询
* 2. 检查订单状态是否已确认收货
* 3. 检查是否已分账避免重复处理
* 4. 计算分账金额
* 5. 构建分账请求并发送至拉卡拉
* 6. 保存分账结果
* </p>
*
* @param lklMerchantNo 拉卡拉商户号
* @param receiveTradeNo 收货交易号对应拉卡拉的trade_no
* @param receiveLogNo 收货流水号对应拉卡拉的log_no
* @return Pair<Boolean, String> 处理结果对first为是否成功second为结果描述信息
*/
@Transactional
@Override
public Pair<Boolean, String> innerDoOrderSeparateByMerchantAndLogNo(String lklMerchantNo, String receiveTradeNo, String receiveLogNo) {
// 1. 输入参数校验
if (StrUtil.isBlank(lklMerchantNo) || StrUtil.isBlank(receiveTradeNo) || StrUtil.isBlank(receiveLogNo)) {
log.warn("[分账操作] 参数校验失败:缺少必要参数, merchantNo={}, tradeNo={}, logNo={}",
lklMerchantNo, receiveTradeNo, receiveLogNo);
return Pair.of(false, "缺少必要参数");
}
try {
log.info("[分账操作] 开始处理分账请求, merchantNo={}, tradeNo={}, logNo={}",
lklMerchantNo, receiveTradeNo, receiveLogNo);
// TODO 检查可分账余额是否足够
// 2. 查询订单信息
ShopOrderLkl shopOrderLkl = shopOrderLklService.getByMerchantNoAndTradeNoAndSubLogNo(lklMerchantNo, receiveTradeNo, receiveLogNo);
if (shopOrderLkl == null) {
log.warn("[分账操作] 失败:对账流水号[{}]不存在", receiveLogNo);
return Pair.of(false, "订单不存在");
}
String orderId = shopOrderLkl.getOrder_id();
log.info("[分账操作] 开始处理订单[{}]的分账", orderId);
log.debug("[分账操作] 处理子订单storeId={}, receive_log_no={}", shopOrderLkl.getStore_id(), shopOrderLkl.getLkl_receive_log_no());
// 3. 检查是否已确认收货
if (!CommonConstant.Enable.equals(shopOrderLkl.getReceive_status()) || StrUtil.isBlank(shopOrderLkl.getLkl_receive_log_no())) {
log.warn("[分账操作] 订单[{}]对账流水号[{}]未被确认收货,跳过处理", orderId, shopOrderLkl.getLkl_receive_log_no());
return Pair.of(false, "订单未确认收货");
}
// 4. 检查分账状态避免重复处理
LklOrderSeparate existingSeparateRecord = lklOrderSeparateService.getByOutTradeNo(shopOrderLkl.getLkl_sub_log_no(), shopOrderLkl.getOut_separate_no());
if (existingSeparateRecord != null) {
String status = existingSeparateRecord.getStatus();
if ("SUCCESS".equals(status)) {
log.info("[分账操作] 订单[{}]子订单[{}]已完成分账,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
return Pair.of(true, "订单已处理");
}
if ("PROCESSING".equals(status) || "ACCEPTED".equals(status)) {
log.info("[分账操作] 订单[{}]子订单[{}]分账处理中或已受理,跳过处理", orderId, shopOrderLkl.getLkl_log_no());
return Pair.of(true, "订单已处理中或已受理");
}
}
// 5. 获取订单分账相关参数
String merchantNo = shopOrderLkl.getLkl_merchant_no();
// 分账金额 = 应付总金额-运费支付时已计算好
Integer splitAmount = shopOrderLkl.getSplit_amt();
splitAmount = CheckUtil.isEmpty(splitAmount) ? 0 : splitAmount;
// 6. 分账金额校验
if (splitAmount < 1) {
String errorMsg = String.format("[分账操作] 店铺[%s]订单[%s]分账金额[%d]低于1分钱跳过分账",
shopOrderLkl.getStore_id(), orderId, splitAmount);
log.error(errorMsg);
if (existingSeparateRecord != null) {
lklOrderSeparateService.updateRemark(existingSeparateRecord.getId(), errorMsg);
}
return Pair.of(false, "订单分账金额低于1分钱");
}
// 7. 获取分账平台接收方信息
LklLedgerMerReceiverBind platformReceiver = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo);
if (platformReceiver == null) {
String errorMsg = String.format("[分账操作] 店铺[%s]未绑定平台方接收账户,跳过分账", shopOrderLkl.getStore_id());
log.error(errorMsg);
return Pair.of(false, "平台方未绑定账户");
}
// 8. 构建分账接收方列表
List<V3SacsSeparateRecvDatas> recvDatas = new ArrayList<>();
// 9. 获取商家分账比例并校验
BigDecimal merchantSplitRatioRaw = shopOrderLkl.getSplit_ratio();
// 判断商家分账比例是否有效必须在(0, 100]范围内
boolean canSplitForMerchant = merchantSplitRatioRaw != null
&& merchantSplitRatioRaw.compareTo(BigDecimal.ZERO) > 0
&& merchantSplitRatioRaw.compareTo(new BigDecimal(100)) <= 0;
if (!canSplitForMerchant) {
String errorMsg = String.format("[分账操作] 店铺[%s]商家分账比例[%s]不在(0-100]范围内,无法分账",
shopOrderLkl.getStore_id(), merchantSplitRatioRaw);
log.error(errorMsg);
return Pair.of(false, "商家分账比例无效");
}
// 10. 计算商家分账比例转换为小数
BigDecimal merchantSplitRatio = merchantSplitRatioRaw.divide(new BigDecimal(100));
// 11. 获取代理商分账信息
BigDecimal distributorSplitRatio = BigDecimal.ZERO;
BigDecimal platformSplitRatio = BigDecimal.ONE;
List<LklLedgerMerReceiverBind> distributorReceivers = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo);
if (distributorReceivers != null && !distributorReceivers.isEmpty()) {
distributorSplitRatio = new BigDecimal("0.8");
platformSplitRatio = new BigDecimal("0.2");
log.debug("[分账操作] 检测到代理商存在,调整分账比例: 代理商比例={}, 平台比例={}", distributorSplitRatio, platformSplitRatio);
}
// 12. 记录关键分账参数便于问题排查
log.info("[分账操作] 参数信息:订单={}, 商户={}, 总金额={}分, 商家比例={}, 平台比例={}, 代理商比例={}, 是否有代理商={}",
orderId, merchantNo, splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio,
(distributorReceivers != null && !distributorReceivers.isEmpty()));
// 13. 计算各参与方分账金额
Map<String, Integer> splitAmountMap = CommonUtil.calculateProfitSharing(splitAmount, merchantSplitRatio, platformSplitRatio, distributorSplitRatio);
Integer merchantAmount = splitAmountMap.get("merchantAmount");
Integer platformAmount = splitAmountMap.get("platformAmount");
Integer agentAmount = splitAmountMap.get("agentAmount");
// 14. 记录分账结果便于问题排查
log.info("[分账操作] 金额计算结果:订单={}, 商户={}, 总金额={}分, 商家分得={}分, 平台分得={}分, 代理商分得={}分",
orderId, merchantNo, splitAmount, merchantAmount, platformAmount, agentAmount);
// 15. 构建分账接收方列表
if (merchantAmount > 0) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvMerchantNo(merchantNo);
receiver.setSeparateValue(merchantAmount.toString());
recvDatas.add(receiver);
log.debug("[分账操作] 添加商家接收方: merchantNo={}, amount={}", merchantNo, merchantAmount);
}
if (platformAmount > 0) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(platformReceiver.getReceiver_no());
receiver.setSeparateValue(platformAmount.toString());
recvDatas.add(receiver);
log.debug("[分账操作] 添加平台接收方: receiverNo={}, amount={}", platformReceiver.getReceiver_no(), platformAmount);
}
if (agentAmount > 0 && distributorReceivers != null && !distributorReceivers.isEmpty()) {
V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas();
receiver.setRecvNo(distributorReceivers.get(0).getReceiver_no());
receiver.setSeparateValue(agentAmount.toString());
recvDatas.add(receiver);
log.debug("[分账操作] 添加代理商接收方: receiverNo={}, amount={}", distributorReceivers.get(0).getReceiver_no(), agentAmount);
}
// 16. 初始化拉卡拉SDK
log.debug("[分账操作] 初始化拉卡拉SDK");
initLKLSDK();
// 17. 构建分账请求对象
V3SacsSeparateRequest separateRequest = new V3SacsSeparateRequest();
separateRequest.setMerchantNo(merchantNo);
separateRequest.setLogNo(shopOrderLkl.getLkl_receive_log_no()); // 使用收货流水号作为分账流水号
separateRequest.setLogDate(shopOrderLkl.getLkl_log_date());
separateRequest.setOutSeparateNo(shopOrderLkl.getOut_separate_no());
separateRequest.setTotalAmt(splitAmount.toString());
separateRequest.setLklOrgNo(orgCode);
separateRequest.setCalType("0"); // 0- 按照指定金额1- 按照指定比例默认 0
separateRequest.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify");
// 18. 设置分账接收方列表
separateRequest.setRecvDatas(recvDatas);
log.info("[分账操作] 请求参数: 订单={}, 商户={}, 金额={}分, 分账接收方数量={}",
orderId, merchantNo, splitAmount, recvDatas.size());
log.debug("[分账操作] 请求详细参数: {}", JSONUtil.toJsonStr(separateRequest));
// 19. 发送分账请求
log.info("[分账操作] 向拉卡拉发送分账请求:订单={}, 商户={}, 分账流水号={}",
orderId, merchantNo, shopOrderLkl.getLkl_receive_log_no());
String response = LKLSDK.httpPost(separateRequest);
if (StrUtil.isBlank(response)) {
String errorMsg = String.format("[分账操作] 拉卡拉无响应,订单=%s商户=%s分账流水号=%s",
orderId, merchantNo, shopOrderLkl.getLkl_receive_log_no());
log.error(errorMsg);
return Pair.of(false, "拉卡拉无响应");
}
log.debug("[分账操作] 响应结果: {}", response);
// 20. 解析响应结果
JSONObject respJson = JSONUtil.parseObj(response);
if (respJson == null || !lklSacsSuccessCode.equals(respJson.getStr("code")) || respJson.getJSONObject("resp_data") == null) {
String errorMsg = String.format("[分账操作] 拉卡拉返回格式异常,订单=%s商户=%s分账流水号=%s响应=%srespJson=%s",
orderId, merchantNo, shopOrderLkl.getLkl_receive_log_no(), response, respJson);
log.error(errorMsg);
return Pair.of(false, "拉卡拉返回格式异常");
}
// 21. 保存分账记录
JSONObject respData = respJson.getJSONObject("resp_data");
LklOrderSeparate separateRecord = new LklOrderSeparate();
separateRecord.setSeparate_no(respData.getStr("separate_no"));
separateRecord.setOut_separate_no(separateRequest.getOutSeparateNo());
separateRecord.setMerchant_no(merchantNo);
separateRecord.setLog_no(separateRequest.getLogNo());
separateRecord.setLog_date(separateRequest.getLogDate());
separateRecord.setOrder_id(shopOrderLkl.getOrder_id());
separateRecord.setTotal_amt(separateRequest.getTotalAmt());
separateRecord.setNotify_url(separateRequest.getNotifyUrl());
separateRecord.setLkl_org_no(separateRequest.getLklOrgNo());
separateRecord.setRecv_datas(JSONUtil.toJsonStr(separateRequest.getRecvDatas()));
separateRecord.setStatus(respData.getStr("status"));
separateRecord.setTotal_separate_value(platformAmount + agentAmount);
try {
if (lklOrderSeparateService.addOrUpdateByReceiverNo(separateRecord)) {
log.info("[分账操作] 记录保存成功:订单={}, 分账单号={}, 状态={}, 分账流水号={}",
orderId, separateRecord.getSeparate_no(), separateRecord.getStatus(), separateRecord.getLog_no());
} else {
String errorMsg = String.format("[分账操作] 保存分账记录失败,订单=%s分账单号=%s分账流水号=%s",
orderId, separateRecord.getSeparate_no(), separateRecord.getLog_no());
log.error(errorMsg);
lklOrderSeparateService.updateRemark(separateRecord.getLog_no(), separateRecord.getSeparate_no(), errorMsg);
return Pair.of(false, "保存分账记录失败");
}
String result = "订单分账已提交处理";
log.info("[分账操作] 结果:订单[{}] {}", orderId, result);
return Pair.of(true, result);
} catch (Exception e) {
String errorMsg = String.format("[分账操作] 保存分账记录异常,订单=%s分账单号=%s流水号=%s错误=%s",
orderId,
separateRecord.getSeparate_no(),
separateRecord.getLog_no(),
e.getMessage());
log.error(errorMsg, e);
return Pair.of(false, "保存分账记录异常");
}
} catch (Exception e) {
String errorMsg = String.format("[分账操作] 系统异常,分账对账流水号=%s错误=%s", receiveLogNo, e.getMessage());
log.error(errorMsg, e);
return Pair.of(false, "系统异常,请稍后重试");
}
}
/**
* 拉卡拉分账结果通知处理
* <p>

View File

@ -63,4 +63,14 @@ public interface ShopOrderLklService extends IBaseService<ShopOrderLkl> {
* @return
*/
List<ShopOrderLkl> selectByOrderId(String orderId, String lklLogNo, String storeId);
/**
* 根据商户号商户订单号子商户订单号查询一条记录
*
* @param lklMerchantNo
* @param lklTradeNo
* @param lklSubLogNo
* @return
*/
ShopOrderLkl getByMerchantNoAndTradeNoAndSubLogNo(String lklMerchantNo, String lklTradeNo, String lklSubLogNo);
}

View File

@ -4961,10 +4961,10 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl<ShopOrderBaseMappe
}
// 重要拉卡拉给平台和代理商分账
Pair<Boolean, String> retOrderSeparateRet = lakalaApiService.innerDoOrderSeparate(order_row.getOrder_id(), Convert.toStr(order_row.getStore_id()));
if (!retOrderSeparateRet.getFirst()) {
throw new ApiException(I18nUtil._("平台或代理商分账失败: " + retOrderSeparateRet.getSecond()));
}
// Pair<Boolean, String> retOrderSeparateRet = lakalaApiService.innerDoOrderSeparate(order_row.getOrder_id(), Convert.toStr(order_row.getStore_id()));
// if (!retOrderSeparateRet.getFirst()) {
// throw new ApiException(I18nUtil._("平台或代理商分账失败: " + retOrderSeparateRet.getSecond()));
// }
// 统计总营业额
ShopStoreAnalytics analytics_row = shopStoreAnalyticsService.get(store_id);

View File

@ -183,6 +183,7 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
record.setAccount_type(JsonUtil.getJsonValueSmart(reqDataJson, "accountType"));
record.setTrans_type(JsonUtil.getJsonValueSmart(reqDataJson, "transType"));
record.setNotify_url(JsonUtil.getJsonValueSmart(reqDataJson, "notifyUrl"));
record.setReceive_notify_url(JsonUtil.getJsonValueSmart(reqDataJson, "complete_notify_url"));
record.setLkl_merchant_no(JsonUtil.getJsonValueSmart(reqDataJson, "merchantNo"));
record.setLkl_term_no(JsonUtil.getJsonValueSmart(reqDataJson, "termNo"));
record.setLkl_req(JSONUtil.toJsonStr(reqDataJson));
@ -235,10 +236,10 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
record.setLkl_trade_no(lklPayNotifyDataJson.getStr("trade_no"));
record.setTrade_status(lklPayNotifyDataJson.getStr("trade_status"));
// 新增的订单字段,lkl_split_log_no,out_separate_no,split_amt 三字段无值就给主单的值
record.setLkl_split_log_no(JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "split_log_no"));
if (CheckUtil.isEmpty(record.getLkl_split_log_no())) {
record.setLkl_split_log_no(logNo);
// 新增的订单字段,lkl_sub_log_no,out_separate_no,split_amt 三字段无值就给主单的值
record.setLkl_sub_log_no(JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "split_log_no"));
if (CheckUtil.isEmpty(record.getLkl_sub_log_no())) {
record.setLkl_sub_log_no(logNo);
}
record.setOut_separate_no(JsonUtil.getJsonValueSmart(lklPayNotifyDataJson, "out_separate_no"));
@ -292,4 +293,56 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
return CollectionUtil.newArrayList();
}
}
/**
* 根据商户号商户订单号子商户订单号查询一条记录
*
* @param lklMerchantNo 拉卡拉商户号
* @param lklTradeNo 拉卡拉交易号
* @param lklSubLogNo 拉卡拉子订单流水号
* @return ShopOrderLkl 拉卡拉订单记录
*/
@Override
public ShopOrderLkl getByMerchantNoAndTradeNoAndSubLogNo(String lklMerchantNo, String lklTradeNo, String lklSubLogNo) {
// 检查参数是否全部为空
if (StringUtils.isAllBlank(lklMerchantNo, lklTradeNo, lklSubLogNo)) {
log.warn("[拉卡拉订单查询] 参数校验失败:所有查询条件均为空");
return null;
}
try {
log.debug("[拉卡拉订单查询] 开始查询, merchantNo={}, tradeNo={}, subLogNo={}",
lklMerchantNo, lklTradeNo, lklSubLogNo);
QueryWrapper<ShopOrderLkl> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("id");
// 根据非空参数构建查询条件
if (StrUtil.isNotBlank(lklMerchantNo)) {
queryWrapper.eq("lkl_merchant_no", lklMerchantNo);
log.debug("[拉卡拉订单查询] 添加商户号查询条件: {}", lklMerchantNo);
}
if (StrUtil.isNotBlank(lklTradeNo)) {
queryWrapper.eq("lkl_trade_no", lklTradeNo);
log.debug("[拉卡拉订单查询] 添加交易号查询条件: {}", lklTradeNo);
}
if (StrUtil.isNotBlank(lklSubLogNo)) {
queryWrapper.eq("lkl_sub_log_no", lklSubLogNo);
log.debug("[拉卡拉订单查询] 添加子订单流水号查询条件: {}", lklSubLogNo);
}
ShopOrderLkl result = findOne(queryWrapper);
log.debug("[拉卡拉订单查询] 查询完成, merchantNo={}, tradeNo={}, subLogNo={}, found={}",
lklMerchantNo, lklTradeNo, lklSubLogNo, result != null);
return result;
} catch (Exception e) {
log.error("[拉卡拉订单查询] 系统异常, merchantNo={}, tradeNo={}, subLogNo={}",
lklMerchantNo, lklTradeNo, lklSubLogNo, e);
return null;
}
}
}

View File

@ -1011,9 +1011,9 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
return new ThirdApiRes().fail(-1, "返回数据转换失败!");
}
String orderId = shopStoreSfOrder.getShop_order_id();
ShopStoreSfOrder order = shopStoreSfOrderService.getBySfOrderId(orderId);
String sfOrderId = shopStoreSfOrder.getSf_order_id();
ShopStoreSfOrder order = shopStoreSfOrderService.getBySfOrderId(sfOrderId);
if (order == null) {
return new ThirdApiRes().fail(-1, "订单不存在!");
}
@ -1067,6 +1067,8 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
// 订单确认收货
shopOrderBaseService.receive(shopStoreSfOrder.getShop_order_id(), null);
String orderId = shopStoreSfOrder.getShop_order_id();
// 消息推送
JSONObject payload = new JSONObject();
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);