拉卡拉支付,增加合单交易方法流程

This commit is contained in:
Jack 2025-08-25 20:05:30 +08:00
parent a1e31e5ca4
commit a01b2573f7
11 changed files with 575 additions and 102 deletions

View File

@ -22,6 +22,7 @@ import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.modules.store.ShopStoreEmployee;
import com.suisung.mall.common.modules.store.ShopStoreEmployeeKefu;
import com.suisung.mall.common.modules.store.ShopStoreEmployeeRightsGroup;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
@ -300,8 +301,13 @@ public interface ShopService {
boolean lklPayNotifyUpdateShopOrderLkl(@RequestBody JSONObject lklPayNotifyDataJson);
@PostMapping(value = "/mobile/shop/oss/upload",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@PostMapping(value = "/mobile/shop/oss/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
CommonResult uploadFile(@RequestPart(name = "upfile") MultipartFile file);
@ApiOperation(value = "获取订单的运费", notes = "获取订单的运费")
@PostMapping(value = "/admin/shop/shop-order-data/getOrderShippingFee")
BigDecimal getOrderShippingFee(@RequestParam(name = "order_id") String order_id);
}

View File

@ -9,6 +9,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.TreeMap;
@ -124,14 +126,279 @@ public class JsonUtil {
return new JSONObject(sortedMap);
}
public static void main(String[] args) {
// JSONObject jsonObject = new JSONObject();
// jsonObject.put("b", "2");
// jsonObject.put("a", "1");
// jsonObject.put("c", "3");
// jsonObject.put("a", "2");
// jsonObject.put("A", "1");
// System.out.println(sortJsonObjectByKeyAsc(jsonObject));
JSONObject jsonObject = new JSONObject();
jsonObject.put("b", "2");
jsonObject.put("a", "1");
jsonObject.put("c", "3");
jsonObject.put("a", "2");
jsonObject.put("A", "1");
System.out.println(sortJsonObjectByKeyAsc(jsonObject));
jsonObject.put("userId", "2323");
System.out.println(getJsonValueSmart(jsonObject, "userId"));
System.out.println(getJsonValueSmart(jsonObject, "user_id"));
System.out.println(getJsonValueSmart(jsonObject, "UserId"));
// // 测试用例
// String[] testNames = {"user_name", "userName", "UserName",
// "order_id", "orderId", "OrderId",
// "product_category", "productCategory", "ProductCategory"};
//
// System.out.println("命名规则转换测试:");
// for (String name : testNames) {
// System.out.println("输入: " + name);
// String[] converted = convertNaming(name);
// System.out.println("输出: " + java.util.Arrays.toString(converted));
// System.out.println();
// }
}
/**
* 从JSON对象中获取指定类型的值按优先级顺序尝试多种命名格式
* 当查找"user_id"会依次尝试查找"user_id""userId""USERID"等不同格式
*
* @param jsonObject JSON对象
* @param key 要查找的键名
* @param clazz 目标类型Class
* @param <T> 目标类型
* @return 指定类型的值如果都获取不到则返回默认值
*/
public static <T> T getJsonValueSmart(JSONObject jsonObject, String key, Class<T> clazz) {
if (jsonObject == null || StringUtils.isBlank(key) || clazz == null) {
return getDefaultInstance(clazz);
}
// 根据要查找的key生成各种可能的格式
String[] searchKeys = convertNaming(key);
// 按顺序尝试查找
for (String searchKey : searchKeys) {
if (jsonObject.containsKey(searchKey)) {
Object value = jsonObject.get(searchKey);
return convertValue(value, clazz);
}
}
return getDefaultInstance(clazz);
}
/**
* 获取指定类型默认实例
*
* @param clazz 类型Class
* @param <T> 类型
* @return 默认实例
*/
@SuppressWarnings("unused")
private static <T> T getDefaultInstance(Class<T> clazz) {
if (clazz == null) {
return null;
}
if (clazz == String.class) {
return clazz.cast("");
} else if (clazz == Integer.class || clazz == int.class) {
return clazz.cast(0);
} else if (clazz == Long.class || clazz == long.class) {
return clazz.cast(0L);
} else if (clazz == Float.class || clazz == float.class) {
return clazz.cast(0.0f);
} else if (clazz == Double.class || clazz == double.class) {
return clazz.cast(0.0);
} else if (clazz == Boolean.class || clazz == boolean.class) {
return clazz.cast(false);
} else if (clazz == BigDecimal.class) {
return clazz.cast(BigDecimal.ZERO);
} else if (clazz == BigInteger.class) {
return clazz.cast(BigInteger.ZERO);
}
return null;
}
/**
* 转换值到指定类型
*
* @param value 原始值
* @param clazz 目标类型Class
* @param <T> 目标类型
* @return 转换后的值
*/
private static <T> T convertValue(Object value, Class<T> clazz) {
if (value == null) {
return getDefaultInstance(clazz);
}
if (clazz.isInstance(value)) {
return clazz.cast(value);
}
String stringValue = String.valueOf(value);
try {
if (clazz == String.class) {
return clazz.cast(stringValue);
} else if (clazz == Integer.class || clazz == int.class) {
return clazz.cast(Integer.valueOf(stringValue));
} else if (clazz == Long.class || clazz == long.class) {
return clazz.cast(Long.valueOf(stringValue));
} else if (clazz == Float.class || clazz == float.class) {
return clazz.cast(Float.valueOf(stringValue));
} else if (clazz == Double.class || clazz == double.class) {
return clazz.cast(Double.valueOf(stringValue));
} else if (clazz == Boolean.class || clazz == boolean.class) {
return clazz.cast(Boolean.valueOf(stringValue));
} else if (clazz == BigDecimal.class) {
return clazz.cast(new BigDecimal(stringValue));
} else if (clazz == BigInteger.class) {
return clazz.cast(new BigInteger(stringValue));
}
} catch (NumberFormatException e) {
// 转换失败时返回默认值
return getDefaultInstance(clazz);
}
return getDefaultInstance(clazz);
}
/**
* 保持原有的字符串获取方法兼容性
*
* @param jsonObject JSON对象
* @param key 要查找的键名
* @return 字符串值如果都获取不到则返回空字符串
*/
public static String getJsonValueSmart(JSONObject jsonObject, String key) {
return getJsonValueSmart(jsonObject, key, String.class);
}
/**
* 根据输入的变量名蛇形命名小驼峰命名或大驼峰命名中的任意一种生成其他三种命名规则
*
* @param variableName 输入的变量名
* @return 包含三种命名规则的字符串数组 [蛇形命名, 小驼峰命名, 大驼峰命名]
*/
public static String[] convertNaming(String variableName) {
if (variableName == null || variableName.isEmpty()) {
return new String[]{"", "", ""};
}
// 提取基础名称统一转换为驼峰命名形式
String camelCaseName = toCamelCase(variableName);
// 生成三种命名规则
String[] result = new String[3];
result[0] = toSnakeCase(camelCaseName); // 蛇形命名下划线分隔
result[1] = toLowerCamelCase(camelCaseName); // 小驼峰命名
result[2] = toUpperCamelCase(camelCaseName); // 大驼峰命名
return result;
}
/**
* 将任意命名转换为标准驼峰命名首字母小写
*
* @param name 输入名称
* @return 标准驼峰命名
*/
private static String toCamelCase(String name) {
if (name == null || name.isEmpty()) {
return name;
}
// 如果是蛇形命名包含下划线
if (name.contains("_")) {
return snakeToCamel(name);
}
// 如果是全大写命名
if (name.equals(name.toUpperCase()) && !name.equals(name.toLowerCase())) {
return name.toLowerCase();
}
// 如果首字母大写大驼峰命名
if (Character.isUpperCase(name.charAt(0))) {
return name.substring(0, 1).toLowerCase() + name.substring(1);
}
// 已经是小驼峰命名或其他形式
return name;
}
/**
* 将蛇形命名转换为驼峰命名
*
* @param snakeCase 蛇形命名
* @return 驼峰命名
*/
private static String snakeToCamel(String snakeCase) {
StringBuilder result = new StringBuilder();
boolean capitalizeNext = false;
for (char c : snakeCase.toCharArray()) {
if (c == '_') {
capitalizeNext = true;
} else if (capitalizeNext) {
result.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
result.append(Character.toLowerCase(c));
}
}
return result.toString();
}
/**
* 将驼峰命名转换为蛇形命名
*
* @param camelCase 驼峰命名
* @return 蛇形命名
*/
private static String toSnakeCase(String camelCase) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < camelCase.length(); i++) {
char c = camelCase.charAt(i);
// 如果是大写字母且不是第一个字符则在前面添加下划线
if (Character.isUpperCase(c) && i > 0) {
result.append('_');
}
result.append(Character.toLowerCase(c));
}
return result.toString();
}
/**
* 转换为小驼峰命名首字母小写
*
* @param name 名称
* @return 小驼峰命名
*/
private static String toLowerCamelCase(String name) {
if (name == null || name.isEmpty()) {
return name;
}
// 首字母小写其余保持不变
return name.substring(0, 1).toLowerCase() + name.substring(1);
}
/**
* 转换为大驼峰命名首字母大写
*
* @param name 名称
* @return 大驼峰命名
*/
private static String toUpperCamelCase(String name) {
if (name == null || name.isEmpty()) {
return name;
}
// 首字母大写其余保持不变
return name.substring(0, 1).toUpperCase() + name.substring(1);
}
}

View File

@ -41,6 +41,7 @@ public interface LakalaPayService {
* @param merchantNo 商户号(商城商家)
* @param termNo 终端号
* @param agentMerchantNo 运费代理商商户号
* @param agentTermNo 运费代理商终端号
* @param xcxAppId 小程序appid
* @param openId openid
* @param storeId 店铺Id
@ -51,7 +52,7 @@ public interface LakalaPayService {
* @param notifyURL 回调地址
* @param requestIP 请求ip
**/
JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark);
JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String agentTermNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark);
/**

View File

@ -24,7 +24,9 @@ import com.lkl.laop.sdk.request.model.V3LabsTradePreorderWechatBus;
import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.feignService.ShopService;
import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.common.utils.RestTemplateHttpUtil;
import com.suisung.mall.pay.service.LakalaPayService;
import com.suisung.mall.pay.service.LklLedgerMemberService;
import com.suisung.mall.pay.service.LklLedgerMerReceiverBindService;
@ -40,6 +42,7 @@ import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
@Slf4j
@ -47,6 +50,9 @@ import javax.servlet.http.HttpServletRequest;
public class LakalaPayServiceImpl implements LakalaPayService {
private static final boolean init = false;
private static final String lklSuccessCode = "000000";
private static final String lklPaySuccessCode = "BBS00000";
@Value("${lakala.server_url}")
private String serverUrl; //服务地址
@Value("${lakala.app_id}")
@ -188,6 +194,7 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param merchantNo 商户号(商城商家)
* @param termNo 终端号
* @param agentMerchantNo 运费代理商商户号
* @param agentTermNo 运费代理商终端号
* @param xcxAppId 小程序appid
* @param openId openid
* @param storeId 店铺Id
@ -197,76 +204,102 @@ public class LakalaPayServiceImpl implements LakalaPayService {
* @param agentAmount 代理商收取金额
* @param notifyURL 回调地址
* @param requestIP 请求ip
* @param remark
* @param remark 备注
**/
@Override
public JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark) {
// 1. 配置初始化
initLKLSDK();
if (StrUtil.isBlank(merchantNo) || StrUtil.isBlank(termNo)) {
throw new ApiException("缺少商户号或终端号!");
public JSONObject lklTransMergePreOrder(String merchantNo, String termNo, String agentMerchantNo, String agentTermNo, String xcxAppId, String openId, String storeId, String orderId, String subject, String totalAmount, String agentAmount, String notifyURL, String requestIP, String remark) {
// 2. 参数校验
if (StrUtil.isBlank(merchantNo)) {
throw new ApiException("商家商户号不能为空!");
}
if (StrUtil.isBlank(agentMerchantNo)) {
throw new ApiException("缺少代理商商户号");
if (StrUtil.isBlank(termNo)) {
throw new ApiException("终端号不能为空");
}
//2. 装配数据
/*** 微信主扫场景示例 */
// 3. 校验其他必要参数
if (StrUtil.isBlank(orderId)) {
throw new ApiException("订单号不能为空!");
}
if (StrUtil.isBlank(totalAmount)) {
throw new ApiException("订单总金额不能为空!");
}
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:NATIVEALIPAY云闪付支持京东白条分期51:JSAPI微信公众号支付支付宝服务窗支付银联JS支付翼支付JS支付拉卡拉钱包支付71:微信小程序支付 61:APP支付微信APP支付
v3LabsTransPreorderWechatReq.setTransType("51");
v3LabsTransPreorderWechatReq.setTotalAmount(totalAmount); // 应支付金额单位
v3LabsTransPreorderWechatReq.setSettleType("1"); //0或者空常规结算方式如需接拉卡拉分账通需传1商户未开通分账之前切记不用上送此参数
v3LabsTransPreorderWechatReq.setNotifyUrl(notifyURL);
v3LabsTransPreorderWechatReq.setRemark(remark);
//地址位置信息
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
// wechatBus.setAttach(storeId); // 附加数据商户自定义数据在查询交易结果时原样返回
v3LabsTransPreorderWechatReq.setAccBusiFields(wechatBus);
// 如果不符合合单交易条件则走聚合主扫单笔交易
if (StrUtil.isBlank(agentMerchantNo) || StrUtil.isBlank(agentTermNo) || StrUtil.isBlank(agentAmount) || "0".equals(agentAmount)) {
log.info("不符合合单交易条件,转为单笔交易处理。商家商户号:{},代理商商户号:{},代理商终端号:{},代理商金额:{}",
merchantNo, agentMerchantNo, agentTermNo, agentAmount);
return lklTransPreOrder(merchantNo, termNo, xcxAppId, openId, storeId, orderId, subject, totalAmount, notifyURL, requestIP, remark);
}
try {
log.info("拉卡拉预下单请求参数:{}", JSONUtil.toJsonStr(v3LabsTransPreorderWechatReq));
// 4. 装配请求数据
JSONObject reqData = new JSONObject();
//3. 发送请求
String responseStr = LKLSDK.httpPost(v3LabsTransPreorderWechatReq);
log.info("拉卡拉预下单响应数据:{}", responseStr);
if (StrUtil.isBlank(responseStr)) {
return null;
}
// 基本交易信息
reqData.put("merchant_no", agentMerchantNo); // 商户号
reqData.put("term_no", termNo); // 终端号
reqData.put("out_trade_no", orderId); // 商户订单号
reqData.put("account_type", "WECHAT"); // 账户类型微信支付
reqData.put("trans_type", "51"); // 交易类型JSAPI
reqData.put("total_amount", totalAmount); // 总金额单位
reqData.put("subject", subject); // 订单标题
reqData.put("settle_type", "1"); // 结算类型分账类型
reqData.put("notify_url", notifyURL); // 异步通知地址
reqData.put("remark", remark); // 备注
JSONObject lakalaRespJSON = JSONUtil.parseObj(responseStr);
// 位置信息
JSONObject locationInfo = new JSONObject();
locationInfo.put("request_ip", requestIP);
reqData.put("location_info", locationInfo);
if (lakalaRespJSON != null && lakalaRespJSON.getStr("code").equals("BBS00000")) {
// 微信业务参数
JSONObject accBusiFields = new JSONObject();
accBusiFields.put("sub_appid", xcxAppId); // 小程序appid
accBusiFields.put("user_id", openId); // 用户openid
reqData.put("acc_busi_fields", accBusiFields);
// 分账信息
JSONObject outSplitInfo = new JSONObject();
outSplitInfo.put("out_sub_trade_no", orderId + "F"); // 子订单号
outSplitInfo.put("merchant_no", agentMerchantNo); // 分账商户号
outSplitInfo.put("term_no", agentTermNo); // 分账终端号
outSplitInfo.put("amount", agentAmount); // 分账金额
reqData.put("out_split_info", outSplitInfo);
// 5. 构造请求体
JSONObject reqBody = new JSONObject();
reqBody.put("req_time", DateTimeUtils.formatDateTime(LocalDateTime.now(), "yyyyMMddHHmmss"));
reqBody.put("version", "3.0");
reqBody.put("req_data", reqData);
// 6. 发送请求
String reqUrl = serverUrl + "/api/v3/labs/trans/merge/preorder";
log.info("拉卡拉合单预下单请求参数:{}", reqBody);
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
JSONObject lakalaRespJSON = RestTemplateHttpUtil.sendLklPost(reqUrl, header, reqBody, JSONObject.class);
log.debug("拉卡拉合单交易响应参数:{}", lakalaRespJSON);
// 7. 处理响应结果
if (ObjectUtil.isNotEmpty(lakalaRespJSON) && lklPaySuccessCode.equals(lakalaRespJSON.getStr("retCode"))) {
// 新增一个拉卡拉订单记录 shop_order_lkl
JSONObject lklPayReqAndRespJson = new JSONObject();
lklPayReqAndRespJson.put("req", JSONUtil.parseObj(v3LabsTransPreorderWechatReq));
lklPayReqAndRespJson.put("req", reqData);
lklPayReqAndRespJson.put("resp", lakalaRespJSON);
shopService.lklPayAddShopOrderLkl(lklPayReqAndRespJson);
}
//4. 响应
// 8. 返回响应结果
return lakalaRespJSON;
} catch (SDKException e) {
log.error("拉卡拉支付出错:", e);
throw new ApiException(I18nUtil._("支付失败!"), e);
} catch (Exception e) {
log.error("拉卡拉合单交易出错,订单号:{},错误信息:", orderId, e);
throw new ApiException("拉卡拉合单交易出错:" + e.getMessage(), e);
}
}

View File

@ -176,8 +176,6 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
} else {
payment_chain_id = payConsumeTrade.getChain_id();
}
// orderTitle = payConsumeTrade.getTrade_title();
}
// 获取支付渠道编码
@ -666,11 +664,29 @@ public class PayUserPayServiceImpl extends BaseServiceImpl<PayUserPayMapper, Pay
String notifyUrl = domain + "/lkl_wxPay_notify_url";
String storeIdStr = Convert.toStr(storeId);// 店铺Id
// 拉卡拉预支付返回参数
cn.hutool.json.JSONObject lakalaRespJSON = lakalaPayService.lklTransPreOrder(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no(),
appId, openId, storeIdStr, out_trade_no, subject, total_amt,
notifyUrl,
requestIP, trade_remark);
// TODO 判断订单有没有运费有运费请求合单交易没有运费请求聚合主扫交易
cn.hutool.json.JSONObject lakalaRespJSON = new cn.hutool.json.JSONObject();
BigDecimal shippingFee = shopService.getOrderShippingFee(out_trade_no);
logger.debug("预支付时,查到的订单{},运费:{}", out_trade_no, shippingFee);
if (shippingFee == null || shippingFee.compareTo(BigDecimal.ZERO) <= 0) {
// 没有运费
// 拉卡拉预支付返回参数
lakalaRespJSON = lakalaPayService.lklTransPreOrder(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no(),
appId, openId, storeIdStr, out_trade_no, subject, total_amt,
notifyUrl,
requestIP, trade_remark);
} else { // 有运费的情况
// 拉卡拉合单预支付返回参数
// TODO RMK 这里只有固定一个运费代理商代收运费以后代理商模块做好了要动态读取店铺的运费代理商
lakalaRespJSON = lakalaPayService.lklTransMergePreOrder(shopStoreBase.getLkl_merchant_no(), shopStoreBase.getLkl_term_no(),
"", "",
appId, openId, storeIdStr, out_trade_no, subject, total_amt,
"0",
notifyUrl,
requestIP, trade_remark);
}
// logger.debug("拉卡拉预支付返回参数:{}", JSONUtil.toJsonStr(field));

View File

@ -13,6 +13,8 @@ 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 com.suisung.mall.common.utils.RSAUtil;
import com.suisung.mall.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.core.io.ClassPathResource;
@ -30,6 +32,8 @@ import java.security.SignatureException;
import java.security.cert.*;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class LakalaUtil {
@ -75,6 +79,73 @@ public class LakalaUtil {
}
}
/**
* 获取配置文件内容
*
* @param fileName recource 文件夹下的路径palyKey/wx/lakala_public_key.cer
* @param keepLineBreaks 保留换行符
* @param stripPemHeaders 是否去除PEM格式的密钥头和尾 BEGGIN和END标签
* @return
*/
public static String getResourceFile(String fileName, boolean keepLineBreaks, boolean stripPemHeaders) {
StringBuilder stringBuilder = new StringBuilder();
try (InputStream inputStream = new ClassPathResource(fileName).getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
String line;
String endSeq = keepLineBreaks ? "\n" : "";
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append(endSeq);
}
if (stripPemHeaders) {
return stripPemHeaders(stringBuilder.toString());
}
return stringBuilder.toString();
} catch (IOException e) {
// 记录异常信息
log.error(e.getMessage());
return "";
}
}
/**
* 拉卡拉官方拼接拉卡拉header中的Authorization字段
* 参考文档https://o.lakala.com/#/home/document/detail?id=33
*
* @param privateKeyPath
* @param appId
* @param serialNo
* @param reqBody
* @return
*/
public static String genAuthorizationByPath(String privateKeyPath, String appId, String serialNo, String reqBody) {
if (StrUtil.isBlank(privateKeyPath) || StrUtil.isBlank(appId) || StrUtil.isBlank(serialNo) || StrUtil.isBlank(reqBody)) {
log.error("生产拉卡拉签名时缺少参数");
return "";
}
// 获取私钥证书内容证书一行去掉头尾注释
String privateKey = getResourceFile(privateKeyPath, false, true);
if (StrUtil.isBlank(privateKey)) {
log.error("拉卡拉私钥证书内容获取失败");
return "";
}
//待签字符串格式${appid}\n+${serialNo}\n+${timeStamp}\n+${nonceStr}\n+${body}\n
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = StringUtils.genRandomNumber(12);
String builder = appId + "\n" +
serialNo + "\n" +
timestamp + "\n" +
nonceStr + "\n" +
reqBody + "\n";
String signature = RSAUtil.signSHA256withRSA(builder, privateKey);
return String.format("LKLAPI-SHA256withRSA appid=\"%s\",serial_no=\"%s\",timestamp=\"%s\",nonce_str=\"%s\",signature=\"%s\"",
appId, serialNo, timestamp, nonceStr, signature);
}
/**
* 获取 body 请求数据
@ -235,4 +306,27 @@ public class LakalaUtil {
return map;
}
/**
* 去除PEM格式的密钥头和尾 BEGGIN和END标签
*
* @param key
* @return
*/
public static String stripPemHeaders(String key) {
if (key == null || key.isEmpty()) {
return key;
}
// 定义正则表达式匹配标签行任意数量连字符和空白字符
// 1. 包含BEGIN或END的标签行无论连字符数量
// 2. 单独的连字符不在标签行中的
// 3. 空白字符(空格制表符换行等)
Pattern pattern = Pattern.compile(
"-*BEGIN[^-]*-*|-*END[^-]*-*|-|\\s");
// 执行替换
Matcher matcher = pattern.matcher(key);
return matcher.replaceAll("");
}
}

View File

@ -495,8 +495,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
String reqUrl = serverUrl + "/api/v3/mms/open_api/ec/apply";
String privateKey = LakalaUtil.getResourceFile(priKeyPath, false, true);
String authorization = LakalaUtil.genAuthorization(privateKey, appId, serialNo, reqBody.toString());
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
@ -813,8 +812,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
reqUrl = serverUrl + "/api/v3/mms/open_api/ec/download";
}
String privateKey = LakalaUtil.getResourceFile(priKeyPath, false, true);
String authorization = LakalaUtil.genAuthorization(privateKey, appId, serialNo, reqBody.toString());
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
JSONObject header = new JSONObject();
header.put("Authorization", authorization);
@ -1463,8 +1461,7 @@ public class LakalaApiServiceImpl implements LakalaApiService {
String reqUrl = serverUrl + "/api/v2/mms/openApi/cardBin";
// 生成授权信息
String privateKey = LakalaUtil.getResourceFile(priKeyPath, false, true);
String authorization = LakalaUtil.genAuthorization(privateKey, appId, serialNo, reqBody.toString());
String authorization = LakalaUtil.genAuthorizationByPath(priKeyPath, appId, serialNo, reqBody.toString());
// 设置请求头
JSONObject header = new JSONObject().put("Authorization", authorization);

View File

@ -332,7 +332,45 @@ public class LakalaUtil {
}
/**
* 拼接拉卡拉header中的Authorization字段
* 拉卡拉官方拼接拉卡拉header中的Authorization字段
* 参考文档https://o.lakala.com/#/home/document/detail?id=33
*
* @param privateKeyPath
* @param appId
* @param serialNo
* @param reqBody
* @return
*/
public static String genAuthorizationByPath(String privateKeyPath, String appId, String serialNo, String reqBody) {
if (StrUtil.isBlank(privateKeyPath) || StrUtil.isBlank(appId) || StrUtil.isBlank(serialNo) || StrUtil.isBlank(reqBody)) {
log.error("生产拉卡拉签名时缺少参数");
return "";
}
// 获取私钥证书内容证书一行去掉头尾注释
String privateKey = getResourceFile(privateKeyPath, false, true);
if (StrUtil.isBlank(privateKey)) {
log.error("拉卡拉私钥证书内容获取失败");
return "";
}
//待签字符串格式${appid}\n+${serialNo}\n+${timeStamp}\n+${nonceStr}\n+${body}\n
String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
String nonceStr = StringUtils.genRandomNumber(12);
String builder = appId + "\n" +
serialNo + "\n" +
timestamp + "\n" +
nonceStr + "\n" +
reqBody + "\n";
String signature = RSAUtil.signSHA256withRSA(builder, privateKey);
return String.format("LKLAPI-SHA256withRSA appid=\"%s\",serial_no=\"%s\",timestamp=\"%s\",nonce_str=\"%s\",signature=\"%s\"",
appId, serialNo, timestamp, nonceStr, signature);
}
/**
* 拉卡拉官方拼接拉卡拉header中的Authorization字段
* 参考文档https://o.lakala.com/#/home/document/detail?id=33
*
* @param privateKey

View File

@ -12,6 +12,8 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
/**
* <p>
* 订单详细信息 前端控制器
@ -70,5 +72,12 @@ public class ShopOrderDataController {
return CommonResult.success(shopOrderDataService.remove(order_id));
}
@ApiOperation(value = "获取订单的运费", notes = "获取订单的运费")
@RequestMapping(value = "/getOrderShippingFee", method = RequestMethod.POST)
public BigDecimal getOrderShippingFee(@RequestParam(name = "order_id") String order_id) {
return shopOrderDataService.getOrderShippingFee(order_id);
}
}

View File

@ -1,6 +1,8 @@
package com.suisung.mall.shop.order.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.modules.order.ShopOrderData;
import com.suisung.mall.common.utils.CheckUtil;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.order.mapper.ShopOrderDataMapper;
import com.suisung.mall.shop.order.service.ShopOrderDataService;
@ -27,24 +29,33 @@ public class ShopOrderDataServiceImpl extends BaseServiceImpl<ShopOrderDataMappe
*/
@Override
public BigDecimal getOrderShippingFee(String orderId) {
if (orderId == null) {
try {
if (orderId == null) {
return BigDecimal.ZERO;
}
// 只查询运费字段提高性能
QueryWrapper<ShopOrderData> queryWrapper = new QueryWrapper<>();
queryWrapper.select("order_shipping_fee", "order_shipping_fee_amount")
.eq("order_id", orderId);
ShopOrderData shopOrderData = getOne(queryWrapper);
if (shopOrderData == null) {
return BigDecimal.ZERO;
}
BigDecimal result = CheckUtil.isNotEmpty(shopOrderData.getOrder_shipping_fee_amount()) ? shopOrderData.getOrder_shipping_fee_amount() : shopOrderData.getOrder_shipping_fee();
// 如果运费为null或负数返回0
if (result == null || result.compareTo(BigDecimal.ZERO) < 0) {
return BigDecimal.ZERO;
}
return result;
} catch (Exception e) {
// 异常处理记录日志并返回默认值
return BigDecimal.ZERO;
}
ShopOrderData shopOrderData = getById(orderId);
if (shopOrderData == null) {
return BigDecimal.ZERO;
}
BigDecimal result = shopOrderData.getOrder_shipping_fee();
if (result == null || result.compareTo(BigDecimal.ZERO) == -1) {
result = shopOrderData.getOrder_shipping_fee_amount();
}
if (result == null || result.compareTo(BigDecimal.ZERO) == -1) {
return BigDecimal.ZERO;
}
return result;
}
}
}

View File

@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.suisung.mall.common.modules.order.ShopOrderBase;
import com.suisung.mall.common.modules.order.ShopOrderLkl;
import com.suisung.mall.common.utils.DateTimeUtils;
import com.suisung.mall.common.utils.JsonUtil;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.order.mapper.ShopOrderLklMapper;
import com.suisung.mall.shop.order.service.ShopOrderBaseService;
@ -143,7 +144,7 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
}
// 提取订单号并校验
String orderId = respDataJson.getStr("out_trade_no");
String orderId = JsonUtil.getJsonValueSmart(respDataJson, "out_trade_no");
if (StringUtils.isBlank(orderId)) {
log.error("订单ID为空无法保存拉卡拉支付记录");
return false;
@ -161,23 +162,23 @@ public class ShopOrderLklServiceImpl extends BaseServiceImpl<ShopOrderLklMapper,
// 设置请求内容
// 订单金额安全处理
Integer amount = reqDataJson.getInt("totalAmount");
Integer amount = JsonUtil.getJsonValueSmart(reqDataJson, "totalAmount", Integer.class);
if (amount == null) {
log.error("订单{}金额无效: {}", orderId, amount);
return false;
}
record.setTotal_amt(amount); // 应支付总金额
record.setAccount_type(reqDataJson.getStr("accountType"));
record.setTrans_type(reqDataJson.getStr("transType"));
record.setNotify_url(reqDataJson.getStr("notifyUrl"));
record.setLkl_merchant_no(reqDataJson.getStr("merchantNo"));
record.setLkl_term_no(reqDataJson.getStr("termNo"));
record.setAccount_type(JsonUtil.getJsonValueSmart(reqDataJson, "accountType"));
record.setTrans_type(JsonUtil.getJsonValueSmart(reqDataJson, "transType"));
record.setNotify_url(JsonUtil.getJsonValueSmart(reqDataJson, "notifyUrl"));
record.setLkl_merchant_no(JsonUtil.getJsonValueSmart(reqDataJson, "merchantNo"));
record.setLkl_term_no(JsonUtil.getJsonValueSmart(reqDataJson, "termNo"));
record.setLkl_req(JSONUtil.toJsonStr(reqDataJson));
// 设置响应内容
record.setLkl_log_no(respDataJson.getStr("log_no"));
record.setLkl_trade_no(respDataJson.getStr("trade_no"));
record.setLkl_log_no(JsonUtil.getJsonValueSmart(respDataJson, "log_no"));
record.setLkl_trade_no(JsonUtil.getJsonValueSmart(respDataJson, "trade_no"));
record.setLkl_resp(JSONUtil.toJsonStr(respDataJson));
// 关键数据获取店铺ID分账比例用到