拉卡拉支付,增加合单交易方法流程
This commit is contained in:
parent
a1e31e5ca4
commit
a01b2573f7
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@ -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: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”
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
|
||||
|
||||
@ -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("");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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,分账比例用到
|
||||
|
||||
Loading…
Reference in New Issue
Block a user