拉卡拉支付调试修正

This commit is contained in:
Jack 2025-01-27 23:21:49 +08:00
parent aca19864ba
commit 3ac2688758
14 changed files with 486 additions and 115 deletions

View File

@ -1,9 +1,14 @@
package com.suisung.mall.common.utils; package com.suisung.mall.common.utils;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*; import java.io.*;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.security.MessageDigest; import java.security.MessageDigest;
@ -29,6 +34,7 @@ public final class StringUtils extends org.apache.commons.lang3.StringUtils {
private static final String ALL_TEMP = INT_TEMP + STR_TEMP; private static final String ALL_TEMP = INT_TEMP + STR_TEMP;
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final Logger logger = LoggerFactory.getLogger(StringUtils.class); private static final Logger logger = LoggerFactory.getLogger(StringUtils.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(removeProvinceCityDistrict("广西壮族自治区贵港市桂平市西山镇新安街粤桂花城1102号")); System.out.println(removeProvinceCityDistrict("广西壮族自治区贵港市桂平市西山镇新安街粤桂花城1102号"));
@ -223,6 +229,7 @@ public final class StringUtils extends org.apache.commons.lang3.StringUtils {
/** /**
* 生成唯一码 * 生成唯一码
*
* @param length * @param length
* @return * @return
*/ */
@ -271,6 +278,51 @@ public final class StringUtils extends org.apache.commons.lang3.StringUtils {
return fullAddress; return fullAddress;
} }
/**
* 判断字符串是否是XML或JSON格式
*
* @param input
* @return XML或JSON或None
*/
public static String isXMLOrJSON(String input) {
if (input == null || input.trim().isEmpty()) {
return "None";
}
input = input.trim();
// 快速初步判断
if (input.startsWith("{") || input.startsWith("[")) {
if (isValidJson(input)) {
return "JSON";
}
} else if (input.startsWith("<")) {
if (isValidXml(input)) {
return "XML";
}
}
return "None";
}
private static boolean isValidJson(String json) {
try {
objectMapper.readTree(json);
return true;
} catch (Exception e) {
return false;
}
}
private static boolean isValidXml(String xml) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new InputSource(new StringReader(xml)));
return true;
} catch (Exception e) {
return false;
}
}
/** /**
* 生成的随机数类型 * 生成的随机数类型

View File

@ -42,6 +42,7 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
return chain.filter(exchange); return chain.filter(exchange);
} }
} }
try { try {
//从token中解析用户信息并设置到Header中去 //从token中解析用户信息并设置到Header中去
String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, ""); String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");

View File

@ -70,6 +70,7 @@ secure:
- "/admin/account/account-user-base/login" - "/admin/account/account-user-base/login"
- "/static/image/**" - "/static/image/**"
- "/mobile/pay/index/notify_url" - "/mobile/pay/index/notify_url"
#- "/mobile/pay/index/lkl_wxPay_notify_url" #拉卡拉微信支付回调
- "/mobile/pay/index/return_url" - "/mobile/pay/index/return_url"
- "/shop/static/**" - "/shop/static/**"
- "/mobile/shop/qrcode/getQrcode" - "/mobile/shop/qrcode/getQrcode"

View File

@ -79,6 +79,13 @@
</dependency> </dependency>
<!--拉卡拉支付与分账 结束--> <!--拉卡拉支付与分账 结束-->
<!-- https://mvnrepository.com/artifact/com.github.wechatpay-apiv3/wechatpay-java -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-java</artifactId>
<version>0.2.15</version>
</dependency>
<!-- rabbitMQ消息队列 --> <!-- rabbitMQ消息队列 -->
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>

View File

@ -243,6 +243,12 @@ public class IndexController extends BaseControllerImpl {
return payUserPayService.notifyUrl(request, response, "wx_native"); return payUserPayService.notifyUrl(request, response, "wx_native");
} }
@ApiOperation(value = "拉卡拉在微信回调通知", notes = "拉卡拉在微信回调通知")
@RequestMapping(value = "/lkl_wxPay_notify_url", method = RequestMethod.POST)
public String lklWxNotifyUrl(HttpServletRequest request) {
return payUserPayService.lklNotifyUrl(request);
}
@RequestMapping(value = "/pay_state_detection", method = RequestMethod.GET) @RequestMapping(value = "/pay_state_detection", method = RequestMethod.GET)
public CommonResult payStateDetection(@RequestParam String order_id) { public CommonResult payStateDetection(@RequestParam String order_id) {
UserDto userDto = getCurrentUser(); UserDto userDto = getCurrentUser();

View File

@ -10,6 +10,7 @@ package com.suisung.mall.pay.service;
import cn.hutool.json.JSONObject; import cn.hutool.json.JSONObject;
import com.suisung.mall.common.pojo.res.PayAccRespFieldsRes; import com.suisung.mall.common.pojo.res.PayAccRespFieldsRes;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -21,6 +22,13 @@ public interface LakalaService {
*/ */
void doInit(); void doInit();
/**
* 初始化微信V3支付参数
*
* @return
*/
NativePayService initWxV3SDK();
JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId); JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId);
/** /**
@ -38,5 +46,5 @@ public interface LakalaService {
* @param remark 备注 * @param remark 备注
* @return * @return
*/ */
PayAccRespFieldsRes transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark); JSONObject transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark);
} }

View File

@ -31,6 +31,13 @@ public interface PayUserPayService extends IBaseService<PayUserPay> {
String notifyUrl(HttpServletRequest request, HttpServletResponse response, String payment_channel_code); String notifyUrl(HttpServletRequest request, HttpServletResponse response, String payment_channel_code);
/**
* lakala 支付回调
* @param request
* @return
*/
String lklNotifyUrl(HttpServletRequest request);
void returnUrl(HttpServletRequest request, HttpServletResponse response, String payment_channel_code); void returnUrl(HttpServletRequest request, HttpServletResponse response, String payment_channel_code);
/** /**

View File

@ -10,6 +10,7 @@ package com.suisung.mall.pay.service.impl;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.ijpay.core.kit.IpKit; import com.ijpay.core.kit.IpKit;
import com.lkl.laop.sdk.Config2; import com.lkl.laop.sdk.Config2;
@ -22,24 +23,42 @@ import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.pojo.res.PayAccRespFieldsRes; import com.suisung.mall.common.pojo.res.PayAccRespFieldsRes;
import com.suisung.mall.common.utils.I18nUtil; import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.pay.service.LakalaService; import com.suisung.mall.pay.service.LakalaService;
import com.wechat.pay.java.core.exception.ServiceException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; 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.*;
@Slf4j @Slf4j
@Service @Service
public class LakalaServiceImpl implements LakalaService { public class LakalaServiceImpl implements LakalaService {
private static volatile boolean init = false; private static volatile boolean init = false;
private static volatile NativePayService wxV3Service= null;
/** /**
* 服务地址 * 服务地址
*/ */
@ -121,6 +140,30 @@ public class LakalaServiceImpl implements LakalaService {
return LKLSDK.init(config); return LKLSDK.init(config);
} }
@Override
public NativePayService initWxV3SDK() {
if (wxV3Service!=null) {
return wxV3Service;
}
Config config =
new RSAAutoCertificateConfig.Builder()
.merchantId("1684833481")
.privateKey(getResourceFile("payKey/wx/apiclient_key.pem"))
.privateKeyFromPath("privateKeyPath")
.merchantSerialNumber("30314B93B394352C6A4616B7611C23402041ADE2")
.apiV3Key("n8tgyMKKAov9lVJuczMOLUjXr5IeZjce")
.build();
// 构建service
NativePayService service = new NativePayService.Builder().config(config).build();
if(service==null){
throw new ApiException(I18nUtil._("微信SDKV3初始化失败"));
}
wxV3Service = service;
return service;
}
@Override @Override
public cn.hutool.json.JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId) { public cn.hutool.json.JSONObject transPreOrder(HttpServletRequest request, HttpServletResponse response, String orderId) {
// 1. 配置初始化 // 1. 配置初始化
@ -157,7 +200,7 @@ public class LakalaServiceImpl implements LakalaService {
//4. 响应 //4. 响应
return JSONUtil.parseObj(responseStr); return JSONUtil.parseObj(responseStr);
} catch (SDKException e) { } catch (SDKException e) {
log.error("transPreOrder error", e); LakalaServiceImpl.log.error("transPreOrder error", e);
throw new ApiException(I18nUtil._("获取公众号绑定信息失败!"), e); throw new ApiException(I18nUtil._("获取公众号绑定信息失败!"), e);
} }
@ -177,9 +220,9 @@ public class LakalaServiceImpl implements LakalaService {
* @param requestIP 请求ip * @param requestIP 请求ip
* @param remark 备注 * @param remark 备注
* @return * @return
* */ */
@Override @Override
public PayAccRespFieldsRes transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) { public JSONObject transPreOrder(String merchantNo, String termNo, String xcxAppId, String openId, String orderId, String subject, String totalAmount, String notifyURL, String requestIP, String remark) {
// 1. 配置初始化 // 1. 配置初始化
doInit(); doInit();
@ -218,20 +261,24 @@ public class LakalaServiceImpl implements LakalaService {
v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus); v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus);
try { try {
log.info("拉卡拉预下单请求参数:{}", JSONUtil.toJsonStr(v3LabsTransPreorderWechatReq));
//3. 发送请求 //3. 发送请求
String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq); String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq);
log.info("拉卡拉预下单响应数据:{}", responseStr);
if (StrUtil.isBlank(responseStr)) { if (StrUtil.isBlank(responseStr)) {
return null; return null;
} }
PayAccRespFieldsRes res = JSONUtil.parseObj(responseStr).get("acc_resp_fields", PayAccRespFieldsRes.class); JSONObject res = JSONUtil.parseObj(responseStr);
//4. 响应 //4. 响应
return res; return res;
} catch (SDKException e) { } catch (SDKException e) {
log.error("拉卡拉支付出错:", e); log.error("拉卡拉支付出错:", e);
throw new ApiException(I18nUtil._("支付失败:"), e); throw new ApiException(I18nUtil._("支付失败!"), e);
}
} }
}
} }

View File

@ -203,6 +203,7 @@ public class PayConsumeDepositServiceImpl extends BaseServiceImpl<PayConsumeDepo
PayConsumeDeposit deposit_row = getOne(deposit_column_row); PayConsumeDeposit deposit_row = getOne(deposit_column_row);
if (deposit_row == null) { if (deposit_row == null) {
log.info("#####PayConsumeDeposit:{}#####", notify_row);
// 增加充值信息 // 增加充值信息
if (!saveOrUpdate(notify_row)) { if (!saveOrUpdate(notify_row)) {
throw new ApiException(ResultCode.FAILED); throw new ApiException(ResultCode.FAILED);

View File

@ -34,6 +34,7 @@ import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.enums.WxApiType; import com.ijpay.wxpay.enums.WxApiType;
import com.ijpay.wxpay.model.RefundModel; import com.ijpay.wxpay.model.RefundModel;
import com.ijpay.wxpay.model.UnifiedOrderModel; import com.ijpay.wxpay.model.UnifiedOrderModel;
import com.lkl.laop.sdk.LKLSDK;
import com.suisung.mall.common.api.*; import com.suisung.mall.common.api.*;
import com.suisung.mall.common.constant.ConfigConstant; import com.suisung.mall.common.constant.ConfigConstant;
import com.suisung.mall.common.domain.OssDto; import com.suisung.mall.common.domain.OssDto;
@ -47,7 +48,6 @@ import com.suisung.mall.common.modules.order.ShopOrderInfo;
import com.suisung.mall.common.modules.order.ShopOrderReturn; import com.suisung.mall.common.modules.order.ShopOrderReturn;
import com.suisung.mall.common.modules.pay.*; import com.suisung.mall.common.modules.pay.*;
import com.suisung.mall.common.modules.pay.dto.ItemActivityInfoDTO; import com.suisung.mall.common.modules.pay.dto.ItemActivityInfoDTO;
import com.suisung.mall.common.pojo.res.PayAccRespFieldsRes;
import com.suisung.mall.common.utils.CheckUtil; import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.common.utils.I18nUtil; import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.LogUtil; import com.suisung.mall.common.utils.LogUtil;
@ -57,6 +57,7 @@ import com.suisung.mall.pay.entity.H5SceneInfo;
import com.suisung.mall.pay.entity.PayTypeBean; import com.suisung.mall.pay.entity.PayTypeBean;
import com.suisung.mall.pay.mapper.PayUserPayMapper; import com.suisung.mall.pay.mapper.PayUserPayMapper;
import com.suisung.mall.pay.service.*; import com.suisung.mall.pay.service.*;
import com.suisung.mall.pay.utils.LakalaUtil;
import io.seata.spring.annotation.GlobalTransactional; import io.seata.spring.annotation.GlobalTransactional;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -423,7 +424,8 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
case WX_XCX: case WX_XCX:
// 小程序 appId // 小程序 appId
String wechat_xcx_app_id = accountBaseConfigService.getConfig("wechat_xcx_app_id"); String wechat_xcx_app_id = accountBaseConfigService.getConfig("wechat_xcx_app_id");
wxJSAPIPay(request, response, trade_row, payTypeBean.getOpenid(), wechat_xcx_app_id); // wxJSAPIPay(request, response, trade_row, payTypeBean.getOpenid(), wechat_xcx_app_id);
lakalaJSAPIPay(request, response, trade_row, payTypeBean.getOpenid(), wechat_xcx_app_id);
break; break;
case WX_JSAPI: case WX_JSAPI:
UserDto user = getCurrentUser(); UserDto user = getCurrentUser();
@ -563,15 +565,17 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
result.put("data", packageParams); result.put("data", packageParams);
result.put("statusCode", 200); result.put("statusCode", 200);
data.put("data", result); data.put("data", result);
logger.info("老流程调起支付的参数:{}", JSONUtil.toJsonStr(data));
setResponseBody(response, data); setResponseBody(response, data);
} }
/** /**
* 拉卡拉第三方支付 * 拉卡拉第三方 JSAPI 下单返回调起微信支付的收银台参数
* *
* @param request * @param request
* @param response * @param response
* @param payConsumeTrade * @param payConsumeTrade 支付消费交易实例
* @param openId * @param openId
* @param appId * @param appId
*/ */
@ -584,66 +588,50 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
String total_fee = StrUtil.toString(NumberUtil.mul(NumberUtil.round(payConsumeTrade.getTrade_payment_amount(), 2), 100).intValue()); String total_fee = StrUtil.toString(NumberUtil.mul(NumberUtil.round(payConsumeTrade.getTrade_payment_amount(), 2), 100).intValue());
// 微信支付配置值 // 微信支付配置值
WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig(); WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
logger.info("微信支付配置值:{}", JSONUtil.toJsonStr(wxPayApiConfig));
// https://mall.gpxscs.cn/mobile/pay/index // https://mall.gpxscs.cn/mobile/pay/index
String domain = wxPayApiConfig.getDomain(); String domain = wxPayApiConfig.getDomain();
String requestIP = IpKit.getRealIp(request); String requestIP = IpKit.getRealIp(request);
String notifyUrl = domain + "/wxPay_notify_url"; String notifyUrl = domain + "/lkl_wxPay_notify_url";
// 初始化拉卡拉支付配置 // 拉卡拉预支付返回参数
PayAccRespFieldsRes field = lakalaService.transPreOrder(null, null, cn.hutool.json.JSONObject lakalaRespJSON = lakalaService.transPreOrder(null, null,
appId, openId, out_trade_no, subject, total_fee, appId, openId, out_trade_no, subject, total_fee,
notifyUrl, notifyUrl,
requestIP, trade_remark); requestIP, trade_remark);
// 统一下单请求参数构建 // logger.info("拉卡拉预支付返回参数:{}", JSONUtil.toJsonStr(field));
Map<String, String> params = UnifiedOrderModel.builder()
.appid(field.getApp_id())
.mch_id(field.getSub_mch_id())
.openid(openId)
.out_trade_no(out_trade_no)
.total_fee(total_fee) // 订单总金额单位为分
.notify_url(notifyUrl)
.trade_type(TradeType.JSAPI.getTradeType())
.spbill_create_ip(requestIP)
.time_start(DateUtil.format(new Date(), "yyyyMMddHHmmss"))
.nonce_str(field.getNonce_str())
.body(subject) // 商品描述
.goods_tag(trade_remark)// 订单优惠标记
.attach("") //自定义数据说明
.build()
.createSign(wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256);
String xmlResult = WxPayApi.pushOrder(false, params);
Map<String, String> resultMap = WxPayKit.xmlToMap(xmlResult);
String returnCode = resultMap.get("return_code");
String returnMsg = resultMap.get("return_msg");
Map data = new HashMap(); Map data = new HashMap();
data.put("code", 1); data.put("code", 1);
data.put("data", new Object()); data.put("data", new Object());
data.put("message", returnMsg);
if (!WxPayKit.codeIsOk(returnCode)) { if (lakalaRespJSON == null || !lakalaRespJSON.getStr("code").equals("BBS00000")) {
data.put("message", lakalaRespJSON.getStr("msg"));
setResponseBody(response, data); setResponseBody(response, data);
return; return;
} }
String resultCode = resultMap.get("result_code"); // 统一下单请求参数构建
if (!WxPayKit.codeIsOk(resultCode)) { cn.hutool.json.JSONObject field = (cn.hutool.json.JSONObject) lakalaRespJSON.getByPath("resp_data.acc_resp_fields");
setResponseBody(response, data); Map<String, String> params = new HashMap();
return; params.put("package", "prepay_id=" + field.getStr("prepay_id"));
} params.put("nonceStr", field.getStr("nonce_str"));
params.put("timeStamp", field.getStr("time_stamp"));
// 预支付订单 Id params.put("paySign", field.getStr("pay_sign"));
String prepayId = resultMap.get("prepay_id"); params.put("signType", field.getStr("sign_type"));
Map<String, Object> packageParams = Convert.toMap(String.class, Object.class, WxPayKit.prepayIdCreateSign(prepayId, appId, wxPayApiConfig.getPartnerKey(), SignType.HMACSHA256)); params.put("appId", field.getStr("app_id"));
data.put("status", 200);
Map result = new HashMap(); Map result = new HashMap();
result.put("data", packageParams); result.put("data", params);
result.put("statusCode", 200); result.put("statusCode", 200);
data.put("status", 200);
data.put("data", result); data.put("data", result);
data.put("message", "OK");
logger.info("拉卡拉调起支付的参数:{}", JSONUtil.toJsonStr(data));
setResponseBody(response, data); setResponseBody(response, data);
} }
@ -1145,8 +1133,10 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
String trade_type = ""; String trade_type = "";
Map<String, String> params = new HashMap<>(); Map<String, String> params = new HashMap<>();
if (ObjectUtil.equal("wx_native", payment_channel_code)) { if (ObjectUtil.equal("wx_native", payment_channel_code)) {
String xmlMsg = HttpKit.readData(request);
params = WxPayKit.xmlToMap(xmlMsg); String dataStr = HttpKit.readData(request);
params = WxPayKit.xmlToMap(dataStr);
trade_type = params.get("trade_type"); trade_type = params.get("trade_type");
order_id = params.get("out_trade_no"); order_id = params.get("out_trade_no");
} }
@ -1200,7 +1190,7 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
} }
} }
} catch (Exception e) { } catch (Exception e) {
LogUtil.error("验签异常!" + e.getMessage(), e); logger.error("验签异常{}", e);
return notifyMsg(false, payment_channel_code, "验签异常!"); return notifyMsg(false, payment_channel_code, "验签异常!");
} }
@ -1261,6 +1251,92 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
} }
@Override
public String lklNotifyUrl(HttpServletRequest request) {
Map<String, String> params;
try {
lakalaService.doInit();
// spring-boot 2.x 直接调用读取请求体并验签方法,返回请求体
String body = LKLSDK.notificationHandle(request);
if (StrUtil.isBlank(body)) {
return lklNotifyMsg(false, "验签失败!");
}
// logger.debug("支付回调 body 数据:{}", body);
// 拉卡拉返回 json 格式的 数据
params = Convert.toMap(String.class, String.class, JSONUtil.parseObj(body));
String order_id = getParameter("out_trade_no");
if(StrUtil.isBlank(order_id)){
order_id = params.getOrDefault("out_trade_no","");
}
logger.debug("支付回调 params 数据:{}", params);
String authorization = request.getHeader("Authorization");
// logger.debug("支付回调 Authorization 数据:{}", authorization);
Map<String, String> authMap = LakalaUtil.getLakalaAuthorizationMap(authorization);
if (authMap != null && authMap.get("signature") != null) {
params.put("sign", authMap.get("signature"));
}
// 基于安全考虑检测支付模式及数据
// 判断是门店 店铺 平台
Integer payment_store_id = 0;
Integer payment_chain_id = 0;
if (!accountBaseConfigService.getTradeModePlantform()) {
// 不可以联合支付
QueryWrapper<PayConsumeTrade> tradeQueryWrapper = new QueryWrapper<>();
tradeQueryWrapper.eq("order_id", order_id);
PayConsumeTrade trade_row_tmp = payConsumeTradeService.findOne(tradeQueryWrapper);
// 固定死 是门店收银还是店铺收银 统一店铺收银
payment_store_id = trade_row_tmp.getStore_id();
}
QueryWrapper<PayPaymentChannel> channelQueryWrapper = new QueryWrapper<>();
channelQueryWrapper.eq("payment_channel_code", "lakala");
PayPaymentChannel payPaymentChannel = payPaymentChannelService.findOne(channelQueryWrapper);
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);
BigDecimal zero = BigDecimal.ZERO;
// 判断是否联合支付
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());
}
if (!payConsumeDepositService.processDeposit(notify_row, zero, zero, zero, zero, zero)) {
log.error("支付失败!");
return lklNotifyMsg(false, "支付失败!");
}
transactionManager.commit(transactionStatus);
} catch (Exception e) {
transactionManager.rollback(transactionStatus);
log.error("支付失败,错误信息:", e);
throw new ApiException(e.getMessage());
}
return lklNotifyMsg(true, "");
} catch (Exception e) {
logger.error("验签发生异常:" + e.getMessage() + " {}", e);
return lklNotifyMsg(false, "验签发生异常!");
}
}
/** /**
* 获取支付异步请求返回值多种支付方式 * 获取支付异步请求返回值多种支付方式
* *
@ -1288,6 +1364,13 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
return msg; return msg;
} }
public String lklNotifyMsg(boolean code, String message) {
Map<String, String> retData = new HashMap<>();
retData.put("code", code ? "SUCCESS" : "FAIL");
retData.put("message", message);
return JSONUtil.toJsonStr(retData);
}
/** /**
* 创建支付回调参数对象 * 创建支付回调参数对象
@ -1319,6 +1402,9 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
case "wx_native": case "wx_native":
getWxNotifyParams(params, payment_channel_id, notify_row); getWxNotifyParams(params, payment_channel_id, notify_row);
break; break;
case "lakala":
getLakalaWxNotifyParams(params, payment_channel_id, notify_row);
break;
} }
return notify_row; return notify_row;
@ -1403,22 +1489,48 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
String deposit_buyer_id = params.get("openid"); String deposit_buyer_id = params.get("openid");
String deposit_service = ObjectUtil.defaultIfNull(params.get("trade_type"), ""); String deposit_service = ObjectUtil.defaultIfNull(params.get("trade_type"), "");
String deposit_sign = ObjectUtil.defaultIfNull(params.get("sign"), ""); String deposit_sign = ObjectUtil.defaultIfNull(params.get("sign"), "");
Date deposit_gmt_payment = Convert.toDate(params.get("time_end"), new Date()); Date deposit_gmt_payment = Convert.toDate(params.get("time_end"), new Date());
notify_row.setDeposit_trade_no(deposit_trade_no); notify_row.setDeposit_trade_no(deposit_trade_no);
notify_row.setDeposit_quantity(deposit_quantity); notify_row.setDeposit_quantity(deposit_quantity);
notify_row.setDeposit_notify_time(new Date());
notify_row.setDeposit_seller_id(deposit_seller_id); notify_row.setDeposit_seller_id(deposit_seller_id);
notify_row.setDeposit_is_total_fee_adjust(deposit_is_total_fee_adjust); notify_row.setDeposit_is_total_fee_adjust(deposit_is_total_fee_adjust);
notify_row.setDeposit_total_fee(deposit_total_fee); notify_row.setDeposit_total_fee(deposit_total_fee);
notify_row.setDeposit_price(deposit_price); notify_row.setDeposit_price(deposit_price);
notify_row.setDeposit_buyer_id(deposit_buyer_id); notify_row.setDeposit_buyer_id(deposit_buyer_id);
notify_row.setDeposit_gmt_payment(deposit_gmt_payment); notify_row.setDeposit_gmt_payment(deposit_gmt_payment);
notify_row.setDeposit_payment_type(StateCode.PAYMENT_TYPE_ONLINE);
notify_row.setDeposit_service(deposit_service); notify_row.setDeposit_service(deposit_service);
notify_row.setDeposit_sign(deposit_sign); notify_row.setDeposit_sign(deposit_sign);
notify_row.setDeposit_extra_param(JSONUtil.toJsonStr(notify_row)); notify_row.setDeposit_extra_param(JSONUtil.toJsonStr(notify_row));
notify_row.setPayment_channel_id(payment_channel_id); notify_row.setPayment_channel_id(payment_channel_id);
notify_row.setDeposit_payment_type(StateCode.PAYMENT_TYPE_ONLINE);
notify_row.setDeposit_notify_time(new Date());
}
public void getLakalaWxNotifyParams(Map<String, String> params, Integer payment_channel_id, PayConsumeDeposit notify_row) {
BigDecimal deposit_total_fee = NumberUtil.div(Convert.toBigDecimal(params.get("total_amount")), 100);
String deposit_sign = ObjectUtil.defaultIfNull(params.get("sign"), "");
Date deposit_gmt_payment = Convert.toDate(params.get("trade_time"), new Date());
notify_row.setDeposit_trade_no(params.get("trade_no"));
notify_row.setOrder_id(params.get("out_trade_no"));
notify_row.setDeposit_quantity(1);
notify_row.setDeposit_seller_id(params.get("sub_mch_id"));
notify_row.setDeposit_is_total_fee_adjust(0);
notify_row.setDeposit_total_fee(deposit_total_fee);
notify_row.setDeposit_price(BigDecimal.ZERO);
notify_row.setDeposit_buyer_id(params.get("user_id2"));// openid
notify_row.setDeposit_gmt_payment(deposit_gmt_payment);
notify_row.setDeposit_service("JSAPI");
notify_row.setDeposit_sign(deposit_sign);
notify_row.setDeposit_sign_type("SHA256withRSA");
notify_row.setDeposit_extra_param(JSONUtil.toJsonStr(notify_row));
notify_row.setPayment_channel_id(payment_channel_id);
notify_row.setDeposit_payment_type(StateCode.PAYMENT_TYPE_ONLINE);
notify_row.setDeposit_notify_time(new Date());
} }
/** /**

View File

@ -0,0 +1,180 @@
/*
* 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.pay.utils;
import cn.hutool.core.util.StrUtil;
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.apache.commons.lang3.StringUtils;
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 {
/**
* 读取证书文件内容
*
* @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);
}
}
/**
* 解析请求头中的认证签名等信息
* 参考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<String, String> getLakalaAuthorizationMap(String authorization) {
Map<String, String> 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;
}
}

View File

@ -117,8 +117,6 @@ lakala:
server_url: https://s2.lakala.com server_url: https://s2.lakala.com
#应用Id #应用Id
app_id: OP10000439 app_id: OP10000439
#商户号
merchant_no: 8226330599900LN
#商户证书序列号 #商户证书序列号
serial_no: 1737359895636 serial_no: 1737359895636
#商户证书 #商户证书
@ -127,5 +125,7 @@ lakala:
api_pri_key_path: payKey/lakala/prod/api_private_key.pem api_pri_key_path: payKey/lakala/prod/api_private_key.pem
#拉卡拉平台证书 #拉卡拉平台证书
lkl_platform_cer_path: payKey/lakala/prod/lkl_platform.cer lkl_platform_cer_path: payKey/lakala/prod/lkl_platform.cer
#商户号
merchant_no: 8226330599900LN
#终端号码M0780629B2B收银台 M0780798专业化扫码 #终端号码M0780629B2B收银台 M0780798专业化扫码
term_no: M0780798 term_no: M0780798

View File

@ -131,45 +131,9 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
queryWrapper.eq("printer_sn", record.getPrinter_sn()); queryWrapper.eq("printer_sn", record.getPrinter_sn());
ShopStorePrinter existRecord = getOne(queryWrapper); ShopStorePrinter existRecord = getOne(queryWrapper);
if (existRecord != null && existRecord.getPrinter_id() > 0) { if (existRecord != null && existRecord.getPrinter_id() > 0) {
// 打印机已经存在的情况
// if (existRecord.getFlag() == CommonConstant.Disable2) {
// // 打印机没有绑定飞鹅平台
// UpdateWrapper<ShopStorePrinter> updateWrapper = new UpdateWrapper<ShopStorePrinter>();
// updateWrapper.eq("printer_id", existRecord.getPrinter_id());
// updateWrapper.set("updated_by", userId);
// updateWrapper.set("updated_at", new Date());
//
// // 往厂商添加打印机
// // "922441475#r6ZXPvHH#核销柜台";
// boolean success = feieUtil.addPrinter(String.format("%s#%s#%s", existRecord.getPrinter_sn(), existRecord.getPrinter_key(), existRecord.getPrinter_name()));
// if (success) {
// updateWrapper.set("flag", CommonConstant.Enable);
// }
//
// update(updateWrapper);
// String msg = "添加成功";
// if (!success) {
// msg = msg + ",但未绑定成功,请检查打印机编号和密钥是否正确。";
// }
//
// return CommonResult.success(null, msg);
// }
return CommonResult.success(null, "打票机已添加,请勿重复操作"); return CommonResult.success(null, "打票机已添加,请勿重复操作");
} }
// String msg = "添加成功";
// 往厂商添加打印机
// "922441475#r6ZXPvHH#核销柜台";
// boolean success = feieUtil.addPrinter(String.format("%s#%s#%s", record.getPrinter_sn(), record.getPrinter_key(), record.getPrinter_name()));
// if (success) {
//
// if (!success) {
// msg = msg + ",但打印机绑定未成功,请检查打印机编号和密钥是否正确。";
// }
// record.setFlag(CommonConstant.Enable);
// }
if (add(record)) { if (add(record)) {
return CommonResult.success(null, "添加成功"); return CommonResult.success(null, "添加成功");
} }
@ -220,19 +184,6 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
updateWrapper.set("updated_by", userId); updateWrapper.set("updated_by", userId);
String msg = "修改成功"; String msg = "修改成功";
// if (existRecord.getPrinter_sn().equals(record.getPrinter_sn())) {
// // sn 没有变化不更新 sn
// if (CommonConstant.Disable2.equals(existRecord.getFlag())) {
// // 往厂商添加打印机
// boolean success = feieUtil.addPrinter(String.format("%s#%s#%s", record.getPrinter_sn(), record.getPrinter_key(), record.getPrinter_name()));
// if (success) {
// updateWrapper.set("flag", CommonConstant.Enable);
// } else {
// msg = msg + ",但打印机绑定未成功,请检查打印机编号和密钥是否正确。";
// }
// }
// } else {
if (!existRecord.getPrinter_sn().equals(record.getPrinter_sn())) { if (!existRecord.getPrinter_sn().equals(record.getPrinter_sn())) {
// 更改了 sn并且 sn 从未添加过打票机不在门店内的 // 更改了 sn并且 sn 从未添加过打票机不在门店内的
QueryWrapper<ShopStorePrinter> queryWrapper2 = new QueryWrapper<>(); QueryWrapper<ShopStorePrinter> queryWrapper2 = new QueryWrapper<>();
@ -364,14 +315,14 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
// 获取订单包含所有所需的字段参考实体类ShopStoreOrderPrintVO ShopStoreOrderProductPrintVO // 获取订单包含所有所需的字段参考实体类ShopStoreOrderPrintVO ShopStoreOrderProductPrintVO
if (StrUtil.isBlank(orderId)) { if (StrUtil.isBlank(orderId)) {
logger.info("订单为空,无法打印小票。"); logger.error("订单为空,无法打印小票。");
return false; return false;
} }
// 获取打印的订单信息判断订单支付状态已支付 // 获取打印的订单信息判断订单支付状态已支付
Map<String, Object> binding = shopOrderBaseService.getOrderPrintInfo(orderId, StateCode.ORDER_PAID_STATE_YES); Map<String, Object> binding = shopOrderBaseService.getOrderPrintInfo(orderId, StateCode.ORDER_PAID_STATE_YES);
if (binding == null) { if (binding == null) {
logger.info("订单{}信息无法获取,无法打印小票。", orderId); logger.error("订单{}信息无法获取,无法打印小票。", orderId);
return false; return false;
} }
@ -380,7 +331,7 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
List<ShopStorePrinter> printerList = selectPrinterList(storeId); List<ShopStorePrinter> printerList = selectPrinterList(storeId);
if (CollUtil.isEmpty(printerList)) { if (CollUtil.isEmpty(printerList)) {
// 店铺没有打印机不再往下执行打印工作 // 店铺没有打印机不再往下执行打印工作
logger.info("店铺{}未添加打票机,无法打印小票。", storeId); logger.error("店铺{}未添加打票机,无法打印小票。", storeId);
return false; return false;
} }
@ -388,14 +339,14 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
ShopStorePrinterTemplate template = shopStorePrinterTemplateService.getShopStorePrinterTemplateInner(storeId, StateCode.PRINTER_TEMP_CATE_ORDER); ShopStorePrinterTemplate template = shopStorePrinterTemplateService.getShopStorePrinterTemplateInner(storeId, StateCode.PRINTER_TEMP_CATE_ORDER);
if (template == null || StrUtil.isBlank(template.getTemplate_name()) || StrUtil.isBlank(template.getTemplate_value())) { if (template == null || StrUtil.isBlank(template.getTemplate_name()) || StrUtil.isBlank(template.getTemplate_value())) {
// 店铺没有打印模版 // 店铺没有打印模版
logger.info("店铺{}未添加打票机打印模版,无法打印小票。", storeId); logger.error("店铺{}未添加打票机打印模版,无法打印小票。", storeId);
return false; return false;
} }
// 生成打印内容暂时飞鹅打票机的内容 // 生成打印内容暂时飞鹅打票机的内容
String printContent = FreeMakerUtils.processTemplate(template.getTemplate_name(), template.getTemplate_value(), binding); String printContent = FreeMakerUtils.processTemplate(template.getTemplate_name(), template.getTemplate_value(), binding);
if (StrUtil.isBlank(printContent)) { if (StrUtil.isBlank(printContent)) {
logger.info("订单{}信息模版渲染异常,无法打印小票。", orderId); logger.error("订单{}信息模版渲染异常,无法打印小票。", orderId);
return false; return false;
} }
@ -408,7 +359,7 @@ public class ShopStorePrinterServiceImpl extends BaseServiceImpl<ShopStorePrinte
List<Pair<String, String>> respList = feieUtil.printContentByList(printerSnList, printContent); List<Pair<String, String>> respList = feieUtil.printContentByList(printerSnList, printContent);
if (respList == null || CollUtil.isEmpty(respList)) { if (respList == null || CollUtil.isEmpty(respList)) {
// 只需一台打印机能打印就算成功了 // 只需一台打印机能打印就算成功了
logger.info("订单{}信息打印,调用飞鹅打印机打印失败。", orderId); logger.error("订单{}信息打印,调用飞鹅打印机打印失败。", orderId);
return false; return false;
} }

View File

@ -179,8 +179,6 @@ public class SyncThirdDataServiceImpl implements SyncThirdDataService {
} }
} }
// 处理商品类型
} }
ShopBaseProductCategory productCategoryTemp = productCategoryService.getCategoryByName(list.get(i).getParent_id(), list.get(i).getCategory_name(), list.get(i).getStore_id()); ShopBaseProductCategory productCategoryTemp = productCategoryService.getCategoryByName(list.get(i).getParent_id(), list.get(i).getCategory_name(), list.get(i).getStore_id());