From c1896f3c39ac8d0c84e4647353626e5e1e47c10c Mon Sep 17 00:00:00 2001 From: Jack <46790855@qq.com> Date: Thu, 24 Apr 2025 17:38:12 +0800 Subject: [PATCH] =?UTF-8?q?shop=20=E9=A1=B9=E7=9B=AE=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=8B=89=E5=8D=A1=E6=8B=89=E7=9A=84=E4=B8=9A=E5=8A=A1=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mobile/AppMarketUpdateController.java | 11 + .../service/AdminAppMarketUpdateService.java | 8 + .../impl/AdminAppMarketUpdateServiceImpl.java | 27 +- .../src/main/resources/application.yml | 20 +- .../service/impl/LakalaPayServiceImpl.java | 13 +- .../src/main/resources/bootstrap-prod.yml | 4 +- mall-shop/pom.xml | 16 + .../lakala/controller/LakalaController.java | 69 ++ .../lakala/mapper/LklLedgerMemberMapper.java | 18 + .../LklLedgerMerReceiverBindMapper.java | 18 + .../mapper/LklLedgerReceiverMapper.java | 18 + .../shop/lakala/service/LakalaPayService.java | 138 ++++ .../service/LklLedgerMemberService.java | 39 + .../LklLedgerMerReceiverBindService.java | 39 + .../service/LklLedgerReceiverService.java | 23 + .../service/impl/LakalaPayServiceImpl.java | 741 ++++++++++++++++++ .../impl/LklLedgerMemberServiceImpl.java | 87 ++ .../LklLedgerMerReceiverBindServiceImpl.java | 85 ++ .../impl/LklLedgerReceiverServiceImpl.java | 55 ++ .../mall/shop/lakala/utils/LakalaUtil.java | 238 ++++++ .../impl/ShopMessageTemplateServiceImpl.java | 2 +- .../src/main/resources/bootstrap-dev.yml | 21 + .../src/main/resources/bootstrap-local.yml | 21 + .../src/main/resources/bootstrap-prod.yml | 21 + .../src/main/resources/bootstrap-test.yml | 21 + .../src/main/resources/bootstrap-uat.yml | 21 + .../mapper/lakala/LklLedgerMemberMapper.xml | 4 + .../lakala/LklLedgerMerReceiverBindMapper.xml | 4 + .../mapper/lakala/LklLedgerReceiverMapper.xml | 4 + 29 files changed, 1768 insertions(+), 18 deletions(-) create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/LakalaController.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMemberMapper.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMerReceiverBindMapper.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerReceiverMapper.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaPayService.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaPayServiceImpl.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMemberServiceImpl.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMerReceiverBindServiceImpl.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerReceiverServiceImpl.java create mode 100644 mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java create mode 100644 mall-shop/src/main/resources/mapper/lakala/LklLedgerMemberMapper.xml create mode 100644 mall-shop/src/main/resources/mapper/lakala/LklLedgerMerReceiverBindMapper.xml create mode 100644 mall-shop/src/main/resources/mapper/lakala/LklLedgerReceiverMapper.xml diff --git a/mall-admin/src/main/java/com/suisung/mall/admin/controller/mobile/AppMarketUpdateController.java b/mall-admin/src/main/java/com/suisung/mall/admin/controller/mobile/AppMarketUpdateController.java index 1c296697..0e34da81 100644 --- a/mall-admin/src/main/java/com/suisung/mall/admin/controller/mobile/AppMarketUpdateController.java +++ b/mall-admin/src/main/java/com/suisung/mall/admin/controller/mobile/AppMarketUpdateController.java @@ -9,6 +9,7 @@ package com.suisung.mall.admin.controller.mobile; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.json.JSONObject; import com.suisung.mall.admin.service.AdminAppMarketUpdateService; import com.suisung.mall.common.api.CommonResult; @@ -35,4 +36,14 @@ public class AppMarketUpdateController { return adminAppMarketUpdateService.checkLatestVersion(paramsJSON.getInt("marketId"), paramsJSON.getStr("packageName"), paramsJSON.getInt("currVersionKey")); } + @ApiOperation(value = "最新通用商家版App下载地址", notes = "最新通用商家版App下载地址") + @RequestMapping(value = "/last/apk", method = RequestMethod.POST) + public CommonResult lastCommonApp(@RequestBody(required = false) JSONObject paramsJSON) { + Integer marketId = 100; + if (paramsJSON != null && ObjectUtil.isNotEmpty(paramsJSON.getInt("marketId"))) { + marketId = paramsJSON.getInt("marketId"); + } + return adminAppMarketUpdateService.lastAdminAppMarketUpdate(marketId); + } + } diff --git a/mall-admin/src/main/java/com/suisung/mall/admin/service/AdminAppMarketUpdateService.java b/mall-admin/src/main/java/com/suisung/mall/admin/service/AdminAppMarketUpdateService.java index 083ac55a..010c31e9 100644 --- a/mall-admin/src/main/java/com/suisung/mall/admin/service/AdminAppMarketUpdateService.java +++ b/mall-admin/src/main/java/com/suisung/mall/admin/service/AdminAppMarketUpdateService.java @@ -24,6 +24,14 @@ public interface AdminAppMarketUpdateService { */ CommonResult checkLatestVersion(Integer marketId, String packageName, Integer currVersionKey); + /** + * 获取最新的(默认通用)商家版 App 下载地址 + * + * @param marketId + * @return + */ + CommonResult lastAdminAppMarketUpdate(Integer marketId); + /** * 后台管理员搜索列表 diff --git a/mall-admin/src/main/java/com/suisung/mall/admin/service/impl/AdminAppMarketUpdateServiceImpl.java b/mall-admin/src/main/java/com/suisung/mall/admin/service/impl/AdminAppMarketUpdateServiceImpl.java index f2a762ad..5d189e1f 100644 --- a/mall-admin/src/main/java/com/suisung/mall/admin/service/impl/AdminAppMarketUpdateServiceImpl.java +++ b/mall-admin/src/main/java/com/suisung/mall/admin/service/impl/AdminAppMarketUpdateServiceImpl.java @@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.suisung.mall.admin.mapper.AdminAppMarketUpdateMapper; import com.suisung.mall.admin.service.AdminAppMarketUpdateService; import com.suisung.mall.common.api.CommonResult; +import com.suisung.mall.common.constant.CommonConstant; import com.suisung.mall.common.domain.UserDto; import com.suisung.mall.common.modules.admin.AdminAppMarketUpdate; import com.suisung.mall.common.pojo.dto.FileInfoDTO; @@ -77,6 +78,30 @@ public class AdminAppMarketUpdateServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("market_id", marketId).eq("status", CommonConstant.Enable).orderByDesc("version_key"); + + AdminAppMarketUpdate adminAppMarketUpdate = findOne(queryWrapper); + if (adminAppMarketUpdate == null) { + return CommonResult.success(null, "没有找到最新版本"); + } + + return CommonResult.success(adminAppMarketUpdate); + } + /** * 后台管理员搜索列表 * @@ -257,7 +282,7 @@ public class AdminAppMarketUpdateServiceImpl extends BaseServiceImpl queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("market_id", marketId) + queryWrapper.eq("market_id", marketId).eq("status", CommonConstant.Enable) .eq("package_name", packageName).orderByDesc("version_key"); return findOne(queryWrapper); diff --git a/mall-gateway/src/main/resources/application.yml b/mall-gateway/src/main/resources/application.yml index 237ccd9b..f9e85cd1 100644 --- a/mall-gateway/src/main/resources/application.yml +++ b/mall-gateway/src/main/resources/application.yml @@ -69,29 +69,29 @@ secure: - "/admin/account/account-user-base/register" - "/admin/account/account-user-base/login" - "/static/image/**" - - "/mobile/pay/index/notify_url" - #- "/mobile/pay/index/lkl_wxPay_notify_url" #拉卡拉微信支付回调 - - "/mobile/pay/index/return_url" - "/shop/static/**" - - "/mobile/shop/qrcode/getQrcode" - - "/admin/shop/shop-base-config/image" + - "/admin/account/open/**" - "/admin/account/account-user-base/doLogin" + #- "/mobile/pay/index/lkl_wxPay_notify_url" #拉卡拉微信支付回调 + - "/mobile/pay/index/notify_url" + - "/mobile/pay/index/return_url" - "/admin/pay/pay-user-resource/userInfoImport" - "/admin/pay/pay-card-history/payCardHistoryImport" + - "/admin/pay/payController/wxRefundNotify" + - "/mobile/shop/qrcode/getQrcode" - "/admin/shop/shop-purchase-invoice/impPurchaseInvoiceTemp" - "/admin/shop/shop-product-base/impProductTemp" - - "/admin/pay/payController/wxRefundNotify" + - "/admin/shop/shop-base-config/image" + - "/mobile/shop/sf-express/order/status/listening/**" + - "/admin/shop/open/**" + - "/admin/shop/esign/async/notify" #E签宝电子签章异步回调 - "/shop/sf-express/cancel-order/notify" - "/shop/sf-express/rider-order-status/notify" - "/shop/sf-express/order-complete/notify" - "/shop/sync/third/**" - - "/mobile/shop/sf-express/order/status/listening/**" - - "/admin/shop/open/**" - - "/admin/account/open/**" - "/esProduct/**" - "/admin/oss/upload/**" - "/mobile/**/**/test/case" - - "/admin/shop/esign/async/notify" #E签宝电子签章异步回调 universal: urls: - "/admin/account/account-user-base/info" diff --git a/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/LakalaPayServiceImpl.java b/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/LakalaPayServiceImpl.java index 7c7c0e2b..e964a00d 100644 --- a/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/LakalaPayServiceImpl.java +++ b/mall-pay/src/main/java/com/suisung/mall/pay/service/impl/LakalaPayServiceImpl.java @@ -103,6 +103,10 @@ public class LakalaPayServiceImpl implements LakalaPayService { return true; } + protected boolean isProd() { + return "prod".equalsIgnoreCase(profile); + } + @Override public cn.hutool.json.JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId) { // 1. 配置初始化 @@ -419,19 +423,19 @@ public class LakalaPayServiceImpl implements LakalaPayService { String fileName = paramsJSON.getStr("splitEntrustFileName"); req.setSplitEntrustFileName(fileName); - // 文件上传到拉卡拉服务器 + // 分账结算委托书文件上传到拉卡拉服务器 JSONObject fileUploadResp = uploadFile(orderNo, "SPLIT_ENTRUST_FILE", StringUtils.getFileExt(fileName), paramsJSON.getStr("splitEntrustFile")); if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) { throw new ApiException(I18nUtil._("分账结算委托书上传失败!")); } String splitEntrustFilePath = fileUploadResp.getStr("attFileId"); - req.setSplitEntrustFilePath(splitEntrustFilePath); -// req.setSplitEntrustFilePath("G1/M00/06/64/CrFdEmBQc-aAGc_XAAAiIbS3WIE960.pdf"); + req.setSplitEntrustFilePath(splitEntrustFilePath); //比如:G1/M00/06/64/CrFdEmBQc-aAGc_XAAAiIbS3WIE960.pdf; - if (profile.equals("prod")) { + if (isProd()) { projectDomain = projectDomain + "/api"; } + // 给拉卡拉通知的回调地址 String retUrl = projectDomain + "/mobile/pay/lakala/ledger/applyLedgerMerNotify"; req.setRetUrl(retUrl); @@ -503,6 +507,7 @@ public class LakalaPayServiceImpl implements LakalaPayService { reqData.getStr("auditStatus"), reqData.getStr("auditStatusText"), reqData.getStr("remark")); + if (success) { respData.put("retCode", "000000"); respData.put("retMsg", "操作成功!"); diff --git a/mall-pay/src/main/resources/bootstrap-prod.yml b/mall-pay/src/main/resources/bootstrap-prod.yml index ec36094c..e0f6e1fb 100644 --- a/mall-pay/src/main/resources/bootstrap-prod.yml +++ b/mall-pay/src/main/resources/bootstrap-prod.yml @@ -115,7 +115,7 @@ project: domain: @project.domain@ #拉卡拉支付和分账 lakala: - # 服务地址 + #服务地址 server_url: https://s2.lakala.com #应用Id app_id: OP10000439 @@ -127,7 +127,7 @@ lakala: api_pri_key_path: payKey/lakala/prod/api_private_key.pem #拉卡拉平台证书 lkl_platform_cer_path: payKey/lakala/prod/lkl_platform.cer - # 机构代码 + #机构代码 org_code: 980688 #商户号 merchant_no: 8226330599900LN diff --git a/mall-shop/pom.xml b/mall-shop/pom.xml index a7cb0458..9059009a 100644 --- a/mall-shop/pom.xml +++ b/mall-shop/pom.xml @@ -278,6 +278,22 @@ 9.4.1.jre8 + + + com.lkl.laop.sdk + lkl-laop-java-sdk + 1.0.7 + ${project.basedir}/src/main/resources/lib/lkl-java-sdk-1.0.7.jar + system + + + com.github.javen205 + IJPay-Core + 2.8.0 + compile + + + diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/LakalaController.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/LakalaController.java new file mode 100644 index 00000000..25a965d3 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/controller/LakalaController.java @@ -0,0 +1,69 @@ +/* + * 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.controller; + +import cn.hutool.json.JSONObject; +import com.suisung.mall.common.api.CommonResult; +import com.suisung.mall.common.service.impl.BaseControllerImpl; +import com.suisung.mall.shop.lakala.service.LakalaPayService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.util.Base64Utils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +@Api(tags = "拉卡拉相关接口 - 前端控制器") +@RestController +@RequestMapping("/mobile/shop/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); + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMemberMapper.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMemberMapper.java new file mode 100644 index 00000000..0771b25b --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMemberMapper.java @@ -0,0 +1,18 @@ +/* + * 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.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.suisung.mall.common.modules.lakala.LklLedgerMember; +import org.springframework.stereotype.Repository; + + +@Repository +public interface LklLedgerMemberMapper extends BaseMapper { +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMerReceiverBindMapper.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMerReceiverBindMapper.java new file mode 100644 index 00000000..0129a889 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerMerReceiverBindMapper.java @@ -0,0 +1,18 @@ +/* + * 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.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind; +import org.springframework.stereotype.Repository; + + +@Repository +public interface LklLedgerMerReceiverBindMapper extends BaseMapper { +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerReceiverMapper.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerReceiverMapper.java new file mode 100644 index 00000000..90703b25 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/mapper/LklLedgerReceiverMapper.java @@ -0,0 +1,18 @@ +/* + * 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.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.suisung.mall.common.modules.lakala.LklLedgerReceiver; +import org.springframework.stereotype.Repository; + + +@Repository +public interface LklLedgerReceiverMapper extends BaseMapper { +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaPayService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaPayService.java new file mode 100644 index 00000000..2166834e --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaPayService.java @@ -0,0 +1,138 @@ +/* + * 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 cn.hutool.json.JSONObject; +import com.suisung.mall.common.api.CommonResult; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public interface LakalaPayService { + + Boolean initLKLSDK(); + + JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId); + + /** + * 拉卡拉预下单 + * 参考:https://o.lakala.com/#/home/document/detail?id=110 + * + * @param merchantNo 商户号 + * @param termNo 终端号 + * @param xcxAppId 小程序appid + * @param openId openid + * @param orderId 订单号 + * @param subject 订单标题 + * @param totalAmount 订单金额 + * @param notifyURL 回调地址 + * @param requestIP 请求ip + * @param remark 备注 + * @return + */ + JSONObject transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark); + + /** + * 聚合扫码-交易查询 + * + * @param storeId + * @param orderId + * @return + */ + JSONObject tradeQuery(Integer storeId, String orderId); + + /** + * 聚合扫码-退款交易 + * 主被扫交易发生后,因商家或消费者原因需要退款时,商家调用此接口退还消费者支付款项。 + * 注意: 1、调用退款接口时请保证商户/账户余额大于等于本次退款金额 + * + * @param storeId + * @param out_trade_no + * @param origin_trade_no // 原拉卡拉交易流水号 + * @param refund_amount 单位分,整数数字型字符 + * @param refund_reason + * @param requestIP + * @return + */ + JSONObject refund(Integer storeId, String out_trade_no, String origin_trade_no, String refund_amount, String refund_reason, String requestIP); + + /** + * 账户余额查询 + * 参考:https://o.lakala.com/#/home/document/detail?id=364 + * + * @param orgNo bmcp机构号 + * @param merchantNo 商户号 或 receiveNo 或 商户用户编号 + * @param payNo 账号(若该参数上送,则payType将无效) + * @param payType 账号类型(01:收款账户,02:付款账户,03:分账商户账户,04:分账接收方账户,05:充值代付账户,06:结算代付账户)- 未上送则默认为01 + * @return + */ + JSONObject ewalletBalanceQuery(String orgNo, String merchantNo, String payNo, String payType); + + /** + * 文件上传 + * 参考:https://o.lakala.com/#/home/document/detail?id=90 + * + * @param orderNo + * @param attType + * @param attExtName + * @param attContext + * @return + */ + JSONObject uploadFile(String orderNo, String attType, String attExtName, String attContext); + + + /** + * 商户分账业务开通申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=379 + * + * @param paramsJSON + * @return + */ + CommonResult applyLedgerMer(JSONObject paramsJSON); + + /** + * 商户分账业务开通申请回调 + * 参考:https://o.lakala.com/#/home/document/detail?id=379 + * + * @param request + * @return + */ + JSONObject applyLedgerMerNotify(HttpServletRequest request); + + /** + * 分账接收方创建申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=380 + * + * @param paramsJSON + * @return + */ + CommonResult applyLedgerReceiver(JSONObject paramsJSON); + + + /** + * 分账关系绑定申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=386 + * + * @param paramsJSON + * @return + */ + CommonResult applyLedgerMerReceiverBind(JSONObject paramsJSON); + + + /** + * 分账关系绑定申请回调 + * 参考:https://o.lakala.com/#/home/document/detail?id=379 + * + * @param request + * @return + */ + JSONObject applyLedgerMerReceiverBindNotify(HttpServletRequest request); + + +} 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 new file mode 100644 index 00000000..4c11850f --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMemberService.java @@ -0,0 +1,39 @@ +/* + * 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.LklLedgerMember; +import com.suisung.mall.core.web.service.IBaseService; + +public interface LklLedgerMemberService extends IBaseService { + + /** + * 根据银联商户号新增或修改记录 + * + * @param record + * @return + */ + Boolean saveOrUpdateByMerCupNo(LklLedgerMember record); + + + /** + * 更新审核结果 + * + * @param applyId + * @param merInnerNo + * @param merCupNo + * @param entrustFileName + * @param entrustFilePath + * @param auditStatus + * @param auditStatusText + * @param remark + * @return + */ + Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark); +} 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 new file mode 100644 index 00000000..002e4e03 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerMerReceiverBindService.java @@ -0,0 +1,39 @@ +/* + * 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.LklLedgerMerReceiverBind; +import com.suisung.mall.core.web.service.IBaseService; + +public interface LklLedgerMerReceiverBindService extends IBaseService { + + /** + * 根据接收方编号新增或更新记录 + * + * @param record + * @return + */ + Boolean saveOrUpdateByMerCupNoReceiverNo(LklLedgerMerReceiverBind record); + + /** + * 更新审核结果 + * + * @param applyId + * @param merInnerNo + * @param merCupNo + * @param receiverNo + * @param entrustFileName + * @param entrustFilePath + * @param auditStatus + * @param auditStatusText + * @param remark + * @return + */ + Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String receiverNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark); +} 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 new file mode 100644 index 00000000..1baa0a7f --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklLedgerReceiverService.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.LklLedgerReceiver; +import com.suisung.mall.core.web.service.IBaseService; + +public interface LklLedgerReceiverService extends IBaseService { + + /** + * 根据接收方编号新增或更新记录 + * + * @param record + * @return + */ + Boolean saveOrUpdateByReceiverNo(LklLedgerReceiver record); +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaPayServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaPayServiceImpl.java new file mode 100644 index 00000000..c57da239 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaPayServiceImpl.java @@ -0,0 +1,741 @@ +/* + * 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.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.ijpay.core.kit.IpKit; +import com.lkl.laop.sdk.LKLSDK; +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.suisung.mall.common.api.CommonResult; +import com.suisung.mall.common.exception.ApiException; +import com.suisung.mall.common.feignService.ShopService; +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.store.ShopStoreBase; +import com.suisung.mall.common.utils.I18nUtil; +import com.suisung.mall.common.utils.StringUtils; +import com.suisung.mall.shop.lakala.service.LakalaPayService; +import com.suisung.mall.shop.lakala.service.LklLedgerMemberService; +import com.suisung.mall.shop.lakala.service.LklLedgerMerReceiverBindService; +import com.suisung.mall.shop.lakala.service.LklLedgerReceiverService; +import com.suisung.mall.shop.lakala.utils.LakalaUtil; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; + + +@Slf4j +@Service +public class LakalaPayServiceImpl implements LakalaPayService { + private static final boolean init = false; + private static final String lklSuccessCode = "000000"; + //### 可选的两个参数,不同的店铺商家,可以数据库里配置不同的商户号和终端号 + @Value("${lakala.merchant_no}") + public String merchantNo; // 拉卡拉分配的商户号 + @Value("${lakala.term_no}") + public String termNo; // 拉卡拉分配的终端号码 + @Value("${lakala.server_url}") + private String serverUrl; //服务地址 + @Value("${lakala.app_id}") + private String appId; // 拉卡拉appId + @Value("${lakala.serial_no}") + private String serialNo; // 你的证书序列号 + @Value("${lakala.api_pri_key_path}") + private String priKeyPath; //商户私钥信息地址 + @Value("${lakala.lkl_platform_cer_path}") + private String lklCerPath; //拉卡拉支付平台证书地址 + @Value("${lakala.api_cert_path}") + private String apiCertPath; + @Value("${lakala.lkl_platform_cer_path}") + private String lklNotifyCerPath; //拉卡拉支付平台证书地址2(用于拉卡拉通知验签) + @Value("${lakala.org_code}") + private String orgCode; + @Value("${project.domain}") + private String projectDomain; + @Value("${spring.profiles.active}") + private String profile; + @Lazy + @Autowired + private ShopService shopService; + + @Autowired + private LklLedgerMemberService lklLedgerMemberService; + + @Autowired + private LklLedgerReceiverService lklLedgerReceiverService; + + @Autowired + private LklLedgerMerReceiverBindService lklLedgerMerReceiverBindService; + + /** + * 初始化 拉卡拉SDK + * + * @return + */ + @Override + public Boolean initLKLSDK() { + if (!init) { + return LakalaUtil.initLKLSDK(appId, serialNo, priKeyPath, lklCerPath, lklNotifyCerPath, serverUrl); + } + return true; + } + + protected boolean isProd() { + return "prod".equalsIgnoreCase(profile); + } + + @Override + public JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId) { + // 1. 配置初始化 + initLKLSDK(); + + //2. 装配数据 + /*** 微信主扫场景示例 */ + V3LabsTransPreorderRequest v3LabsTransPreorderWechatReq = new V3LabsTransPreorderRequest(); + v3LabsTransPreorderWechatReq.setMerchantNo(merchantNo); + v3LabsTransPreorderWechatReq.setTermNo(termNo); + v3LabsTransPreorderWechatReq.setOutTradeNo(orderId); + v3LabsTransPreorderWechatReq.setSubject("油麦菜5斤装特惠"); + v3LabsTransPreorderWechatReq.setAccountType("WECHAT"); + v3LabsTransPreorderWechatReq.setTransType("51"); + v3LabsTransPreorderWechatReq.setTotalAmount("1"); + v3LabsTransPreorderWechatReq.setNotifyUrl("http://mall.gpxscs.cn/notify"); + v3LabsTransPreorderWechatReq.setRemark("测试预下单备注"); + + //地址位置信息 + V3LabsTradeLocationInfo v3LabsTradePreorderLocationInfo1 = new V3LabsTradeLocationInfo(IpKit.getRealIp(request)); + v3LabsTransPreorderWechatReq.setLocationInfo(v3LabsTradePreorderLocationInfo1); + + //微信主扫场景下acc_busi_fields域内容 + V3LabsTradePreorderWechatBus wechatBus = new V3LabsTradePreorderWechatBus(); + wechatBus.setSubAppid("wx5a73f844dac0da5c"); // 小程序appId + wechatBus.setUserId("oDVKR7T0qxg6O8tqIL9SgY6LXqqQ"); // 微信 openId + wechatBus.setDeviceInfo("WEB"); // 终端设备号(门店号或收银设备ID),注意:PC网页或JSAPI支付请传”WEB” + v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq); + + //4. 响应 + return JSONUtil.parseObj(responseStr); + } catch (SDKException e) { + LakalaPayServiceImpl.log.error("transPreOrder error", 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 orderId 订单号 + * @param subject 订单标题 + * @param totalAmount 订单金额 + * @param notifyURL 回调地址 + * @param requestIP 请求ip + * @param remark 备注 + * @return + */ + @Override + public JSONObject transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) { + // 1. 配置初始化 + initLKLSDK(); + + if (StrUtil.isBlank(merchantNo)) { + merchantNo = this.merchantNo; + } + if (StrUtil.isBlank(termNo)) { + termNo = this.termNo; + } + + //2. 装配数据 + /*** 微信主扫场景示例 */ + V3LabsTransPreorderRequest v3LabsTransPreorderWechatReq = new V3LabsTransPreorderRequest(); + v3LabsTransPreorderWechatReq.setMerchantNo(merchantNo); + v3LabsTransPreorderWechatReq.setTermNo(termNo); + v3LabsTransPreorderWechatReq.setOutTradeNo(orderId); + v3LabsTransPreorderWechatReq.setSubject(subject); + //微信:WECHAT 支付宝:ALIPAY 银联:UQRCODEPAY 翼支付: BESTPAY 苏宁易付宝: SUNING 拉卡拉支付账户:LKLACC 网联小钱包:NUCSPAY 京东钱包:JD + v3LabsTransPreorderWechatReq.setAccountType("WECHAT"); + // 41:NATIVE((ALIPAY,云闪付支持,京东白条分期)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); + + //地址位置信息 + V3LabsTradeLocationInfo v3LabsTradePreorderLocationInfo = new V3LabsTradeLocationInfo(requestIP); + v3LabsTransPreorderWechatReq.setLocationInfo(v3LabsTradePreorderLocationInfo); + + //微信主扫场景下 acc_busi_fields 域内容 + V3LabsTradePreorderWechatBus wechatBus = new V3LabsTradePreorderWechatBus(); + wechatBus.setSubAppid(xcxAppId); // 小程序appId + wechatBus.setUserId(openId); // 微信 openId + wechatBus.setDeviceInfo("WEB"); // 终端设备号(门店号或收银设备ID),注意:PC网页或JSAPI支付请传”WEB” + v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus); + + try { + log.info("拉卡拉预下单请求参数:{}", JSONUtil.toJsonStr(v3LabsTransPreorderWechatReq)); + + //3. 发送请求 + String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq); + log.info("拉卡拉预下单响应数据:{}", responseStr); + if (StrUtil.isBlank(responseStr)) { + return null; + } + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + + // || !lakalaRespJSON.getStr("code").equals("BBS00000") + + //4. 响应 + return lakalaRespJSON; + } catch (SDKException e) { + log.error("拉卡拉支付出错:", e); + throw new ApiException(I18nUtil._("支付失败!"), e); + } + } + + /** + * 聚合扫码-交易查询 + * + * @param storeId + * @param orderId + * @return + */ + @Override + public JSONObject tradeQuery(Integer storeId, String orderId) { + if (ObjectUtil.isEmpty(storeId) || StrUtil.isBlank(orderId)) { + return null; + } + + // 1. 配置初始化 + initLKLSDK(); + + // 这里获取店铺的拉卡拉的商户号和终端号码 + ShopStoreBase shopStoreBase = shopService.getLklMerchantNoAndTermNo(storeId); + if (shopStoreBase == null || StrUtil.isBlank(shopStoreBase.getLkl_merchant_no()) || StrUtil.isBlank(shopStoreBase.getLkl_term_no())) { + log.error("缺少参数,拉卡拉交易查询失败!"); + return null; + } + + //2. 装配数据 + V3LabsQueryTradequeryRequest v3LabsQueryTradequeryRequest = new V3LabsQueryTradequeryRequest(); + v3LabsQueryTradequeryRequest.setMerchantNo(shopStoreBase.getLkl_merchant_no()); + v3LabsQueryTradequeryRequest.setTermNo(shopStoreBase.getLkl_term_no()); + v3LabsQueryTradequeryRequest.setOutTradeNo(orderId); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(v3LabsQueryTradequeryRequest); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + // || !lakalaRespJSON.getStr("code").equals("BBS00000") + + return lakalaRespJSON; + } catch (SDKException e) { + log.error("交易查询失败:", e); + throw new ApiException(I18nUtil._("交易查询失败!"), e); + } + } + + /** + * 聚合扫码-退款交易 + * 主被扫交易发生后,因商家或消费者原因需要退款时,商家调用此接口退还消费者支付款项。 + * 注意: 1、调用退款接口时请保证商户/账户余额大于等于本次退款金额 + * + * @param storeId + * @param out_trade_no + * @param origin_trade_no // 原拉卡拉交易流水号 + * @param refund_amount 单位分,整数数字型字符 + * @param refund_reason + * @param requestIP + * @return + */ + @Override + public JSONObject refund(Integer storeId, String out_trade_no, String origin_trade_no, String refund_amount, String refund_reason, String requestIP) { + if (ObjectUtil.isEmpty(storeId) || StrUtil.isBlank(out_trade_no) || StrUtil.isBlank(refund_amount) || StrUtil.isBlank(origin_trade_no) || StrUtil.isBlank(requestIP)) { + return null; + } + + // 1. 配置初始化 + initLKLSDK(); + + + // 这里获取店铺的拉卡拉的商户号和终端号码 + ShopStoreBase shopStoreBase = shopService.getLklMerchantNoAndTermNo(storeId); + if (shopStoreBase == null || StrUtil.isBlank(shopStoreBase.getLkl_merchant_no()) || StrUtil.isBlank(shopStoreBase.getLkl_term_no())) { + log.error("缺少参数,退款交易失败!"); + return null; + } + + //2. 装配数据 + V3LabsRelationIdmrefundRequest req = new V3LabsRelationIdmrefundRequest(); + req.setOutRefundOrderNo(out_trade_no); + req.setMerchantNo(shopStoreBase.getLkl_merchant_no()); + req.setTermNo(shopStoreBase.getLkl_term_no()); + req.setRefundAmount(refund_amount); + req.setRefundReason(refund_reason); + req.setOriginTradeNo(origin_trade_no); + + V3LabsTradeLocationInfo v3LabsTradeLocationInfo = new V3LabsTradeLocationInfo(requestIP, null, ""); + req.setLocationInfo(v3LabsTradeLocationInfo); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + + return lakalaRespJSON; + } catch (SDKException e) { + log.error("退款交易失败:", e); + throw new ApiException(I18nUtil._("退款交易!"), e); + } + } + + /** + * 账户余额查询 + * 参考:https://o.lakala.com/#/home/document/detail?id=364 + * + * @param orgNo bmcp机构号 测试 “1” + * @param merchantNo 商户号 或 receiveNo 或 商户用户编号 + * @param payNo 账号(若该参数上送,则payType将无效) + * @param payType 账号类型(01:收款账户,02:付款账户,03:分账商户账户,04:分账接收方账户,05:充值代付账户,06:结算代付账户)- 未上送则默认为01 + * @return + */ + @Override + public JSONObject ewalletBalanceQuery(String orgNo, String merchantNo, String payNo, String payType) { + if (StrUtil.isBlank(orgNo) || StrUtil.isBlank(merchantNo) || (StrUtil.isBlank(payNo) && StrUtil.isBlank(payType))) { + return null; + } + + // 1. 配置初始化 + initLKLSDK(); + + + //2. 装配数据 + V2LaepIndustryEwalletBalanceQueryRequest req = new V2LaepIndustryEwalletBalanceQueryRequest(); + req.setOrgNo(orgNo); + req.setMerchantNo(merchantNo); + req.setPayNo(payNo); + req.setPayType(payType); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + + return lakalaRespJSON; + } catch (SDKException e) { + log.error("账户余额查询失败:", e); + throw new ApiException(I18nUtil._("账户余额查询!"), e); + } + + } + + /** + * 文件上传 + * 参考:https://o.lakala.com/#/home/document/detail?id=90 + * + * @param orderNo + * @param attType + * @param attExtName + * @param attContext + * @return + */ + @Override + public JSONObject uploadFile(String orderNo, String attType, String attExtName, String attContext) { + // 1. 配置初始化 + initLKLSDK(); + + //2. 装配数据 + V2MmsOpenApiUploadFileRequest req = new V2MmsOpenApiUploadFileRequest(); + req.setVersion("2.0"); + req.setOrgCode(orgCode); + req.setOrderNo(orderNo); + req.setAttType(attType); + req.setAttExtName(attExtName); + req.setAttContext(attContext); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals(lklSuccessCode)) { + throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg"))); + } + + return (JSONObject) lakalaRespJSON.get("respData"); + } catch (SDKException e) { + log.error("文件上传失败:", e); + throw new ApiException(I18nUtil._("文件上传失败!"), e); + } + } + + @Override + public CommonResult applyLedgerMer(JSONObject paramsJSON) { + // 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")); + req.setSplitLowestRatio(new BigDecimal(paramsJSON.getStr("splitLowestRatio"))); + String fileName = paramsJSON.getStr("splitEntrustFileName"); + req.setSplitEntrustFileName(fileName); + + // 分账结算委托书文件上传到拉卡拉服务器 + JSONObject fileUploadResp = uploadFile(req.getOrderNo(), "SPLIT_ENTRUST_FILE", StringUtils.getFileExt(fileName), 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; + + if (isProd()) { + projectDomain = projectDomain + "/api"; + } + // 给拉卡拉通知的回调地址 + String retUrl = projectDomain + "/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); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals(lklSuccessCode)) { + throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg"))); +// responseStr="{'retCode':'000000','retMsg':'申请已受理,请等待审核结果','respData':{'version':'1.0','orderNo':'KFPT20230223181025407788734','orgCode':'1','applyId':681201215598657536}}"; +// lakalaRespJSON = JSONUtil.parseObj(responseStr); + } + + paramsJSON.set("apply_id", lakalaRespJSON.getByPath("respData.applyId")); + paramsJSON.set("remark", lakalaRespJSON.getStr("retMsg")); + paramsJSON.set("audit_status_text", paramsJSON.get("remark")); + + // 新增数据 + // 将 JSON 对象的键名转换为下划线命名 + LklLedgerMember lklLedgerMember = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMember.class); + lklLedgerMemberService.saveOrUpdateByMerCupNo(lklLedgerMember); + + return CommonResult.success(null, "提交成功,待审核中!"); + } catch (SDKException e) { + log.error("分账申请失败:", e); + throw new ApiException(I18nUtil._("申请失败!"), e); + } + } + + /** + * 商户分账业务开通申请回调 + * 参考:https://o.lakala.com/#/home/document/detail?id=379 + * + * @param request + * @return + */ + @Override + public JSONObject applyLedgerMerNotify(HttpServletRequest request) { + log.info("商户分账申请业务回调收到通知"); + // 验签 + String authorization = request.getHeader("Authorization"); + String requestBody = LakalaUtil.getBody(request); + log.info("商户分账申请业务回调返回参数:{}", requestBody); + + boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath); + if (!checkSuccess) { + log.info("商户分账申请业务回调:验签失败"); + return JSONUtil.createObj().set("retCode", "OP90002").set("retMsg", "验签失败!"); + } + + JSONObject paramsJSON = JSONUtil.parseObj(requestBody); + + JSONObject respData = new JSONObject(); + respData.put("retCode", "OP90003"); + respData.put("retMsg", "响应处理失败!"); + + if (paramsJSON != null) { + JSONObject reqData = (JSONObject) paramsJSON.get("respData"); + if (ObjectUtil.isEmpty(reqData)) { + return respData; + } + + // 更改本地分账记录状态数据 + Boolean success = lklLedgerMemberService.updateAuditResult(reqData.getStr("applyId"), + reqData.getStr("merInnerNo"), + reqData.getStr("merCupNo"), + reqData.getStr("entrustFileName"), + reqData.getStr("entrustFilePath"), + reqData.getStr("auditStatus"), + reqData.getStr("auditStatusText"), + reqData.getStr("remark")); + + if (success) { + respData.put("retCode", lklSuccessCode); + respData.put("retMsg", "操作成功!"); + log.info("商户分账申请业务回调:处理成功"); + } + } + + return respData; + } + + /** + * 分账接收方创建申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=380 + * + * @param paramsJSON + * @return + */ + @Override + public CommonResult applyLedgerReceiver(JSONObject paramsJSON) { + // 1. 配置初始化 + initLKLSDK(); + + //2. 装配数据 + V2MmsOpenApiLedgerApplyLedgerReceiverRequest req = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest(); + + String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数 + req.setOrderNo(orderNo); + req.setOrgCode(orgCode); + req.setVersion("2.0"); + + req.setReceiverName(paramsJSON.getStr("receiverName")); + req.setContactMobile(paramsJSON.getStr("contactMobile")); + + 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")); + + if (paramsJSON.getJSONArray("attachList") != null && paramsJSON.getJSONArray("attachList").size() > 0) { + List attachList = new ArrayList<>(); + V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo attachInfo = new V2MmsOpenApiLedgerApplyLedgerReceiverRequest.AttachInfo(); + for (JSONObject attachJSON : paramsJSON.getJSONArray("attachList").jsonIter()) { + String fileName = attachJSON.getStr("attachName"); + String attachType = attachJSON.getStr("attachType"); + String fileBase64 = attachJSON.getStr("attachStoreFile"); + attachInfo.setAttachName(fileName); + attachInfo.setAttachType(attachType); + + JSONObject fileUploadResp = uploadFile(StringUtils.genLklOrderNo(8), attachType, + StringUtils.getFileExt(fileName), fileBase64); + if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) { + throw new ApiException(I18nUtil._("附件上传失败!")); + } + + attachInfo.setAttachStorePath(fileUploadResp.getStr("attFileId")); + attachList.add(attachInfo); + } + + req.setAttachList(attachList); + paramsJSON.set("attach_list", JSONUtil.toJsonStr(attachList)); + } + + paramsJSON.set("orderNo", orderNo); + paramsJSON.set("version", "2.0"); + paramsJSON.set("org_code", orgCode); + + try { + //3. 发送请求,申请创建分账接收方 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals(lklSuccessCode)) { + throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg"))); + } + + paramsJSON.set("receiver_no", lakalaRespJSON.getByPath("respData.receiverNo")); + paramsJSON.set("org_id", lakalaRespJSON.getByPath("respData.orgId")); + paramsJSON.set("org_name", lakalaRespJSON.getByPath("respData.orgName")); + + // 新增数据 + // 将 JSON 对象的键名转换为下划线命名 + LklLedgerReceiver lklLedgerReceiver = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerReceiver.class); + + // 新增或修改本地数据 + lklLedgerReceiverService.saveOrUpdateByReceiverNo(lklLedgerReceiver); + + return CommonResult.success(null, "接收方创建成功!"); + } catch (SDKException e) { + log.error("接收方创建失败:", e); + throw new ApiException(I18nUtil._("接收方创建失败!"), e); + } + } + + /** + * 分账关系绑定申请 + * 参考:https://o.lakala.com/#/home/document/detail?id=386 + * + * @param paramsJSON + * @return + */ + @Override + public CommonResult applyLedgerMerReceiverBind(JSONObject paramsJSON) { + // 1. 配置初始化 + initLKLSDK(); + + //2. 装配数据 + V2MmsOpenApiLedgerApplyBindRequest req = new V2MmsOpenApiLedgerApplyBindRequest(); + + String orderNo = StringUtils.genLklOrderNo(8); // 8位随机数 + req.setOrderNo(orderNo); + req.setOrgCode(orgCode); + req.setVersion("2.0"); + + req.setMerInnerNo(paramsJSON.getStr("merInnerNo")); + req.setMerCupNo(paramsJSON.getStr("merCupNo")); + req.setReceiverNo(paramsJSON.getStr("receiverNo")); + + String fileName = paramsJSON.getStr("entrustFileName"); + String splitEntrustFileBase64 = paramsJSON.getStr("entrustFile"); + req.setEntrustFileName(fileName); + + String retUrl = projectDomain + "/mobile/pay/lakala/ledger/applyLedgerMerReceiverBindNotify"; + req.setRetUrl(retUrl); + + // 文件上传到拉卡拉服务器 + JSONObject fileUploadResp = uploadFile(orderNo, + "SPLIT_COOPERATION_FILE", + StringUtils.getFileExt(fileName), + splitEntrustFileBase64); + if (fileUploadResp == null || StrUtil.isBlank(fileUploadResp.getStr("attFileId"))) { + throw new ApiException(I18nUtil._("合作协议上传失败!")); + } + + String entrustFilePath = fileUploadResp.getStr("attFileId"); + req.setEntrustFilePath(entrustFilePath); + + paramsJSON.set("orderNo", orderNo); + paramsJSON.set("version", "2.0"); + paramsJSON.set("ret_url", retUrl); + paramsJSON.set("org_code", orgCode); + paramsJSON.set("entrust_file_name", fileName); + paramsJSON.set("entrust_file_path", entrustFilePath); + + try { + //3. 发送请求 + String responseStr = LKLSDK.httpPost(req); + + JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr); + if (lakalaRespJSON == null || !lakalaRespJSON.getStr("retCode").equals(lklSuccessCode)) { + throw new ApiException(I18nUtil._(lakalaRespJSON.getStr("retMsg"))); + } + + paramsJSON.set("apply_id", lakalaRespJSON.getByPath("respData.applyId")); + paramsJSON.set("remark", lakalaRespJSON.getStr("retMsg")); + + // 新增数据 + // 将 JSON 对象的键名转换为下划线命名 + LklLedgerMerReceiverBind lklLedgerMerReceiverBind = JSONUtil.toBean(StringUtils.convertCamelToSnake(paramsJSON.toString()), LklLedgerMerReceiverBind.class); + lklLedgerMerReceiverBindService.saveOrUpdateByMerCupNoReceiverNo(lklLedgerMerReceiverBind); + + return CommonResult.success(null, "提交成功,待审核中!"); + } catch (SDKException e) { + log.error("分账绑定关系申请失败:", e); + throw new ApiException(I18nUtil._("分账绑定关系申请失败!"), e); + } + } + + /** + * 分账关系绑定申请回调 + * 参考:https://o.lakala.com/#/home/document/detail?id=379 + * + * @param request + * @return + */ + @Override + public JSONObject applyLedgerMerReceiverBindNotify(HttpServletRequest request) { + // 验签 + String authorization = request.getHeader("Authorization"); + String requestBody = LakalaUtil.getBody(request); + + boolean checkSuccess = LakalaUtil.verify(authorization, requestBody, lklNotifyCerPath); + if (!checkSuccess) { + return JSONUtil.createObj().set("retCode", "OP90002").set("retMsg", "验签失败!"); + } + +// String requestBody = LakalaUtil.getBody(request); + + JSONObject paramsJSON = JSONUtil.parseObj(requestBody); + JSONObject respData = new JSONObject(); + respData.put("retCode", "OP90003"); + respData.put("retMsg", "响应处理失败!"); + + if (paramsJSON != null && paramsJSON.get("respData") != null) { + JSONObject reqData = (JSONObject) paramsJSON.get("respData"); + + Boolean success = lklLedgerMerReceiverBindService.updateAuditResult(reqData.getStr("applyId"), + reqData.getStr("merInnerNo"), + reqData.getStr("merCupNo"), + reqData.getStr("receiverNo"), + reqData.getStr("entrustFileName"), + reqData.getStr("entrustFilePath"), + reqData.getStr("auditStatus"), + reqData.getStr("auditStatusText"), + reqData.getStr("remark")); + if (success) { + respData.put("retCode", lklSuccessCode); + respData.put("retMsg", "操作成功!"); + } + } + + return respData; + } + + +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMemberServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMemberServiceImpl.java new file mode 100644 index 00000000..8a7b7b3b --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMemberServiceImpl.java @@ -0,0 +1,87 @@ +/* + * 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.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.suisung.mall.common.modules.lakala.LklLedgerMember; +import com.suisung.mall.core.web.service.impl.BaseServiceImpl; +import com.suisung.mall.shop.lakala.mapper.LklLedgerMemberMapper; +import com.suisung.mall.shop.lakala.service.LklLedgerMemberService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Slf4j +@Service +public class LklLedgerMemberServiceImpl extends BaseServiceImpl implements LklLedgerMemberService { + + + /** + * 根据银联商户号新增或修改记录 + * + * @param record + * @return + */ + @Override + public Boolean saveOrUpdateByMerCupNo(LklLedgerMember record) { + if (record == null || StrUtil.isBlank(record.getMer_cup_no())) { + return false; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("mer_cup_no", record.getMer_cup_no()); + List existsRecordList = list(queryWrapper); + if (!CollectionUtil.isEmpty(existsRecordList) + && existsRecordList.get(0) != null + && existsRecordList.get(0).getId() > 0) { + // update + record.setId(existsRecordList.get(0).getId()); + return updateById(record); + } + + return add(record); + } + + /** + * 更新审核结果 + * + * @param applyId + * @param merInnerNo + * @param merCupNo + * @param entrustFileName + * @param entrustFilePath + * @param auditStatus + * @param auditStatusText + * @param remark + * @return + */ + @Override + public Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark) { + if (StrUtil.isBlank(applyId) || StrUtil.isBlank(merCupNo) || StrUtil.isBlank(auditStatus)) { + log.error("缺少参数:applyId={}, merCupNo={}, auditStatus={}", applyId, merCupNo, auditStatus); + return false; + } + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("apply_id", applyId); + updateWrapper.set("mer_inner_no", merInnerNo); + updateWrapper.set("mer_cup_no", merCupNo); + updateWrapper.set("split_entrust_file_name", entrustFileName); + updateWrapper.set("split_entrust_file_path", entrustFilePath); + updateWrapper.set("audit_status", auditStatus); + updateWrapper.set("audit_status_text", auditStatusText); + updateWrapper.set("remark", remark); + + return update(updateWrapper); + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMerReceiverBindServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMerReceiverBindServiceImpl.java new file mode 100644 index 00000000..f9613a26 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerMerReceiverBindServiceImpl.java @@ -0,0 +1,85 @@ +/* + * 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.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.suisung.mall.common.modules.lakala.LklLedgerMerReceiverBind; +import com.suisung.mall.core.web.service.impl.BaseServiceImpl; +import com.suisung.mall.shop.lakala.mapper.LklLedgerMerReceiverBindMapper; +import com.suisung.mall.shop.lakala.service.LklLedgerMerReceiverBindService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class LklLedgerMerReceiverBindServiceImpl extends BaseServiceImpl implements LklLedgerMerReceiverBindService { + /** + * 根据接收方编号新增或更新记录 + * + * @param record + * @return + */ + @Override + public Boolean saveOrUpdateByMerCupNoReceiverNo(LklLedgerMerReceiverBind record) { + if (record == null || StrUtil.isBlank(record.getReceiver_no())) { + return false; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("mer_cup_no", record.getMer_cup_no()); + queryWrapper.eq("receiver_no", record.getReceiver_no()); + List existsRecordList = list(queryWrapper); + if (!CollectionUtil.isEmpty(existsRecordList) + && existsRecordList.get(0) != null + && existsRecordList.get(0).getId() > 0) { + // update + record.setId(existsRecordList.get(0).getId()); + return updateById(record); + } + + return save(record); + } + + /** + * 更新审核结果 + * + * @param applyId + * @param merInnerNo + * @param merCupNo + * @param receiverNo + * @param entrustFileName + * @param entrustFilePath + * @param auditStatus + * @param auditStatusText + * @param remark + * @return + */ + @Override + public Boolean updateAuditResult(String applyId, String merInnerNo, String merCupNo, String receiverNo, String entrustFileName, String entrustFilePath, String auditStatus, String auditStatusText, String remark) { + if (StrUtil.isBlank(applyId) || StrUtil.isBlank(merCupNo) || StrUtil.isBlank(receiverNo) || StrUtil.isBlank(auditStatus)) { + return false; + } + + UpdateWrapper updateWrapper = new UpdateWrapper<>(); + updateWrapper.eq("apply_id", applyId); + updateWrapper.set("receiver_no", receiverNo); + updateWrapper.set("mer_inner_no", merInnerNo); + updateWrapper.set("mer_cup_no", merCupNo); + updateWrapper.set("split_entrust_file_name", entrustFileName); + updateWrapper.set("split_entrust_file_path", entrustFilePath); + updateWrapper.set("audit_status", auditStatus); + updateWrapper.set("audit_status_text", auditStatusText); + updateWrapper.set("remark", remark); + + return update(updateWrapper); + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerReceiverServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerReceiverServiceImpl.java new file mode 100644 index 00000000..80b3d4d3 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklLedgerReceiverServiceImpl.java @@ -0,0 +1,55 @@ +/* + * 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.collection.CollectionUtil; +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.suisung.mall.common.modules.lakala.LklLedgerReceiver; +import com.suisung.mall.core.web.service.impl.BaseServiceImpl; +import com.suisung.mall.shop.lakala.mapper.LklLedgerReceiverMapper; +import com.suisung.mall.shop.lakala.service.LklLedgerReceiverService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +public class LklLedgerReceiverServiceImpl extends BaseServiceImpl implements LklLedgerReceiverService { + /** + * 根据接收方编号新增或更新记录 + * + * @param record + * @return + */ + @Override + public Boolean saveOrUpdateByReceiverNo(LklLedgerReceiver record) { + if (record == null || (StrUtil.isBlank(record.getLicense_no()) && StrUtil.isBlank(record.getContact_mobile()))) { + return false; + } + + QueryWrapper queryWrapper = new QueryWrapper<>(); + if (StrUtil.isNotBlank(record.getLicense_no())) { + queryWrapper.eq("license_no", record.getLicense_no()); + } + if (StrUtil.isNotBlank(record.getContact_mobile())) { + queryWrapper.eq("contact_mobile", record.getContact_mobile()); + } + + List existsRecordList = list(queryWrapper); + if (!CollectionUtil.isEmpty(existsRecordList) + && existsRecordList.get(0) != null + && existsRecordList.get(0).getId() > 0) { + // update + record.setId(existsRecordList.get(0).getId()); + return updateById(record); + } + + return save(record); + } +} 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 new file mode 100644 index 00000000..69375c13 --- /dev/null +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/utils/LakalaUtil.java @@ -0,0 +1,238 @@ +/* + * 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.utils; + +import cn.hutool.core.util.StrUtil; +import com.lkl.laop.sdk.Config2; +import com.lkl.laop.sdk.LKLSDK; +import com.suisung.mall.common.exception.ApiException; +import com.suisung.mall.common.utils.I18nUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.binary.Base64; +import org.springframework.core.io.ClassPathResource; + +import javax.servlet.http.HttpServletRequest; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.Signature; +import java.security.SignatureException; +import java.security.cert.*; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class LakalaUtil { + + /** + * 初始化 拉卡拉 SDK(全局只需设置一次) + * + * @return + */ + public static boolean initLKLSDK(String appId, String serialNo, String priKeyPath, String lklCerPath, String lklNotifyCerPath, String serverUrl) { + Config2 config = new Config2(); + config.setAppId(appId); + config.setSerialNo(serialNo); + config.setPriKey(getResourceFile(priKeyPath)); + config.setLklCer(getResourceFile(lklCerPath)); + config.setLklNotifyCer(getResourceFile(lklNotifyCerPath)); + config.setServerUrl(serverUrl); + return LKLSDK.init(config); + } + + /** + * 读取证书文件内容 + * + * @param fileName recource 文件夹下的路径,如:palyKey/wx/lakala_public_key.cer + * @return + */ + public static String getResourceFile(String fileName) { + StringBuilder stringBuilder = new StringBuilder(); + try (InputStream inputStream = new ClassPathResource(fileName).getInputStream(); + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) { + String line; + while ((line = bufferedReader.readLine()) != null) { + stringBuilder.append(line).append("\n"); + } + String content = stringBuilder.toString(); + // log.info("证书内容:{}", content); + return content; + } catch (IOException e) { + // 记录异常信息 + log.error(e.getMessage()); + return ""; + } + } + + /** + * 获取 body 请求数据 + * + * @param request + * @return + */ + public static String getBody(HttpServletRequest request) { + InputStreamReader in = null; + try { + in = new InputStreamReader(request.getInputStream(), StandardCharsets.UTF_8); + StringBuffer bf = new StringBuffer(); + int len; + char[] chs = new char[1024]; + while ((len = in.read(chs)) != -1) { + bf.append(new String(chs, 0, len)); + } + return bf.toString(); + } catch (Exception e) { + log.error("请求头部取数据异常:{}", e); + throw new ApiException(I18nUtil._("获取请求数据失败!"), e); + } finally { + if (null != in) { + try { + in.close(); + } catch (Exception e) { + log.error("流关闭异常:{}", e); + } + } + } + } + + /** + * 获取证书信息 + * + * @param inputStream + * @return + */ + public static X509Certificate loadCertificate(InputStream inputStream) { + try { + CertificateFactory cf = CertificateFactory.getInstance("X509"); + X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream); +// String publicKeyBase64 = Base64.encodeBase64String(cert.getPublicKey().getEncoded()); +// System.out.println(publicKeyBase64); + cert.checkValidity(); + return cert; + } catch (CertificateExpiredException e) { + throw new RuntimeException("证书已过期", e); + } catch (CertificateNotYetValidException e) { + throw new RuntimeException("证书尚未生效", e); + } catch (CertificateException e) { + throw new RuntimeException("无效的证书", e); + } + } + + + /** + * 签名验证 + * + * @param certificate + * @param message + * @param signature + * @return + */ + public static boolean verify(X509Certificate certificate, byte[] message, String signature) { + try { + Signature sign = Signature.getInstance("SHA256withRSA"); + sign.initVerify(certificate); + sign.update(message); + byte[] signatureB = Base64.decodeBase64(signature); + return sign.verify(signatureB); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("当前Java环境不支持SHA256withRSA", e); + } catch (SignatureException e) { + throw new RuntimeException("签名验证过程发生了错误", e); + } catch (InvalidKeyException e) { + throw new RuntimeException("无效的证书", e); + } + } + + + /** + * 签名校验 + * + * @param authorization + * @param reqBody + * @param lklNotifyCerPath + * @return + */ + public static boolean verify(String authorization, String reqBody, String lklNotifyCerPath) { + try { + log.debug("拉卡拉 Authorization 内容:{}", authorization); + Map headerMap = getLakalaAuthorizationMap(authorization); + String timestamp = headerMap.get("timestamp"); + String nonceStr = headerMap.get("nonce_str"); + String signature = headerMap.get("signature"); + + X509Certificate lklNotifyCer = loadCertificate(new ClassPathResource(lklNotifyCerPath).getInputStream()); + if (StrUtil.isBlank(timestamp) || StrUtil.isBlank(nonceStr) || StrUtil.isBlank(signature) || lklNotifyCer == null) { + return false; + } + + String preSignDataStr = timestamp + "\n" + + nonceStr + "\n" + + reqBody + "\n"; + log.debug("拉卡拉签名明文内容:{}", preSignDataStr); + if (verify(lklNotifyCer, preSignDataStr.getBytes(StandardCharsets.UTF_8), signature)) { + log.debug("验签成功"); + return true; + } + + log.debug("验签失败"); + return false; + } catch (Exception e) { + log.error("验签发生异常:{}", e); + return false; + } + } + + /** + * 解析请求头中的认证签名等信息 + * 参考:https://o.lakala.com/#/home/document/detail?id=36 + * + * @param authorization 格式如下 + * LKLAPI-SHA256withRSA timestamp="1643271327", + * nonce_str="rQCbASKattHx", + * signature=" + * iPSycbakMt7AgjmwbtaweDVI/RLsQnGvOGiVM93haFkPpT/BxUprYx/GKFLQZebSQMvBfbeWinmnOJlqd3bXgye41BUAVmbItSTOzaQhNyS2kbDwXXGJWmT84aeJWHUB05BWB8ng + * /+X7jrPtsenC6aO7Xgh8jNylJlkU59TKCi7BPGbyHo6pAWJl/Bus0IQps1ay+Eo6Ks3Ins3COV7 + * /lmu5p5FD7TAZsfP+ZvMFObLJOrDQeBTMFKFFWj4ZkjNzNlQqZWlfLv4yLns + * /dKTDLDy5tRO5zwunW+li5YLcwOVf3tbevNFtg53WoBhQnwf838WNvY9zfRhOpCc4fBlWAA==" + * @return + */ + public static Map getLakalaAuthorizationMap(String authorization) { + Map map = new HashMap(); + authorization = authorization.trim(); + int bpos = authorization.indexOf(" "); + String authType = authorization.substring(0, bpos); + String[] typeArr = authType.split("-"); + if (typeArr.length > 1) { + map.put("signSystemCode", typeArr[0]); + map.put("signAlgorithm", typeArr[1]); + } + + String signInfo = authorization.substring(bpos + 1); + String[] infoArr = signInfo.split(","); + String[] var7 = infoArr; + int var8 = infoArr.length; + + for (int var9 = 0; var9 < var8; ++var9) { + String info = var7[var9]; + if (info.contains("=")) { + int fpos = info.indexOf("="); + String value = info.substring(fpos + 1).trim(); + value = value.substring(1, value.length() - 1); + map.put(info.substring(0, fpos).trim(), value); + } + } + + return map; + } +} diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/message/service/impl/ShopMessageTemplateServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/message/service/impl/ShopMessageTemplateServiceImpl.java index 051c5e30..514ec790 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/message/service/impl/ShopMessageTemplateServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/message/service/impl/ShopMessageTemplateServiceImpl.java @@ -307,7 +307,7 @@ public class ShopMessageTemplateServiceImpl extends BaseServiceImpl + + + diff --git a/mall-shop/src/main/resources/mapper/lakala/LklLedgerMerReceiverBindMapper.xml b/mall-shop/src/main/resources/mapper/lakala/LklLedgerMerReceiverBindMapper.xml new file mode 100644 index 00000000..3e060b7a --- /dev/null +++ b/mall-shop/src/main/resources/mapper/lakala/LklLedgerMerReceiverBindMapper.xml @@ -0,0 +1,4 @@ + + + + diff --git a/mall-shop/src/main/resources/mapper/lakala/LklLedgerReceiverMapper.xml b/mall-shop/src/main/resources/mapper/lakala/LklLedgerReceiverMapper.xml new file mode 100644 index 00000000..2b03205c --- /dev/null +++ b/mall-shop/src/main/resources/mapper/lakala/LklLedgerReceiverMapper.xml @@ -0,0 +1,4 @@ + + + +