diff --git a/README.MD b/README.MD index da73b328..ec991b94 100644 --- a/README.MD +++ b/README.MD @@ -27,7 +27,6 @@ mall-im } 清除 docker 日志 -docker ps -aq | xargs docker inspect --format='{{.LogPath}}' | xargs truncate -s 0 查看哪个文件夹占空间 du -sh * | sort -h @@ -36,10 +35,41 @@ cd /data/docker/overlay2 查看哪个文件夹占空间 du -sh * | sort -h -进入到 大文件的目录下,执行以下脚本 -for file in *log*; do -cat /dev/null > "$file" -done +### 删除 docker 产生的日志文件:update:2025-05-21 -或者递归删除log 文件 -find overlay2 -type f -name "*.log.*" -print0 | xargs -0 -I {} cat /dev/null > {} \ No newline at end of file +* 避免误删: 先用 -print 参数预览匹配的文件,确认无误后再执行 -delete: +* 删除 7 天前 + +``` bash +-- 打印所有匹配文件路径 +find /data/docker/overlay2 -type f \( \ + -name "*.log" \ + -o -name "*.log.[0-9]*" \ + -o -name "*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]" \ +\) -not \( \ + -name "mysql*.log*" \ + -o -name "*.idx" \ + -o -name "*.lck" \ + -o -name "*.js" \ + -o -name "*.yml" \ + -o -name "*.toml" \ + -o -name "*.gz" \ + -o -name "*mysql*" \ +\) -print + +-- 删除 7 天前的日志文件 +find /data/docker/overlay2 -type f \( \ + -name "*.log" \ + -o -name "*.log.[0-9]*" \ + -o -name "*.log.[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]" \ +\) -not \( \ + -name "mysql*.log*" \ + -o -name "*.idx" \ + -o -name "*.lck" \ + -o -name "*.js" \ + -o -name "*.yml" \ + -o -name "*.toml" \ + -o -name "*.gz" \ + -o -name "*mysql*" \ +\) -mtime +7 -delete +``` \ No newline at end of file diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklLedgerMerReceiverBind.java b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklLedgerMerReceiverBind.java index 5b714f5e..2495d37a 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklLedgerMerReceiverBind.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklLedgerMerReceiverBind.java @@ -40,6 +40,7 @@ public class LklLedgerMerReceiverBind implements Serializable { private String entrust_file_path; private String ret_url; private String apply_id; + private Long platform_id; private String audit_status; private String audit_status_text; private String remark; diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderSeparate.java b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderSeparate.java index d0562013..bd3ef01c 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderSeparate.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/lakala/LklOrderSeparate.java @@ -46,6 +46,9 @@ public class LklOrderSeparate { @ApiModelProperty(value = "分账指令流水号,分账系统生成唯一流水") private String separate_no; + @ApiModelProperty(value = "平台订单Id") + private String order_id; + @ApiModelProperty(value = "分账总金额,单位:分") private String total_amt; diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderLkl.java b/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderLkl.java new file mode 100644 index 00000000..b147537a --- /dev/null +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/order/ShopOrderLkl.java @@ -0,0 +1,70 @@ +package com.suisung.mall.common.modules.order; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.Date; + +/** + *

+ * 关联拉卡拉的店铺订单信息 + *

+ * + * @author Xinze + * @since 2021-04-30 + */ +@Data +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +@TableName("shop_order_lkl") +@ApiModel(value = "ShopOrderLkl对象", description = "关联拉卡拉的店铺订单信息") +public class ShopOrderLkl implements Serializable { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty(value = "自增Id") + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + private String store_id; + + private String order_id; + + private String log_no; + + private String log_date; + + private String merchant_no; + + private Integer total_amt; + + private Integer shopping_fee; + + private BigDecimal split_ratio; + + private String payment_time; + + private String account_type; + + private String trans_type; + + private String lkl_trade_no; + + private String lkl_term_no; + + private String lkl_resp; + + private Integer status; + + private Date created_at; + + private Date updated_at; +} diff --git a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java index 0131dff4..b7e38ce4 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java +++ b/mall-common/src/main/java/com/suisung/mall/common/modules/store/ShopMchEntry.java @@ -246,6 +246,9 @@ public class ShopMchEntry implements Serializable { @ApiModelProperty(value = "是否签署电子合同:1-是;2-否;") private Integer has_ec_signed; + @ApiModelProperty(value = "是否进件成功:1-是;2-否;") + private Integer has_apply_mer; + @ApiModelProperty(value = "是否申请分账业务:1-是;2-否;") private Integer has_apply_split; diff --git a/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java b/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java index 30311956..2fb4341e 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java +++ b/mall-common/src/main/java/com/suisung/mall/common/utils/CommonUtil.java @@ -140,6 +140,8 @@ public class CommonUtil { /** * Double 四舍五入,不保留小数点 + * 2.5=3 + * 2.3=2 * * @param d * @return @@ -150,6 +152,8 @@ public class CommonUtil { /** * Decimal 四舍五入,不保留小数点 + * 2.5=3 + * 2.3=2 * * @param d * @return @@ -158,6 +162,18 @@ public class CommonUtil { return d.setScale(0, RoundingMode.HALF_UP); } + /** + * Decimal 不四舍五入,不保留小数点 + * 2.5 = 2 + * 2.2 = 2 + * + * @param d + * @return + */ + public static BigDecimal DecimalRoundHalfDown(BigDecimal d) { + return d.setScale(0, RoundingMode.HALF_DOWN); + } + /** * 根据上一个订单状态和当前状态,获取订单状态变化备注 diff --git a/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/PayUserPayServiceImpl.java b/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/PayUserPayServiceImpl.java index e1836f0d..64bdd1f7 100644 --- a/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/PayUserPayServiceImpl.java +++ b/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/PayUserPayServiceImpl.java @@ -1268,73 +1268,70 @@ public class PayUserPayServiceImpl extends BaseServiceImpl authMap = LakalaUtil.getLakalaAuthorizationMap(authorization); - if (authMap != null && authMap.get("signature") != null) { + if (authMap != null && authMap.containsKey("signature")) { params.put("sign", authMap.get("signature")); + } else { + logger.error("缺少签名信息"); + return lklNotifyMsg(false, "缺少签名信息"); } - // 基于安全考虑,检测支付模式及数据 - // 判断是门店 店铺 平台 - Integer payment_store_id = 0; - Integer payment_chain_id = 0; - String orderSubject = ""; - Integer userId = 0; - + // 查询交易信息 QueryWrapper tradeQueryWrapper = new QueryWrapper<>(); tradeQueryWrapper.eq("order_id", order_id); PayConsumeTrade trade_row_tmp = payConsumeTradeService.findOne(tradeQueryWrapper); - if (trade_row_tmp != null) { - payment_store_id = trade_row_tmp.getStore_id(); - orderSubject = trade_row_tmp.getTrade_title(); - userId = trade_row_tmp.getBuyer_id(); - } + Integer payment_store_id = trade_row_tmp != null ? trade_row_tmp.getStore_id() : 0; + String orderSubject = trade_row_tmp != null ? trade_row_tmp.getTrade_title() : ""; + Integer userId = trade_row_tmp != null ? trade_row_tmp.getBuyer_id() : 0; + + // 查询支付渠道 QueryWrapper channelQueryWrapper = new QueryWrapper<>(); channelQueryWrapper.eq("payment_channel_code", "lakala"); PayPaymentChannel payPaymentChannel = payPaymentChannelService.findOne(channelQueryWrapper); + + if (payPaymentChannel == null) { + logger.error("支付渠道不存在"); + return lklNotifyMsg(false, "支付渠道不存在"); + } Integer payment_channel_id = payPaymentChannel.getPayment_channel_id(); // 插入充值记录 - PayConsumeDeposit notify_row = createNotify(params, payPaymentChannel); - notify_row.setOrder_id(order_id); - notify_row.setStore_id(payment_store_id); // 所属店铺 - notify_row.setChain_id(payment_chain_id); // 所属门店 - notify_row.setPayment_channel_id(payment_channel_id); - notify_row.setDeposit_subject(orderSubject); - notify_row.setDeposit_body(orderSubject); - notify_row.setUser_id(userId); - // notify_row.setDeposit_state(1);// 支付状态:0-默认; 1-接收正确数据处理完逻辑; 9-异常订单 - // notify_row.setDeposit_async(0); // 是否同步:0-同步; 1-异步回调使用 - - - BigDecimal zero = BigDecimal.ZERO; + PayConsumeDeposit payConsumeDeposit = createNotify(params, payPaymentChannel); + payConsumeDeposit.setOrder_id(order_id); + payConsumeDeposit.setStore_id(payment_store_id); // 所属店铺 + payConsumeDeposit.setChain_id(0); // 所属门店默认为0 + payConsumeDeposit.setPayment_channel_id(payment_channel_id); + payConsumeDeposit.setDeposit_subject(orderSubject); + payConsumeDeposit.setDeposit_body(orderSubject); + payConsumeDeposit.setUser_id(userId); // 判断是否联合支付 PayConsumeTradeCombine tradeCombine = payConsumeTradeCombineService.get(order_id); TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition); try { if (tradeCombine != null && StrUtil.isNotBlank(tradeCombine.getOrder_ids())) { - notify_row.setOrder_id(tradeCombine.getOrder_ids()); + payConsumeDeposit.setOrder_id(tradeCombine.getOrder_ids()); } - if (!payConsumeDepositService.processDeposit(notify_row, zero, zero, zero, zero, zero)) { + if (!payConsumeDepositService.processDeposit(payConsumeDeposit, BigDecimal.ZERO, BigDecimal.ZERO, + BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)) { log.error("支付失败!"); return lklNotifyMsg(false, "支付失败!"); } @@ -1342,13 +1339,13 @@ public class PayUserPayServiceImpl extends BaseServiceImpl(new JSONObject().put("code", 200).put("msg", "success").toString(), HttpStatus.OK); } 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 602c77a3..9384d1ae 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 @@ -9,7 +9,6 @@ package com.suisung.mall.shop.lakala.service; import cn.hutool.json.JSONObject; -import com.lkl.laop.sdk.request.V3SacsSeparateRequest; import com.suisung.mall.common.api.CommonResult; import org.springframework.data.util.Pair; @@ -180,7 +179,7 @@ public interface LakalaApiService { * @param bankCardNo * @return */ - JSONObject getBankCardBin(String bankCardNo); + CommonResult getBankCardBin(String bankCardNo); /** @@ -190,7 +189,8 @@ public interface LakalaApiService { * @param merchantNo 拉卡拉外部商户号 * @param logNo 拉卡拉对账单流水号 * @param logDate 拉卡拉对账单交易日期 yyyyMMdd - * @return 响应结果 { + * @return 响应结果 分账总金额,可分账金额 键值对 + * { * "merchant_no": "82229005943096D", * "total_separate_amt": "9900", * "can_separate_amt": "0", @@ -198,15 +198,24 @@ public interface LakalaApiService { * "log_no": "66210306990190" * } */ - JSONObject queryMchCanSplitAmt(String merchantNo, String logNo, String logDate); + Pair queryMchCanSplitAmt(String merchantNo, String logNo, String logDate); /** * 拉卡拉订单分账,用户下单成功之后,进行分账 * 说明:分账指令是异步处理模式,响应报文成功时,指令状态是”status”: “PROCESSING”,需要等待分账结果通知,或者主动发起查询,建议主动发起查询与分账指令动作之间间隔15秒以上。 * 参考:https://o.lakala.com/#/home/document/detail?id=389 * - * @param v3SacsSeparateRequest + * @param orderId 平台订单号 * @return */ - Pair innerDoOrderSeparate(V3SacsSeparateRequest v3SacsSeparateRequest); + Pair innerDoOrderSeparate(String orderId); + + /** + * 分账结果通知 + * 参考:https://o.lakala.com/#/home/document/detail?id=393 + * + * @param request + * @return + */ + JSONObject sacsSeparateNotify(HttpServletRequest request); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerEcService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerEcService.java index 63afe7aa..343e8bd3 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerEcService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerEcService.java @@ -19,7 +19,7 @@ public interface LklLedgerEcService extends IBaseService { * @param record * @return */ - Boolean saveOrUpdateByMchId(LklLedgerEc record); + Boolean addOrUpdateByMchId(LklLedgerEc record); /** * 根据applyId更新记录 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java index 29bfbda5..d0a6aa46 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java @@ -36,7 +36,7 @@ public interface LklLedgerMemberService extends IBaseService { * @param record * @return */ - Boolean saveOrUpdateByMerCupNo(LklLedgerMember record); + Boolean addOrUpdateByMerCupNo(LklLedgerMember record); /** diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java index 238f54f1..f1a5a965 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java @@ -11,6 +11,8 @@ package com.suisung.mall.shop.lakala.service; import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind; import com.suisung.mall.core.web.service.IBaseService; +import java.util.List; + public interface LklLedgerMerReceiverBindService extends IBaseService { /** @@ -19,10 +21,10 @@ public interface LklLedgerMerReceiverBindService extends IBaseService selectListByMerCupNo(String merCupNo, Boolean isPlatform); + + + /** + * 根据商户编号查询一条平台绑定记录 + * + * @param merCupNo + * @return + */ + LklLedgerMerReceiverBind getPlatformByMerCupNo(String merCupNo); + + /** + * 根据商户编号查询一条代理商绑定记录 + * + * @param merCupNo + * @return + */ + List selectDistributorByMerCupNo(String merCupNo); + /** * 更新审核结果 * diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.java index 26886a3a..6efa80e8 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.java @@ -22,7 +22,7 @@ public interface LklLedgerReceiverService extends IBaseService getByCondition(String LicenseNo, String ContactMobile); + + /** + * 根据条件查询记录数量 + * + * @param LicenseNo + * @param ContactMobile + * @param platformId + * @return + */ + Long countByCondition(String LicenseNo, String ContactMobile, Long platformId); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java new file mode 100644 index 00000000..07ea7bac --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. + * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. + * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. + * Vestibulum commodo. Ut rhoncus gravida arcu. + */ + +package com.suisung.mall.shop.lakala.service; + +import com.suisung.mall.common.modules.lakala.LklOrderSeparate; +import com.suisung.mall.core.web.service.IBaseService; + +public interface LklOrderSeparateService extends IBaseService { + + /** + * 新增或更新记录 + * + * @param record + * @return + */ + Boolean addOrUpdateByReceiverNo(LklOrderSeparate record); +} 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 c3d0abd8..73c9c818 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 @@ -9,8 +9,10 @@ package com.suisung.mall.shop.lakala.service.impl; +import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.ijpay.core.kit.IpKit; @@ -19,19 +21,19 @@ import com.lkl.laop.sdk.exception.SDKException; import com.lkl.laop.sdk.request.*; import com.lkl.laop.sdk.request.model.V3LabsTradeLocationInfo; import com.lkl.laop.sdk.request.model.V3LabsTradePreorderWechatBus; +import com.lkl.laop.sdk.request.model.V3SacsSeparateRecvDatas; import com.suisung.mall.common.api.CommonResult; import com.suisung.mall.common.constant.CommonConstant; import com.suisung.mall.common.exception.ApiException; import com.suisung.mall.common.feignService.ShopService; -import com.suisung.mall.common.modules.lakala.LklLedgerEc; -import com.suisung.mall.common.modules.lakala.LklLedgerMember; -import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind; -import com.suisung.mall.common.modules.lakala.LklLedgerReceiver; +import com.suisung.mall.common.modules.lakala.*; +import com.suisung.mall.common.modules.order.ShopOrderLkl; import com.suisung.mall.common.modules.store.ShopMchEntry; import com.suisung.mall.common.modules.store.ShopStoreBase; import com.suisung.mall.common.utils.*; import com.suisung.mall.shop.lakala.service.*; import com.suisung.mall.shop.lakala.utils.LakalaUtil; +import com.suisung.mall.shop.order.service.ShopOrderLklService; import com.suisung.mall.shop.page.service.OssService; import com.suisung.mall.shop.store.service.ShopMchEntryService; import com.suisung.mall.shop.store.service.ShopStoreBaseService; @@ -113,6 +115,10 @@ public class LakalaApiServiceImpl implements LakalaApiService { @Resource private ShopMchEntryService shopMchEntryService; + @Lazy + @Resource + private ShopOrderLklService shopOrderLklService; + // @Lazy // @Resource // private @@ -135,6 +141,10 @@ public class LakalaApiServiceImpl implements LakalaApiService { @Resource private LklTkServiceImpl lklTkService; + @Lazy + @Resource + private LklOrderSeparateService lklOrderSeparateService; + @Resource private OssService ossService; @@ -557,7 +567,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { ecParams.put("A109", today.getDayOfMonth()); ecParams.put("B1", today.getYear()); ecParams.put("B3", "是"); - ecParams.put("B4", "是"); +// ecParams.put("B4", "是"); ecParams.put("B2", today.getMonthValue()); ecParams.put("B8", isQy ? shopMchEntry.getBiz_license_company() : shopMchEntry.getAccount_holder_name()); ecParams.put("B9", StrUtil.sub(shopMchEntry.getSales_info(), 0, 22)); @@ -612,8 +622,12 @@ public class LakalaApiServiceImpl implements LakalaApiService { JSONObject header = new JSONObject(); header.put("Authorization", authorization); + log.debug("申请入网电子合同请求参数:{}", JsonUtil.toJSONString(reqBody)); + String errMsg = ""; ResponseEntity response = RestTemplateHttpUtil.sendPostBodyBackEntity(reqUrl, header, reqBody, JSONObject.class); + log.debug("申请入网电子合同响应参数:{}", JsonUtil.toJSONString(response)); + if (ObjectUtil.isEmpty(response) || response.getStatusCode() != HttpStatus.OK) { errMsg = "申请入网电子合同失败,无响应数据!"; shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), "", CommonConstant.MCH_APPR_STA_LKL_NOPASS, errMsg); @@ -628,7 +642,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { } JSONObject respData = respBody.getJSONObject("resp_data"); - if (respBody.getJSONObject("resp_data") == null) { + if (respData == null) { errMsg = "申请入网电子合同失败,返回数据有误"; shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), "", CommonConstant.MCH_APPR_STA_LKL_NOPASS, errMsg); return Pair.of(false, errMsg); @@ -644,7 +658,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { record.setEc_apply_id(respData.getLong("ec_apply_id")); record.setResult_url(respData.getStr("result_url")); record.setResp_body(respBody.toString()); - Boolean success = lklLedgerEcService.saveOrUpdateByMchId(record); + Boolean success = lklLedgerEcService.addOrUpdateByMchId(record); if (!success) { errMsg = "申请入网电子合同失败,数据保存失败"; shopMchEntryService.updateMerchEntryApprovalByMchId(shopMchEntry.getId(), "", CommonConstant.MCH_APPR_STA_NOPASS, errMsg); @@ -764,7 +778,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 新增数据 // 将 JSON 对象的键名转换为下划线命名 LklLedgerMember lklLedgerMemberNew = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMember.class); - if (!lklLedgerMemberService.saveOrUpdateByMerCupNo(lklLedgerMemberNew)) { + if (!lklLedgerMemberService.addOrUpdateByMerCupNo(lklLedgerMemberNew)) { return Pair.of(false, I18nUtil._("商户分账业务材料新增失败,待审核中!")); } @@ -789,19 +803,13 @@ public class LakalaApiServiceImpl implements LakalaApiService { log.debug("商户入网电子合同申请回调通知开始"); // 验签 - String authorization = request.getHeader("Authorization"); - String requestBody = LakalaUtil.getBody(request); - log.debug("商户入网电子合同申请回调返回requestbody 参数:{}\n authorization参数:{}\n", requestBody, authorization); - + Pair checkResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath); + if (!checkResult.getFirst()) { + return JSONUtil.createObj().set("code", "FAIL").set("retMsg", checkResult.getSecond()); + } + JSONObject paramsJSON = JSONUtil.parseObj(checkResult.getSecond()); String errMsg = "入网电子合同申请回调:"; - boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath); - if (!checkSuccess) { - log.error(errMsg + "验签失败"); - return JSONUtil.createObj().set("code", "FAIL").set("retMsg", "验签失败!"); - } - - JSONObject paramsJSON = JSONUtil.parseObj(requestBody); JSONObject respData = new JSONObject(); respData.put("code", "FAIL"); @@ -981,98 +989,42 @@ public class LakalaApiServiceImpl implements LakalaApiService { } /** - * 商户分账业务开通申请 + * 申请商户分账业务开通 + * 流程:提交分账业务申请(同时申请分账接收方)-等待拉卡拉审核通过-绑定商户和接收方关系-等待拉卡拉审核通过-完成整个流程 * - * @param paramsJSON + * @param paramsJSON {merCupNo 拉卡拉外部商户号} * @return */ @Override public CommonResult applyLedgerMer(JSONObject paramsJSON) { - Pair resultPair = innerApplyLedgerMer(paramsJSON.getStr("merCupNo")); + // 检查参数 + if (ObjectUtil.isEmpty(paramsJSON) || StrUtil.isBlank(paramsJSON.getStr("merCupNo"))) { + return CommonResult.failed("请填写商户号!"); + } + + String merCupNo = paramsJSON.getStr("merCupNo"); + + ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByMerCupNo(merCupNo); + if (shopMchEntry == null) { + return CommonResult.failed("无法获取入驻记录"); + } + + Long receiverCnt = lklLedgerReceiverService.countByCondition("", "", shopMchEntry.getDistributor_id()); + if (receiverCnt <= 0) { + // 1:新增一个接收方记录,起码要一个平台方,代理商根据入驻信息新增 + Boolean success = lklLedgerReceiverService.innerApplyLedgerReceiver(merCupNo, shopMchEntry.getDistributor_id()); + if (!success) { + return CommonResult.failed("申请分账接收方失败"); + } + } + + Pair resultPair = innerApplyLedgerMer(merCupNo); if (!resultPair.getFirst()) { return CommonResult.failed(resultPair.getSecond()); } return CommonResult.success(); -// log.debug("商户分账业务开通申请1开始"); -// -// // TODO 判断是否已经申请过? -// // 1. 配置初始化 -// initLKLSDK(); -// -// //2. 装配数据 -// V2MmsOpenApiLedgerApplyLedgerMerRequest req = new V2MmsOpenApiLedgerApplyLedgerMerRequest(); -// req.setVersion("2.0"); -// req.setOrderNo(StringUtils.genLklOrderNo(8));// 14位年月日时(24小时制)分秒+8位的随机数 -// req.setOrgCode(orgCode); -// req.setMerInnerNo(paramsJSON.getStr("merInnerNo"));// 从进件申请返回的商户号 -// req.setMerCupNo(paramsJSON.getStr("merCupNo")); // 从进件申请返回的商户号 -// req.setContactMobile(paramsJSON.getStr("contactMobile")); // 商户入驻注册的手机号 -// // 分账比例为了考虑低价订单的运费占比高,分账比例暂时定70%分账给商户,30%分账给平台 -// // new BigDecimal(paramsJSON.getStr("splitLowestRatio")) -// req.setSplitLowestRatio(new BigDecimal(splitLowestRatio)); -// String fileName = paramsJSON.getStr("splitEntrustFileName"); -// req.setSplitEntrustFileName(fileName); -// -// // 分账结算委托书文件上传到拉卡拉服务器 -// JSONObject fileUploadResp = uploadFile(req.getOrderNo(), "SPLIT_ENTRUST_FILE", StringUtils.getFileExt(fileName), UploadUtil.fileUrlToBase64(paramsJSON.getStr("splitEntrustFile"))); -// if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) { -// throw new ApiException(I18nUtil._("分账结算委托书上传失败!")); -// } -// -// String splitEntrustFilePath = fileUploadResp.getStr("attFileId"); -// req.setSplitEntrustFilePath(splitEntrustFilePath); //比如:G1/M00/06/64/CrFdEmBQc-aAGc_XAAAiIbS3WIE960.pdf; -// -// // 正式上线的时候,调整 api 地址 -// String domain = projectDomain; -// if (isProdProject()) { -// domain += "/api"; -// } -// // 给拉卡拉通知的回调地址 -// String retUrl = domain + "/mobile/shop/lakala/ledger/applyLedgerMerNotify"; -// req.setRetUrl(retUrl); -// -// paramsJSON.set("orderNo", req.getOrderNo()); -// paramsJSON.set("version", "2.0"); -// paramsJSON.set("ret_url", retUrl); -// paramsJSON.set("org_code", orgCode); -// paramsJSON.set("split_entrust_file_path", splitEntrustFilePath); -// -// String errMsg = "商户分账业务开通申请:"; -// -// try { -// //3. 发送请求 -// String responseStr = LKLSDK.httpPost(req); -// -// // 成功返回示例:{'retCode':'000000','retMsg':'申请已受理,请等待审核结果','respData':{'version':'1.0','orderNo':'KFPT20230223181025407788734','orgCode':'1','applyId':681201215598657536}} -// JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); -// if (StrUtil.isBlank(responseStr) || lakalaRespJSON == null) { -// return CommonResult.failed(I18nUtil._("申请开通分账失败!")); -// } -// -// if (!lakalaRespJSON.getStr("retCode").equals(lklSuccessCode)) { -// return CommonResult.failed(lakalaRespJSON.getStr("retMsg")); -// } -// -// paramsJSON.set("apply_id", lakalaRespJSON.getByPath("respData.applyId")); -// paramsJSON.set("remark", lakalaRespJSON.getStr("retMsg")); -// paramsJSON.set("audit_status_text", paramsJSON.get("remark")); -// -// paramsJSON.set("mch_id", paramsJSON.get("mchId")); -// -// // 新增数据 -// // 将 JSON 对象的键名转换为下划线命名 -// LklLedgerMember lklLedgerMember = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMember.class); -// lklLedgerMemberService.saveOrUpdateByMerCupNo(lklLedgerMember); -// -// return CommonResult.success(null, "提交成功,待审核中!"); -// } catch (SDKException e) { -// errMsg += e.getMessage(); -// log.error(errMsg); -// shopMchEntryService.updateMerchEntryApprovalByMchId(paramsJSON.getLong("mchId"), paramsJSON.getStr("mchMobile"), CommonConstant.MCH_APPR_STA_NOPASS, errMsg); -// throw new ApiException(I18nUtil._("商家申请开通分账出错!"), e); -// } } /** @@ -1160,7 +1112,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { } // 更新商家的hasApplySplit状态=1; - shopMchEntryService.updateMulStatus("", merCupNo, 0, 1, 0, 0); + shopMchEntryService.updateMulStatus("", merCupNo, 0, 1, 0, 0, 0); respData.put("code", "SUCCESS"); respData.put("message", "操作成功!"); @@ -1185,17 +1137,12 @@ public class LakalaApiServiceImpl implements LakalaApiService { log.debug("商户分账申请业务异步回调通知开始"); // 验签 - String authorization = request.getHeader("Authorization"); - String requestBody = LakalaUtil.getBody(request); - log.debug("商户分账申请业务异步回调返回requestbody 参数:{}\n authorization参数:{}\n", requestBody, authorization); - - - if (!LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath)) { - log.error("商户分账申请业务回调:验签失败"); - return JSONUtil.createObj().put("code", "FAIL").put("message", "验签失败!"); + Pair checkResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath); + if (!checkResult.getFirst()) { + return JSONUtil.createObj().set("code", "FAIL").set("retMsg", checkResult.getSecond()); } + JSONObject paramsJSON = JSONUtil.parseObj(checkResult.getSecond()); - JSONObject paramsJSON = JSONUtil.parseObj(requestBody); if (paramsJSON == null) { log.error("商户分账申请业务回调:请求参数为空"); return JSONUtil.createObj().put("code", "FAIL").put("message", "请求参数为空"); @@ -1262,7 +1209,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { } // 更新商家分账申请状态为已申请(hasApplySplit=1) - shopMchEntryService.updateMulStatus("", merCupNo, 0, 1, 0, 0); + shopMchEntryService.updateMulStatus("", merCupNo, 0, 1, 0, 0, 0); log.debug("商户分账申请业务回调:处理成功,applyId={}", applyId); return JSONUtil.createObj().put("code", "SUCCESS").put("message", "操作成功!"); @@ -1275,8 +1222,152 @@ public class LakalaApiServiceImpl implements LakalaApiService { * @param paramsJSON * @return */ + @Transactional @Override public CommonResult applyLedgerReceiver(JSONObject paramsJSON) { + // 前置检查:避免重复创建 + LklLedgerReceiver existingReceiver = lklLedgerReceiverService.getByCondition( + paramsJSON.getStr("licenseNo"), + paramsJSON.getStr("contactMobile"), + paramsJSON.getLong("platformId") + ); + if (existingReceiver != null) { + return CommonResult.success(existingReceiver, "该接收方已创建过!"); + } + + // 1. 配置初始化 + initLKLSDK(); + + // 2. 装配数据 + V2MmsOpenApiLedgerApplyLedgerReceiverRequest req = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest(); + String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数 + String mchMobile = paramsJSON.getStr("contactMobile"); + + // 基础参数设置 + req.setOrderNo(orderNo); + req.setOrgCode(orgCode); + req.setVersion("2.0"); + req.setReceiverName(paramsJSON.getStr("receiverName")); + req.setContactMobile(mchMobile); + req.setLicenseNo(paramsJSON.getStr("licenseNo")); + req.setLicenseName(paramsJSON.getStr("licenseName")); + req.setLegalPersonName(paramsJSON.getStr("legalPersonName")); + req.setLegalPersonCertificateType(paramsJSON.getStr("legalPersonCertificateType")); + req.setLegalPersonCertificateNo(paramsJSON.getStr("legalPersonCertificateNo")); + req.setAcctNo(paramsJSON.getStr("acctNo")); + req.setAcctName(paramsJSON.getStr("acctName")); + req.setAcctTypeCode(paramsJSON.getStr("acctTypeCode")); + req.setAcctCertificateType(paramsJSON.getStr("acctCertificateType")); + req.setAcctCertificateNo(paramsJSON.getStr("acctCertificateNo")); + req.setAcctOpenBankCode(paramsJSON.getStr("acctOpenBankCode")); + req.setAcctOpenBankName(paramsJSON.getStr("acctOpenBankName")); + req.setAcctClearBankCode(paramsJSON.getStr("acctClearBankCode")); + + // 附件处理(优化循环逻辑) + JSONArray attachList = paramsJSON.getJSONArray("attachList"); + if (attachList != null && !attachList.isEmpty()) { + List processedAttachments = new ArrayList<>(); + + for (Object obj : attachList) { + if (!(obj instanceof JSONObject)) continue; + + JSONObject attachJSON = (JSONObject) obj; + String fileName = attachJSON.getStr("attachName"); + String attachType = attachJSON.getStr("attachType"); + String fileUrl = attachJSON.getStr("attachStoreFile"); + + if (StrUtil.isBlank(fileName) || StrUtil.isBlank(attachType) || StrUtil.isBlank(fileUrl)) { + log.warn("附件参数不完整,跳过: {}", attachJSON); + continue; + } + + try { + String fileBase64 = UploadUtil.fileUrlToBase64(fileUrl); + JSONObject uploadResponse = uploadFile( + StringUtils.genLklOrderNo(8), + attachType, + StringUtils.getFileExt(fileName), + fileBase64 + ); + + if (uploadResponse == null || StrUtil.isBlank(uploadResponse.getStr("attFileId"))) { + log.error("附件上传失败: {} ({})", fileName, attachType); + continue; + } + + V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo info = + new V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo(); + info.setAttachName(fileName); + info.setAttachType(attachType); + info.setAttachStorePath(uploadResponse.getStr("attFileId")); + processedAttachments.add(info); + } catch (Exception e) { + log.error("处理附件时出错: {} ({})", fileName, attachType, e); + } + } + + if (!processedAttachments.isEmpty()) { + req.setAttachList(processedAttachments); + paramsJSON.set("attach_list", JSONUtil.toJsonStr(processedAttachments)); + } + } + + // 保存请求参数 + paramsJSON.set("orderNo", orderNo); + paramsJSON.set("version", "2.0"); + paramsJSON.set("org_code", orgCode); + + // 3. 发送请求并处理响应 + try { + String responseStr = LKLSDK.httpPost(req); + + if (StrUtil.isBlank(responseStr)) { + return CommonResult.failed(I18nUtil._("创建分账接收方响应数据无效!")); + } + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null || !lklSuccessCode.equals(lakalaRespJSON.getStr("retCode"))) { + String errorMsg = lakalaRespJSON != null + ? lakalaRespJSON.getStr("retMsg", "未知错误") + : "响应解析失败"; + return CommonResult.failed(I18nUtil._(errorMsg)); + } + + // 提取响应数据 + paramsJSON.set("receiver_no", lakalaRespJSON.getByPath("respData.receiverNo")); + paramsJSON.set("org_id", lakalaRespJSON.getByPath("respData.orgId")); + paramsJSON.set("org_name", lakalaRespJSON.getByPath("respData.orgName")); + + // 转换并保存数据 + LklLedgerReceiver receiver = JSONUtil.toBean( + StringUtils.convertCamelToSnake(paramsJSON.toString()), + LklLedgerReceiver.class + ); + + if (!lklLedgerReceiverService.addOrUpdateByReceiverNo(receiver)) { + log.error("接收方创建成功,但更新本地数据失败!"); + return CommonResult.failed(I18nUtil._("接收方创建成功,但更新本地数据失败!")); + } + + // 更新商户分账状态 + shopMchEntryService.updateMulStatus(mchMobile, "", 0, 0, 1, 0, 0); + return CommonResult.success(receiver, "创建接收方成功!"); + + } catch (Exception e) { + log.error("接收方创建失败:{}", e.getMessage(), e); + throw new ApiException(I18nUtil._("创建接收方失败: {}" + e.getMessage()), e); + } + } + + /** + * 分账接收方创建申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=380 + * + * @param paramsJSON + * @return + */ + // @Override + public CommonResult applyLedgerReceiverTemp(JSONObject paramsJSON) { // 判断分账接收方记录是否存在,存在就不再新建了 LklLedgerReceiver lklLedgerReceiverOld = lklLedgerReceiverService.getByCondition(paramsJSON.getStr("licenseNo"), paramsJSON.getStr("contactMobile"), paramsJSON.getLong("platformId")); if (lklLedgerReceiverOld != null) { @@ -1313,6 +1404,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { req.setAcctOpenBankName(paramsJSON.getStr("acctOpenBankName")); req.setAcctClearBankCode(paramsJSON.getStr("acctClearBankCode")); + if (paramsJSON.getJSONArray("attachList") != null && paramsJSON.getJSONArray("attachList").size() > 0) { List attachList = new ArrayList<>(); V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo attachInfo = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo(); @@ -1366,14 +1458,14 @@ public class LakalaApiServiceImpl implements LakalaApiService { LklLedgerReceiver lklLedgerReceiver = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerReceiver.class); // 新增或修改本地数据 - Boolean success = lklLedgerReceiverService.saveOrUpdateByReceiverNo(lklLedgerReceiver); + Boolean success = lklLedgerReceiverService.addOrUpdateByReceiverNo(lklLedgerReceiver); if (!success) { log.error("接收方创建成功,但更新本地数据失败!"); return CommonResult.failed(I18nUtil._("接收方创建成功,但更新本地数据失败!")); } // 更新商户分账多个状态 has_apply_receiver=1 - shopMchEntryService.updateMulStatus(mchMobile, "", 0, 0, 1, 0); + shopMchEntryService.updateMulStatus(mchMobile, "", 0, 0, 1, 0, 0); return CommonResult.success(lklLedgerReceiver, "创建接收方成功!"); } catch (Exception e) { @@ -1516,7 +1608,7 @@ public class LakalaApiServiceImpl implements LakalaApiService { // 新增数据 // 将 JSON 对象的键名转换为下划线命名 LklLedgerMerReceiverBind lklLedgerMerReceiverBind = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMerReceiverBind.class); - if (lklLedgerMerReceiverBindService.saveOrUpdateByMerCupNoReceiverNo(lklLedgerMerReceiverBind)) { + if (lklLedgerMerReceiverBindService.addOrUpdateByMerCupNoReceiverNo(lklLedgerMerReceiverBind)) { successCnt += 1; } // return Pair.of(true, "提交成功,待审核中!"); @@ -1623,12 +1715,13 @@ public class LakalaApiServiceImpl implements LakalaApiService { paramsJSON.set("apply_id", respJson.getByPath("respData.applyId")); paramsJSON.set("receiver_no", receiver.getReceiver_no()); paramsJSON.set("remark", respJson.getStr("retMsg")); + paramsJSON.set("platform_id", receiver.getPlatform_id()); // 转换JSON键名格式并保存 String snakeJson = StringUtils.convertCamelToSnake(paramsJSON.toString()); LklLedgerMerReceiverBind bindRecord = JSONUtil.toBean(snakeJson, LklLedgerMerReceiverBind.class); - if (lklLedgerMerReceiverBindService.saveOrUpdateByMerCupNoReceiverNo(bindRecord)) { + if (lklLedgerMerReceiverBindService.addOrUpdateByMerCupNoReceiverNo(bindRecord)) { successCount++; } else { log.warn("绑定记录保存失败:merCupNo={}, receiverNo={}", merCupNo, receiver.getReceiver_no()); @@ -1663,17 +1756,13 @@ public class LakalaApiServiceImpl implements LakalaApiService { log.debug("分账商家绑定接收方异步回调通知开始"); // 1. 验签处理(提前失败返回) - String authorization = request.getHeader("Authorization"); - String requestBody = LakalaUtil.getBody(request); - log.debug("回调参数:requestBody={}\nauthorization={}", requestBody, authorization); - - if (!LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath)) { - log.error("验签失败,拒绝处理回调"); - return JSONUtil.createObj().put("code", "FAIL").put("message", "验签失败"); + // 验签 + Pair checkResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath); + if (!checkResult.getFirst()) { + return JSONUtil.createObj().set("code", "FAIL").set("retMsg", checkResult.getSecond()); } + JSONObject paramsJSON = JSONUtil.parseObj(checkResult.getSecond()); - // 2. 参数解析与校验 - JSONObject paramsJSON = JSONUtil.parseObj(requestBody); if (paramsJSON == null || !paramsJSON.containsKey("respData")) { log.error("回调参数缺失respData字段"); return JSONUtil.createObj().put("code", "FAIL").put("message", "参数格式错误"); @@ -1716,48 +1805,76 @@ public class LakalaApiServiceImpl implements LakalaApiService { } // 6. 更新商家绑定状态 - shopMchEntryService.updateMulStatus("", merCupNo, 0, 0, 0, 1); + shopMchEntryService.updateMulStatus("", merCupNo, 0, 0, 0, 1, 0); log.debug("分账商家绑定接收方回调处理完成,merCupNo:{}", merCupNo); return JSONUtil.createObj().put("code", "SUCCESS").put("message", "操作成功"); } /** - * 银行卡信息查询 + * 查询银行卡Bin信息 * 参考:https://o.lakala.com/#/home/document/detail?id=179 * * @param bankCardNo 银行卡号 * @return bankCode, bankName, clearingBankCode */ @Override - public JSONObject getBankCardBin(String bankCardNo) { + public CommonResult getBankCardBin(String bankCardNo) { + // 参数校验 if (StrUtil.isBlank(bankCardNo)) { - return null; + return CommonResult.failed("银行卡号不能为空"); } - // 1. 配置初始化 - initLKLSDK(); + // 构建请求参数 + JSONObject reqData = new JSONObject() + .put("version", "1.0") + .put("orderNo", StringUtils.genLklOrderNo(8)) + .put("orgCode", orgCode) + .put("cardNo", bankCardNo); -// //2. 装配数据 -// V2MmsOpenApiBankCardBinRequest req = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest(); -// -// JSONObject formData = new JSONObject(); -// formData.putByPath("reqData.version", "2.0"); -// formData.putByPath("reqData.orderNo", StringUtils.genLklOrderNo(8)); -// formData.putByPath("reqData.orgCode", orgCode); -// formData.putByPath("reqData.cardNo", bankCardNo); -// -// String urlPath = "/api/v2/mms/openApi/cardBin"; -// ResponseEntity response = RestTemplateHttpUtil.sendPostFormDataBackEntity(buildLklServiceUrl(urlPath), header, formData, JSONObject.class); -// if (ObjectUtil.isEmpty(response) || response.getStatusCode() != HttpStatus.OK) { -// return null; -// } -// JSONObject result = response.getBody(); -// if (result == null) { -// return null; -// } + JSONObject reqBody = new JSONObject() + .put("timestamp", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss")) + .put("version", "1.0.0") + .put("reqData", reqData); - return null; + // 构建请求URL(优化逻辑) + String reqUrl = serverUrl + "/api/v2/mms/openApi/cardBin"; + + // 生成授权信息 + String privateKey = LakalaUtil.getResourceFile(priKeyPath, false, true); + String authorization = LakalaUtil.genAuthorization(privateKey, appId, serialNo, reqBody.toString()); + + // 设置请求头 + JSONObject header = new JSONObject().put("Authorization", authorization); + + log.debug("查询银行卡Bin信息请求参数:{}", JsonUtil.toJSONString(reqBody)); + + // 发送请求并获取响应 + ResponseEntity response = RestTemplateHttpUtil.sendPostBodyBackEntity( + reqUrl, header, reqBody, JSONObject.class); + + + // 响应校验(简化逻辑) + if (ObjectUtil.isEmpty(response) || !HttpStatus.OK.equals(response.getStatusCode())) { + return CommonResult.failed("查询银行卡Bin信息失败,无响应数据!"); + } + + JSONObject respBody = response.getBody(); + log.debug("查询银行卡Bin信息响应参数:{}", respBody); + if (respBody == null || !lklSuccessCode.equals(respBody.getStr("retCode"))) { + String errorMsg = respBody != null && StrUtil.isNotBlank(respBody.getStr("retMsg")) + ? respBody.getStr("msg") + : "返回状态有误"; + return CommonResult.failed("查询银行卡Bin信息失败," + errorMsg); + } + + // 获取并返回结果数据 + JSONObject respData = respBody.getJSONObject("respData"); + if (respData == null) { + return CommonResult.failed("查询银行卡Bin信息失败,返回数据有误"); + } + + return CommonResult.success(respData); } /** @@ -1776,8 +1893,8 @@ public class LakalaApiServiceImpl implements LakalaApiService { * } */ @Override - public JSONObject queryMchCanSplitAmt(String merchantNo, String logNo, String logDate) { - if (StrUtil.isBlank(merchantNo) || (StrUtil.isBlank(logNo) && StrUtil.isBlank(logDate))) { + public Pair queryMchCanSplitAmt(String merchantNo, String logNo, String logDate) { + if (StringUtils.isAnyBlank(merchantNo, logNo, logDate)) { return null; } @@ -1804,7 +1921,14 @@ public class LakalaApiServiceImpl implements LakalaApiService { return null; } - return (JSONObject) lklRespJSON.get("resp_data"); + JSONObject respData = (JSONObject) lklRespJSON.get("resp_data"); + if (respData == null + || StringUtils.isAnyBlank(respData.getStr("total_separate_amt"), respData.getStr("can_separate_amt"))) { + log.error(I18nUtil._("返回值有误!")); + return null; + } + + return Pair.of(respData.getStr("total_separate_amt"), respData.getStr("can_separate_amt")); } catch (SDKException e) { log.error("账户余额查询失败:", e); return null; @@ -1812,43 +1936,432 @@ public class LakalaApiServiceImpl implements LakalaApiService { } /** - * 拉卡拉订单分账,用户下单成功之后,进行分账 + * 拉卡拉订单分账,用户下单成功之后(大约15秒后),进行分账 * 说明:分账指令是异步处理模式,响应报文成功时,指令状态是”status”: “PROCESSING”,需要等待分账结果通知,或者主动发起查询,建议主动发起查询与分账指令动作之间间隔15秒以上。 * 参考:https://o.lakala.com/#/home/document/detail?id=389 * - * @param v3SacsSeparateRequest + * @param orderId 平台订单Id * @return */ @Override - public Pair innerDoOrderSeparate(V3SacsSeparateRequest v3SacsSeparateRequest) { - if (v3SacsSeparateRequest == null) { - return Pair.of(false, "分账参数不能为空"); + public Pair innerDoOrderSeparate(String orderId) { + // 输入参数校验 + if (StrUtil.isBlank(orderId)) { + return Pair.of(false, "订单号不能为空"); } - // 1. 配置初始化 - initLKLSDK(); + try { + // 初始化拉卡拉SDK + initLKLSDK(); + + // 查询订单信息 + List shopOrders = shopOrderLklService.selectByOrderId(orderId, ""); + if (CollectionUtil.isEmpty(shopOrders)) { + return Pair.of(false, "订单不存在"); + } + + int totalCount = shopOrders.size(); + int successCount = 0; + StringBuilder errorMessages = new StringBuilder(); + + // 遍历处理每个店铺订单的分账 + for (ShopOrderLkl order : shopOrders) { + String merchantNo = order.getMerchant_no(); + Integer paymentAmount = order.getTotal_amt(); + Integer shoppingFee = order.getShopping_fee(); + BigDecimal splitRatioMch = order.getSplit_ratio(); + + // 金额合法性校验 + if (paymentAmount <= 0 || (shoppingFee != null && paymentAmount <= shoppingFee)) { + String errorMsg = String.format("店铺[%s]订单金额异常或运费过高,跳过分账", order.getStore_id()); + log.error(errorMsg); + errorMessages.append(errorMsg).append("; "); + continue; + } + + // 获取分账接收方信息 + LklLedgerMerReceiverBind platform = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo); + List distributors = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo); + + if (platform == null) { + String errorMsg = String.format("店铺[%s]未绑定平台方接收账户,跳过分账", order.getStore_id()); + log.error(errorMsg); + errorMessages.append(errorMsg).append("; "); + continue; + } + + // 判断是否可以分账(商家比例非100%) + boolean canSplit = splitRatioMch != null && splitRatioMch.compareTo(new BigDecimal("100")) < 0; + if (!canSplit) { + log.warn("店铺[{}]分账比例为0,仅扣除运费", order.getStore_id()); + } + + // 构建分账请求对象 + V3SacsSeparateRequest request = new V3SacsSeparateRequest(); + request.setMerchantNo(merchantNo); + request.setLogNo(order.getLog_no()); + request.setLogDate(order.getLog_date()); + request.setOutSeparateNo(orderId); + request.setTotalAmt(paymentAmount.toString()); + request.setLklOrgNo(orgCode); + request.setCalType("0"); + request.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify"); + + // 构建分账接收方列表 + List recvDatas = new ArrayList<>(); + + // 1. 先处理运费分账(如果有运费) + if (shoppingFee != null && shoppingFee > 0) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(platform.getReceiver_no()); + receiver.setSeparateValue(shoppingFee.toString()); + recvDatas.add(receiver); + } + + // 2. 再处理剩余金额的分账(如果可以分账) + if (canSplit) { + // 计算实际可分账金额(扣除运费后) + Integer splitAmount = paymentAmount - shoppingFee; + // 计算平台+代理商的总比例 + BigDecimal splitRatioNoMch = new BigDecimal("100").subtract(splitRatioMch); + + if (!CollectionUtils.isEmpty(distributors) && splitRatioNoMch.compareTo(BigDecimal.ONE) > 0) { + // 平台+代理商分账模式 + // 平台收取1%手续费 + BigDecimal platformValue = CommonUtil.DecimalRoundHalfDown(new BigDecimal(splitAmount).multiply(new BigDecimal("0.01"))); + if (platformValue.compareTo(BigDecimal.ZERO) > 0) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(platform.getReceiver_no()); + receiver.setSeparateValue(platformValue.toString()); + recvDatas.add(receiver); + } + + // 代理商分账(扣除平台1%后的剩余比例) + BigDecimal distributorRatio = splitRatioNoMch.subtract(new BigDecimal("1")).divide(new BigDecimal(100)); + BigDecimal distributorValue = CommonUtil.DecimalRoundHalfDown(new BigDecimal(splitAmount).multiply(distributorRatio)); + if (distributorValue.compareTo(BigDecimal.ZERO) > 0 && !distributors.isEmpty()) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(distributors.get(0).getReceiver_no()); + receiver.setSeparateValue(distributorValue.toString()); + recvDatas.add(receiver); + } + } else { + // 仅平台分账模式 + BigDecimal platformRatio = splitRatioNoMch.divide(new BigDecimal(100)); + BigDecimal platformValue = new BigDecimal(splitAmount).multiply(platformRatio); + if (platformValue.compareTo(BigDecimal.ZERO) > 0) { + V3SacsSeparateRecvDatas receiver = new V3SacsSeparateRecvDatas(); + receiver.setRecvNo(platform.getReceiver_no()); + receiver.setSeparateValue(platformValue.toString()); + recvDatas.add(receiver); + } + } + } + + // 设置分账接收方列表 + request.setRecvDatas(recvDatas); + log.debug("分账请求参数: {}", JSONUtil.toJsonStr(request)); + + // 发送分账请求 + String response = LKLSDK.httpPost(request); + if (StrUtil.isBlank(response)) { + errorMessages.append("拉卡拉无响应; "); + continue; + } + + log.debug("分账响应结果: {}", response); + + // 解析响应结果 + JSONObject respJson = JSONUtil.parseObj(response); + if (respJson == null || !lklSacsSuccessCode.equals(respJson.getStr("code")) || respJson.getJSONObject("resp_data") == null) { + errorMessages.append("拉卡拉返回格式异常; "); + log.error("拉卡拉分账失败:{}", response); + continue; + } + + // 保存分账记录 + JSONObject respData = respJson.getJSONObject("resp_data"); + LklOrderSeparate record = new LklOrderSeparate(); + record.setSeparate_no(respData.getStr("separate_no")); + record.setOut_separate_no(request.getOutSeparateNo()); + record.setMerchant_no(merchantNo); + record.setLog_no(request.getLogNo()); + record.setLog_date(request.getLogDate()); + record.setOrder_id(order.getOrder_id()); + record.setTotal_amt(request.getTotalAmt()); + record.setNotify_url(request.getNotifyUrl()); + record.setLkl_org_no(request.getLklOrgNo()); + record.setRecv_datas(JSONUtil.toJsonStr(request.getRecvDatas())); + record.setStatus(respData.getStr("status")); + + try { + lklOrderSeparateService.addOrUpdateByReceiverNo(record); + successCount++; + } catch (Exception e) { + log.error("保存分账记录失败: {}", e.getMessage(), e); + errorMessages.append("保存分账记录失败; "); + } + } + + // 返回最终处理结果 + if (successCount == 0) { + return Pair.of(false, "分账全部失败: " + errorMessages); + } else if (successCount < totalCount) { + return Pair.of(true, "部分分账成功,处理中: " + errorMessages); + } else { + return Pair.of(true, "全部订单分账已提交处理"); + } + } catch (Exception e) { + log.error("分账系统异常: {}", e.getMessage(), e); + return Pair.of(false, "系统异常,请稍后重试"); + } + } + + public Pair innerDoOrderSeparateTemp(String orderId) { + if (StrUtil.isBlank(orderId)) { + return Pair.of(false, "订单号不能为空"); + } try { - log.debug("分账执行请求参数:{}", JSONUtil.toJsonStr(v3SacsSeparateRequest)); - //3. 发送请求 - String responseStr = LKLSDK.httpPost(v3SacsSeparateRequest); - if (StrUtil.isBlank(responseStr)) { - return Pair.of(false, "服务器无返回值!"); + // 1. 配置初始化 + initLKLSDK(); + + // 获取订单信息、店铺信息,商户信息、分账信息 + List shopOrderLklList = shopOrderLklService.selectByOrderId(orderId, ""); + if (CollectionUtil.isEmpty(shopOrderLklList)) { + return Pair.of(false, "订单不存在"); } - JSONObject lklRespJSON = JSONUtil.parseObj(responseStr); - if (lklRespJSON == null || !lklSacsSuccessCode.equals(lklRespJSON.getStr("code")) || lklRespJSON.get("resp_data") == null) { - log.error("返回值有误!"); - return Pair.of(false, "返回值有误!"); + int successCnt = 0; + // 一个订单里包含了多个商家的时候,需要处理 + for (ShopOrderLkl shopOrderLkl : shopOrderLklList) { + String merchantNo = shopOrderLkl.getMerchant_no(); + + // 分账总金额 + Integer paymentAmount = shopOrderLkl.getTotal_amt(); + Integer shoppingFee = shopOrderLkl.getShopping_fee(); + BigDecimal splitRatioMch = shopOrderLkl.getSplit_ratio(); + // 应付款金额大于零,并且大于运费。 + if (paymentAmount <= 0 + || (shoppingFee != null && paymentAmount - shoppingFee <= 0)) { + log.error("店铺{}订单{}金额有误或运费高于分账金额,不会进行分账", shopOrderLkl.getStore_id(), orderId); + continue; + } + + // 平台和代理商的分成比例 + BigDecimal splitRatioNoMch = new BigDecimal("100").subtract(shopOrderLkl.getSplit_ratio()); + LklLedgerMerReceiverBind platform = lklLedgerMerReceiverBindService.getPlatformByMerCupNo(merchantNo); + List distributorList = lklLedgerMerReceiverBindService.selectDistributorByMerCupNo(merchantNo); + if (platform == null) { + log.error("店铺{}订单{}未绑定平台方,不会进行分账", shopOrderLkl.getStore_id(), orderId); + continue; + } + + boolean canSplit = true; // 能分账并扣运费 + if (splitRatioMch == null || splitRatioMch.compareTo(new BigDecimal("100")) >= 0) { + log.warn("店铺{}分账比例为0,只扣运费,不会进行分账", shopOrderLkl.getStore_id()); + canSplit = false; //不分账,只扣运费 + } + + V3SacsSeparateRequest req = new V3SacsSeparateRequest(); + req.setMerchantNo(merchantNo); // 拉卡拉商户号 + req.setLogNo(shopOrderLkl.getLog_no()); + req.setLogDate(shopOrderLkl.getLog_date()); + req.setOutSeparateNo(orderId); // 暂时传入平台订单Id + req.setTotalAmt(shopOrderLkl.getTotal_amt().toString()); + req.setLklOrgNo(orgCode); + req.setCalType("0"); // 0- 按照指定金额,1- 按照指定比例。默认 0 + req.setNotifyUrl(projectDomain + "/api/mobile/shop/lakala/sacs/separateNotify"); + + List recvDatas = new ArrayList<>(); + + // 平台先代扣掉运费 + if (shoppingFee != null && shoppingFee > 0) { + V3SacsSeparateRecvDatas recvData = new V3SacsSeparateRecvDatas(); + recvData.setRecvNo(platform.getReceiver_no()); + recvData.setSeparateValue(shoppingFee.toString()); // 分账金额,单位分,分账金额不能大于订单金额,分账金额不能小于0 + recvDatas.add(recvData); + } + + if (canSplit) { //扣完运费后还能能分账 + + // 应付款扣除运费后,得出实际分账的金额,单位:分 + Integer splitAmount = paymentAmount - shoppingFee; + if (!CollectionUtils.isEmpty(distributorList) + && (splitRatioMch != null && splitRatioNoMch.compareTo(BigDecimal.ONE) > 0)) { // 有平台和代理商,并且分账比例大于1的情况 + + // 平台方分账 + BigDecimal platformSeparateValue = CommonUtil.DecimalRoundHalfDown(new BigDecimal(splitAmount).multiply(new BigDecimal("0.01"))); // 平台收取1%手续费 + if (platformSeparateValue.compareTo(BigDecimal.ZERO) > 0) { + V3SacsSeparateRecvDatas recvData = new V3SacsSeparateRecvDatas(); + recvData.setRecvNo(platform.getReceiver_no()); + recvData.setSeparateValue(platformSeparateValue.toString()); // 分账金额,单位分,分账金额不能大于订单金额,分账金额不能小于0 + recvDatas.add(recvData); + } + + // TODO: 一级以上的代理商以后做处理 + // 一级代理方分账, 分账比例扣掉1%的平台方费,其余都是代理商的 + BigDecimal splitRatioDistributor = splitRatioNoMch.subtract(new BigDecimal("1")).divide(new BigDecimal(100)); + BigDecimal distributorSeparateValue = CommonUtil.DecimalRoundHalfDown(new BigDecimal(splitAmount).multiply(splitRatioDistributor)); + if (distributorSeparateValue.compareTo(BigDecimal.ZERO) > 0) { + V3SacsSeparateRecvDatas recvData2 = new V3SacsSeparateRecvDatas(); + recvData2.setRecvNo(distributorList.get(0).getReceiver_no()); + recvData2.setSeparateValue(distributorSeparateValue.toString()); // 分账金额,单位分,分账金额不能大于订单金额,分账金额不能小于0 + recvDatas.add(recvData2); + } + } else { + // 仅平台方一方分账 + BigDecimal splitRatioPlatform = CommonUtil.DecimalRoundHalfDown(splitRatioNoMch.divide(new BigDecimal(100))); + BigDecimal separateValue = new BigDecimal(splitAmount).multiply(splitRatioPlatform); + if (separateValue.compareTo(BigDecimal.ZERO) > 0) { + V3SacsSeparateRecvDatas recvData = new V3SacsSeparateRecvDatas(); + recvData.setRecvNo(platform.getReceiver_no()); + recvData.setSeparateValue(separateValue.toString()); // 分账金额,单位分,分账金额不能大于订单金额,分账金额不能小于0 + recvDatas.add(recvData); + } + } + } + + req.setRecvDatas(recvDatas); + + + log.debug("分账执行请求参数:{}", JSONUtil.toJsonStr(req)); + + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + if (StrUtil.isBlank(responseStr)) { + log.error("服务器无返回值!"); + continue; + } + log.debug("分账执行响应参数:{}", responseStr); + + JSONObject lklRespJSON = JSONUtil.parseObj(responseStr); + if (lklRespJSON == null + || !lklSacsSuccessCode.equals(lklRespJSON.getStr("code")) + || lklRespJSON.getJSONObject("resp_data") == null) { + log.error("分账返回值有误!"); + continue; + } + + JSONObject respData = lklRespJSON.getJSONObject("resp_data"); + + // 4. 处理返回结果,等待异步通知 + LklOrderSeparate record = new LklOrderSeparate(); + record.setSeparate_no(respData.getStr("separate_no")); // 必填参数 + record.setOut_separate_no(req.getOutSeparateNo());// 必填参数 + record.setMerchant_no(merchantNo); + record.setLog_no(shopOrderLkl.getLog_no()); + record.setLog_date(shopOrderLkl.getLog_date()); + record.setOrder_id(shopOrderLkl.getOrder_id()); + record.setTotal_amt(shopOrderLkl.getTotal_amt().toString()); + record.setNotify_url(req.getNotifyUrl()); + record.setLkl_org_no(req.getLklOrgNo()); + record.setRecv_datas(JSONUtil.toJsonStr(req.getRecvDatas())); + record.setStatus(respData.getStr("status")); + lklOrderSeparateService.addOrUpdateByReceiverNo(record); + + successCnt++; } - return Pair.of(true, "分账成功执行,处理中"); + if (successCnt <= 0) { + return Pair.of(false, "分账失败"); + } else if (successCnt != shopOrderLklList.size()) { + return Pair.of(true, "部分订单分账执行成功,处理中"); + } + + return Pair.of(true, "全部订单分账执行成功,处理中"); } catch (SDKException e) { - log.error("账户余额查询失败:", e); + log.error("分账发生错误:", e); return Pair.of(false, "分账发生错误"); } } + /** + * 分账结果通知 + * 参考:https://o.lakala.com/#/home/document/detail?id=393 + * + * @param request + * @return + */ + @Override + public JSONObject sacsSeparateNotify(HttpServletRequest request) { + log.debug("分账结果通知异步回调开始"); + + // 1. 验签处理 + Pair signCheckResult = LakalaUtil.chkLklApiNotifySign(request, lklNotifyCerPath); + if (!signCheckResult.getFirst()) { + log.warn("分账通知验签失败: {}", signCheckResult.getSecond()); + return JSONUtil.createObj() + .set("code", "FAIL") + .set("retMsg", signCheckResult.getSecond()); + } + + // 2. 解析请求参数 + JSONObject paramsJson = JSONUtil.parseObj(signCheckResult.getSecond()); + if (paramsJson == null) { + log.error("分账通知参数解析失败: 请求参数为空"); + return JSONUtil.createObj() + .put("code", "FAIL") + .put("message", "请求参数为空"); + } + + // 3. 提取关键参数并校验 + String logNo = paramsJson.getStr("log_no"); + String separateNo = paramsJson.getStr("separate_no"); + String outSeparateNo = paramsJson.getStr("out_separate_no"); + String status = paramsJson.getStr("status"); + String finalStatus = paramsJson.getStr("final_status"); + + List missingParams = new ArrayList<>(); + if (StrUtil.isBlank(outSeparateNo)) missingParams.add("outSeparateNo"); + if (StrUtil.isBlank(separateNo)) missingParams.add("separateNo"); + if (StrUtil.isBlank(status)) missingParams.add("status"); + + if (!missingParams.isEmpty()) { + String errorMsg = "分账通知缺少必要参数: " + String.join(", ", missingParams); + log.error(errorMsg + ", 参数详情: {}", paramsJson); + return JSONUtil.createObj() + .put("code", "FAIL") + .put("message", errorMsg); + } + + // 4. 构建分账记录对象 + LklOrderSeparate record = new LklOrderSeparate(); + record.setLog_no(logNo); + record.setSeparate_no(separateNo); + record.setOut_separate_no(outSeparateNo); + record.setStatus(status); + record.setFinal_status(finalStatus); + record.setCal_type(paramsJson.getStr("cal_type")); + record.setSeparate_type(paramsJson.getStr("separate_type")); + record.setSeparate_date(paramsJson.getStr("separate_date")); + + // 处理detail_datas(避免空指针) + JSONArray detailDatas = paramsJson.getJSONArray("detail_datas"); + record.setDetail_datas(detailDatas != null ? detailDatas.toString() : "[]"); + + // 5. 持久化处理 + try { + boolean updateSuccess = lklOrderSeparateService.addOrUpdateByReceiverNo(record); + if (!updateSuccess) { + log.error("分账记录更新失败, separateNo={}", separateNo); + return JSONUtil.createObj() + .put("code", "FAIL") + .put("message", "数据更新失败"); + } + + log.debug("分账通知处理成功, separateNo={}, status={}", separateNo, status); + return JSONUtil.createObj() + .put("code", "SUCCESS") + .put("message", "操作成功"); + } catch (Exception e) { + log.error("分账通知数据处理异常, separateNo={}: {}", separateNo, e.getMessage(), e); + return JSONUtil.createObj() + .put("code", "FAIL") + .put("message", "系统处理异常"); + } + } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerEcServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerEcServiceImpl.java index 2cf0f6ca..eeb2003d 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerEcServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerEcServiceImpl.java @@ -47,7 +47,7 @@ public class LklLedgerEcServiceImpl extends BaseServiceImpl selectListByMerCupNo(String merCupNo, Boolean isPlatform) { + if (StrUtil.isBlank(merCupNo)) { + return null; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("mer_cup_no", merCupNo) + .eq("audit_status", CommonConstant.Enable); + if (isPlatform != null && isPlatform) { + queryWrapper.eq("platform_id", 0); + } else if (isPlatform != null && !isPlatform) { + queryWrapper.gt("platform_id", 0); + } + + queryWrapper.orderByAsc("id"); + + return list(queryWrapper); + } + + /** + * 根据商户编号查询一条平台绑定记录 + * + * @param merCupNo + * @return + */ + @Override + public LklLedgerMerReceiverBind getPlatformByMerCupNo(String merCupNo) { + List list = selectListByMerCupNo(merCupNo, true); + if (CollectionUtil.isEmpty(list)) { + return null; + } + + return list.get(0); + } + + /** + * 根据商户编号查询一条代理商绑定记录 + * + * @param merCupNo + * @return + */ + @Override + public List selectDistributorByMerCupNo(String merCupNo) { + List list = selectListByMerCupNo(merCupNo, false); + if (CollectionUtil.isEmpty(list)) { + return null; + } + + return list; + } + /** * 更新审核结果 * @@ -111,7 +177,7 @@ public class LklLedgerMerReceiverBindServiceImpl extends BaseServiceImpl 0; if (success) { // 更新多个状态 - shopMchEntryService.updateMulStatus("", merCupNo, 0, 0, 1, 0); + shopMchEntryService.updateMulStatus("", merCupNo, 0, 0, 1, 0, 0); } return success; @@ -311,4 +312,31 @@ public class LklLedgerReceiverServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); + if (StrUtil.isNotBlank(LicenseNo)) { + queryWrapper.eq("license_no", LicenseNo); + } + if (StrUtil.isNotBlank(ContactMobile)) { + queryWrapper.eq("contact_mobile", ContactMobile); + } + + if (ObjectUtil.isNotEmpty(platformId) && platformId > 0) { + queryWrapper.eq("platform_id", platformId); + } + + queryWrapper.eq("status", CommonConstant.Enable); + + return count(queryWrapper); + } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java new file mode 100644 index 00000000..d285873e --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. + * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. + * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. + * Vestibulum commodo. Ut rhoncus gravida arcu. + */ + +package com.suisung.mall.shop.lakala.service.impl; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.suisung.mall.common.modules.lakala.LklOrderSeparate; +import com.suisung.mall.core.web.service.impl.BaseServiceImpl; +import com.suisung.mall.shop.lakala.mapper.LklOrderSeparateMapper; +import com.suisung.mall.shop.lakala.service.LklOrderSeparateService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class LklOrderSeparateServiceImpl extends BaseServiceImpl implements LklOrderSeparateService { + + /** + * 新增或更新记录(根据out_separate_no 和 separate_no 更新记录) + * + * @param record + * @return + */ + @Override + public Boolean addOrUpdateByReceiverNo(LklOrderSeparate record) { + if (record == null || (StrUtil.isBlank(record.getOut_separate_no()) && StrUtil.isBlank(record.getSeparate_no()))) { + return false; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("separate_no", record.getSeparate_no()).eq("out_separate_no", record.getOut_separate_no()); + if (StrUtil.isNotBlank(record.getSeparate_no())) { + queryWrapper.eq("log_no", record.getLog_no()); + } + LklOrderSeparate existsRecord = getOne(queryWrapper); + if (existsRecord != null && existsRecord.getId() > 0) { + // 更新记录 + record.setId(existsRecord.getId()); + return updateById(record); + } + + return add(record); + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java index 266eed9b..ba3c3dc0 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklTkServiceImpl.java @@ -510,8 +510,9 @@ public class LklTkServiceImpl { } JSONObject respBody = response.getBody(); + logger.debug("进件返回结果:{}", respBody); if (response.getStatusCode() != HttpStatus.OK && ObjectUtil.isNotEmpty(respBody)) { - String errMsg = respBody.getStr("message") == null ? "返回状态有误" : respBody.getStr("message"); + String errMsg = respBody.getStr("message") == null ? "未知错误" : respBody.getStr("message"); return Pair.of(false, "进件失败:" + errMsg); } @@ -544,6 +545,7 @@ public class LklTkServiceImpl { // 解密请求参数 String requestBody = LakalaUtil.getBody(request); logger.debug("拉卡拉进件异步通知返回参数:{}", requestBody); + if (StrUtil.isBlank(requestBody)) { return new JSONObject().set("code", "400").set("message", "返回参数为空"); } @@ -562,39 +564,44 @@ public class LklTkServiceImpl { String notifyPubKey = LakalaUtil.getResourceFile(notifyPubKeyPath, false, false); String data = LakalaUtil.decryptNotifyData(notifyPubKey, srcData); if (StrUtil.isBlank(data)) { - return new JSONObject().set("code", "400").set("message", "密文解密出错!"); + return new JSONObject().set("code", "400").set("message", "数据解密出错!"); } logger.debug("拉卡拉进件异步通知data解密成功,开始处理逻辑"); // 逻辑处理 JSONObject dataJSON = JSONUtil.parseObj(data); - if (dataJSON.isEmpty() || StrUtil.isBlank(dataJSON.getStr("externalCustomerNo")) || StrUtil.isBlank(dataJSON.getStr("status"))) { + String auditStatus = dataJSON.getStr("status"); + String merCupNo = dataJSON.getStr("externalCustomerNo"); //拉卡拉外部商户号 + String merInnerNo = dataJSON.getStr("customerNo"); //拉卡拉内部商户号 + String termNos = dataJSON.getStr("termNos"); //拉卡拉分配的业务终端号 + + // 合并参数校验,减少嵌套 + if (dataJSON.isEmpty() || + StrUtil.isBlank(auditStatus) || + StrUtil.isBlank(merCupNo) || + StrUtil.isBlank(merInnerNo)) { return new JSONObject().set("code", "500").set("message", "参数解析出错"); } - String auditStatus = dataJSON.getStr("status"); + // 校验审核状态 if (!"SUCCESS".equals(auditStatus) && !"REVIEW_PASS".equals(auditStatus)) { + logger.debug("返回的审核状态:{}", auditStatus); return new JSONObject().set("code", "FAIL").set("message", "返回审核状态有误"); } // RMK 拉卡拉进价提交成功,边处理周边的数据,边等待审核异步通知 // 给商家入驻表增加拉卡拉的商户号和拉卡拉返回的数据 - String merCupNo = dataJSON.getStr("externalCustomerNo"); //拉卡拉外部商户号 - String merInnerNo = dataJSON.getStr("customerNo"); //拉卡拉内部商户号 - String termNos = dataJSON.getStr("termNos"); //拉卡拉分配的业务终端号 - if (StrUtil.isBlank(merInnerNo) || StrUtil.isBlank(merCupNo)) { - return new JSONObject().set("code", "500").set("message", "返回数据有误"); - } - ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByMerInnerNo(merInnerNo); if (ObjectUtil.isEmpty(shopMchEntry)) { - logger.error("拉卡拉进件异步通知:返回的内部商户号:{} 入驻信息不存在!", merInnerNo); - return new JSONObject().put("code", "500").put("message", "内部商户号:" + merInnerNo + " 入驻信息不存在"); + logger.error("拉卡拉进件异步通知:{}内部商户号入驻信息不存在!", merInnerNo); + return new JSONObject().put("code", "500").put("message", merInnerNo + "内部商户号入驻信息不存在"); } - Boolean success = shopMchEntryService.updateMerchEntryLklAuditStatusByLklMerCupNo(merInnerNo, merCupNo, termNos, CommonConstant.Enable, null, data); + Boolean success = shopMchEntryService.updateMerchEntryLklAuditStatusByLklMerCupNo( + merInnerNo, merCupNo, termNos, CommonConstant.Enable, null, data); + if (!success) { return new JSONObject().set("code", "500").set("message", "更新商户号失败"); // throw new ApiException("更新商户号失败"); @@ -618,11 +625,13 @@ public class LklTkServiceImpl { if (success && StrUtil.isNotBlank(shopMchEntry.getContract_download_url())) { // TODO 新建一个正式的已审核通过的店铺, 新建之前判断是否已经新建过了? - // 新建一个正式的已审核通过的店铺 -// ShopMchEntry shopEntry = shopMchEntryService.getShopMerchEntryByMerCupNo(merCupNo); + // 新建一个正式的已审核通过的店铺,不要抛异常,使用补偿机制,可以独立初始化店铺 + ShopMchEntry shopEntry = shopMchEntryService.getShopMerchEntryByMerCupNo(merCupNo); // if (shopEntry != null && !CommonConstant.Enable.equals(shopEntry.getStore_status())) { // String mchMobile = shopEntry.getLogin_mobile(); -// Pair retPair = shopStoreBaseService.merchEntryInfo2StoreInfo(mchMobile); +// // 禁止往外抛异常,如果失败使用补偿机制,创建新店 +// Pair retPair = shopStoreBaseService.merchEntryInfo2StoreInfo(mchMobile, false); +// // if (retPair.getFirst() > 0) { // // 2025-05-17暂停e签宝电子合同生成流程 // // 更改合同记录表的店铺id @@ -631,8 +640,10 @@ public class LklTkServiceImpl { // // esignContractFillingFileService.updateContractFillingStoreId(mchMobile, retPair.getFirst()); // // 店铺创建状态已完成 // shopMchEntryService.updateMerchEntryStoreStatus(mchMobile, CommonConstant.Enable); +// logger.info("商家进件:初始化店铺成功!"); // } else { -// throw new ApiException("商家进件:初始化店铺失败!"); +// logger.error("商家进件:初始化店铺失败!"); +// // throw new ApiException("商家进件:初始化店铺失败!"); // } // } @@ -643,6 +654,7 @@ public class LklTkServiceImpl { // 2:新增一个接收方记录,起码要一个平台方,代理商根据入驻信息新增 Boolean genSuccess = lklLedgerReceiverService.innerApplyLedgerReceiver(merCupNo, shopMchEntry.getDistributor_id()); + // 统一处理分账申请结果 if (retPair.getFirst() && genSuccess) { return new JSONObject().put("code", "200").put("message", "处理成功"); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java index 1f9e943d..7956777f 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java @@ -18,6 +18,7 @@ import com.suisung.mall.common.utils.StringUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.springframework.core.io.ClassPathResource; +import org.springframework.data.util.Pair; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -403,4 +404,26 @@ public class LakalaUtil { return null; } + + /** + * 拉卡拉异步回调通知,签名验证公共方法 + * + * @param request + * @param lklNotifyCerPath + * @return 验签成功,返回解密后的json字符串,验签失败,返回错误信息 + */ + public static Pair chkLklApiNotifySign(HttpServletRequest request, String lklNotifyCerPath) { + // 验签 + String authorization = request.getHeader("Authorization"); + String requestBody = getBody(request); + log.debug("{} 回调通知 requestbody 参数:{}\n authorization参数:{}\n", request.getRequestURL(), requestBody, authorization); + + boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath); + if (!checkSuccess) { + log.error("{} 回调通知验签失败", request.getRequestURL()); + return Pair.of(false, "验签失败!"); + } + + return Pair.of(true, requestBody); + } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/mapper/ShopOrderLklMapper.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/mapper/ShopOrderLklMapper.java new file mode 100644 index 00000000..6d954634 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/mapper/ShopOrderLklMapper.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. + * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. + * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. + * Vestibulum commodo. Ut rhoncus gravida arcu. + */ + +package com.suisung.mall.shop.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.suisung.mall.common.modules.order.ShopOrderLkl; + +public interface ShopOrderLklMapper extends BaseMapper { +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderLklService.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderLklService.java new file mode 100644 index 00000000..1e4c12ef --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/ShopOrderLklService.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025. Lorem ipsum dolor sit amet, consectetur adipiscing elit. + * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan. + * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna. + * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus. + * Vestibulum commodo. Ut rhoncus gravida arcu. + */ + +package com.suisung.mall.shop.order.service; + +import com.suisung.mall.common.modules.order.ShopOrderLkl; +import com.suisung.mall.core.web.service.IBaseService; + +import java.util.List; + +public interface ShopOrderLklService extends IBaseService { + + /** + * 添加或更新记录 + * + * @param record + * @return + */ + Boolean addOrUpdateByStoreOrder(ShopOrderLkl record); + + /** + * 根据订单编号查询多条记录(因为一个订单里可能有多个店铺的商品) + * + * @param orderId + * @param storeId + * @return + */ + List selectByOrderId(String orderId, String storeId); +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java index cb35487f..ca31d97f 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/order/service/impl/ShopOrderBaseServiceImpl.java @@ -2743,7 +2743,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl order_rows = null; + List order_rows; List review_id_row = new ArrayList<>(); // 检测数据是否合法,过滤允许修改的数据 if (CollUtil.isNotEmpty(order_ids)) { @@ -2821,7 +2821,7 @@ public class ShopOrderBaseServiceImpl extends BaseServiceImpl implements ShopOrderLklService { + + @Override + public Boolean addOrUpdateByStoreOrder(ShopOrderLkl record) { + if (record == null + || StringUtils.isAnyBlank(record.getOrder_id(), record.getStore_id(), record.getLog_no())) { + return false; + } + + List existsRecordList = selectByOrderId(record.getOrder_id(), record.getStore_id()); + if (CollectionUtil.isNotEmpty(existsRecordList) + && existsRecordList.get(0) != null + && existsRecordList.get(0).getId() > 0) { + // 更新记录 + record.setId(existsRecordList.get(0).getId()); + return updateById(record); + } + + return add(record); + } + + /** + * 根据订单编号查询多条记录(因为一个订单里可能有多个店铺的商品) + * + * @param orderId + * @return + */ + @Override + public List selectByOrderId(String orderId, String storeId) { + if (StringUtils.isBlank(orderId)) { + return null; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_id", orderId).orderByAsc("id"); + if (StrUtil.isNotBlank(storeId)) { + queryWrapper.eq("store_id", storeId); + } + + return list(queryWrapper); + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/controller/admin/ShopStoreBaseController.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/controller/admin/ShopStoreBaseController.java index 44393d0d..7119d95a 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/controller/admin/ShopStoreBaseController.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/controller/admin/ShopStoreBaseController.java @@ -254,7 +254,7 @@ public class ShopStoreBaseController extends BaseControllerImpl { @ApiOperation(value = "商家入驻资料转成店铺", notes = "商家入驻资料转成店铺") @RequestMapping(value = "/mchinfo/to/storeinfo", method = RequestMethod.POST) public CommonResult merchEntryInfo2StoreInfo(@RequestBody JSONObject jsonParam) { - Pair result = shopStoreBaseService.merchEntryInfo2StoreInfo(jsonParam.getStr("mchMobile")); + Pair result = shopStoreBaseService.merchEntryInfo2StoreInfo(jsonParam.getStr("mchMobile"), true); if (result.getFirst().equals(0)) { return CommonResult.failed(result.getSecond()); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java index fb83e784..29127239 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopMchEntryService.java @@ -213,8 +213,9 @@ public interface ShopMchEntryService { * @param hasApplySplit * @param hasApplyReceiver * @param hasBindReceiver + * @param hasApplyMer * @return */ - Boolean updateMulStatus(String mchMobile, String merCupNo, Integer hasEcSigned, Integer hasApplySplit, Integer hasApplyReceiver, Integer hasBindReceiver); + Boolean updateMulStatus(String mchMobile, String merCupNo, Integer hasEcSigned, Integer hasApplySplit, Integer hasApplyReceiver, Integer hasBindReceiver, Integer hasApplyMer); } \ No newline at end of file diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreBaseService.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreBaseService.java index 47ac019b..a62579ff 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreBaseService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/ShopStoreBaseService.java @@ -141,9 +141,10 @@ public interface ShopStoreBaseService extends IBaseService { * (重要)入驻审批通过并且合同盖章完结之后,把商家入驻信息转换成店铺信息,正式生成店铺所需的数据 * * @param mchMobile + * @param allowThrown 是否允许抛出异常 * @return 店铺Id */ - Pair merchEntryInfo2StoreInfo(String mchMobile); + Pair merchEntryInfo2StoreInfo(String mchMobile, Boolean allowThrown); /** * 根据店铺名称判断店铺是否存在 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java index 3639e6bf..472384b9 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/store/service/impl/ShopMchEntryServiceImpl.java @@ -247,7 +247,7 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl tmplArgs = new HashMap<>(1); tmplArgs.put("name", record.getBiz_license_company()); // 商家公司名称 // 所有店铺管理员的发送邮件, 提醒商家:您有一笔新的订单 ${order_id},请及时处理。 - shopMessageTemplateService.aliyunSmsSend(mobileAndLicenseNumber.getFirst(), "SMS_479760276", tmplArgs);//SMS_475836097 + shopMessageTemplateService.aliyunSmsSend(mobileAndLicenseNumber.getFirst(), "SMS_486545331", tmplArgs);//SMS_479760276 } return CommonResult.success(); @@ -847,6 +847,7 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("lkl_mer_inner_no", lklInnerMerNo); + updateWrapper.set("has_apply_mer", CommonConstant.Enable); // 是否进件成功:1-是;2-否; if (StrUtil.isNotBlank(lklMerCupNo)) { updateWrapper.set("lkl_mer_cup_no", lklMerCupNo); } @@ -974,11 +975,12 @@ public class ShopMchEntryServiceImpl extends BaseServiceImpl merchEntryInfo2StoreInfo(String mchMobile) { + public Pair merchEntryInfo2StoreInfo(String mchMobile, Boolean allowThrown) { if (StrUtil.isBlank(mchMobile)) { logger.error("生成店铺:商家手机号不能为空"); - return Pair.of(0, "商家手机号不能为空"); + return Pair.of(0, "商家手机不能为空"); } // 从绑定关系中,获取商家注册账号信息 Integer userId = accountService.getUserBindConnectUserIdByCondition(mchMobile, BindCode.MOBILE, CommonConstant.USER_TYPE_MCH); if (userId == null) { logger.error("生成店铺:绑定关系中获取不到该手机{}商家的账号", mchMobile); - return Pair.of(0, "商家账号异常!"); + return Pair.of(0, "该商家手机未注册账号!"); } ShopMchEntry shopMchEntry = shopMchEntryService.getShopMerchEntryByCondition(mchMobile, ""); @@ -3019,6 +3020,17 @@ public class ShopStoreBaseServiceImpl extends BaseServiceImpl + + + + + * + +