跳到主要内容Java 对接拉卡拉支付完整指南 | 极客日志JavaPayjava
Java 对接拉卡拉支付完整指南
综述由AI生成档详细介绍了在 Java Spring Boot 项目中集成拉卡拉支付 SDK 的完整流程。涵盖环境准备、依赖引入、证书配置、SDK 初始化、收银台订单创建接口实现、支付回调通知处理及常见问题解答。重点包含全报文加密、签名验证、订单状态管理、幂等性处理及安全配置建议,为开发者提供可直接参考的代码示例与实施指南。
嘘30 浏览 Java 对接拉卡拉支付完整指南
概述
拉卡拉支付是中国领先的第三方支付解决方案提供商。本文档详细介绍如何在 Java Spring Boot 项目中集成拉卡拉支付 SDK,实现收银台订单创建和支付回调处理功能。
功能特性
- 收银台订单创建(全报文加密)
- 支付回调通知处理
- 签名验证
- 订单状态管理
1. 环境准备
接入官方 SDK。
<dependency>
<groupId>com.lkl.laop.sdk</groupId>
<artifactId>lkl-laop-java-sdk</artifactId>
<version>1.0.8</version>
<systemPath>${project.basedir}/src/main/resources/lib/lkl-java-sdk-1.0.8.jar</systemPath>
<scope>system</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
slf4j-api
1.7.30
org.apache.commons
commons-lang3
3.4
com.fasterxml.jackson.core
jackson-databind
2.13.3
javax.servlet
javax.servlet-api
4.0.1
org.bouncycastle
bcprov-jdk15on
1.68
org.bouncycastle
bcpkix-jdk15on
1.68
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
<dependency>
<groupId>
</groupId>
<artifactId>
</artifactId>
<version>
</version>
</dependency>
2. 证书准备
- 商户私钥证书(PEM 格式)
- 拉卡拉平台证书(PEM 格式)
- 拉卡拉平台通知证书(PEM 格式)
SDK 初始化配置
1. 配置文件 (application.yml)
在 application.yml 中添加拉卡拉支付配置:
lakala:
sdk:
app-id: "OP00000003"
serial-no: "dfba8194c41b84cf"
private-key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDvDBZyHUDndAGx rIcsCV2njhNO3vCEZotTaWYSYwtDvkcAb1EjsBFabXZaKigpqFXk5XXNI3NIHP9M ...
-----END PRIVATE KEY-----
lkl-certificate: |
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIGAXRTgcMnMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNVBAYT ...
-----END CERTIFICATE-----
lkl-notify-certificate: |
-----BEGIN CERTIFICATE-----
MIIEMTCCAxmgAwIBAgIGAXRTgcMnMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNVBAYT ...
-----END CERTIFICATE-----
sm4-key: "LHo55AjrT4aDhAIBZhb5KQ=="
server-url: "https://test.wsmsd.cn/sit"
connect-timeout: 50000
read-timeout: 100000
socket-timeout: 50000
2. SDK 初始化类 (LakalaSdkConfig.java)
package com.shunp.framework.config;
import com.laop.sdk.Config2;
import com.laop.sdk.LKLSDK;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
@Slf4j
public class LakalaSdkConfig {
@Value("${lakala.sdk.app-id:}")
private String appId;
@Value("${lakala.sdk.serial-no:}")
private String serialNo;
@Value("${lakala.sdk.private-key:}")
private String privateKey;
@Value("${lakala.sdk.lkl-certificate:}")
private String lklCertificate;
@Value("${lakala.sdk.lkl-notify-certificate:}")
private String lklNotifyCertificate;
@Value("${lakala.sdk.sm4-key:}")
private String sm4Key;
@Value("${lakala.sdk.server-url:}")
private String serverUrl;
@Value("${lakala.sdk.connect-timeout:50000}")
private Integer connectTimeout;
@Value("${lakala.sdk.read-timeout:100000}")
private Integer readTimeout;
@Value("${lakala.sdk.socket-timeout:50000}")
private Integer socketTimeout;
@PostConstruct
public void initLklSdk() {
try {
log.info("开始初始化拉卡拉 SDK...");
if (appId == null || appId.trim().isEmpty()) {
log.warn("拉卡拉 SDK appId 未配置,跳过初始化");
return;
}
Config2 config = new Config2();
config.setAppId(appId.trim());
config.setSerialNo(serialNo.trim());
config.setPriKey(privateKey.trim());
config.setLklCer(lklCertificate.trim());
config.setLklNotifyCer(lklNotifyCertificate.trim());
config.setServerUrl(serverUrl.trim());
if (sm4Key != null && !sm4Key.trim().isEmpty()) {
config.setSm4Key(sm4Key.trim());
}
if (connectTimeout != null) {
config.setConnectTimeout(connectTimeout);
}
if (readTimeout != null) {
config.setReadTimeout(readTimeout);
}
if (socketTimeout != null) {
config.setSocketTimeout(socketTimeout);
}
boolean initResult = LKLSDK.init(config);
if (initResult) {
log.info("拉卡拉 SDK 初始化成功,appId: {}", appId);
} else {
log.error("拉卡拉 SDK 初始化失败,appId: {}", appId);
}
} catch (Exception e) {
log.error("拉卡拉 SDK 初始化异常", e);
}
}
}
支付接口实现
1. 支付 Controller (LakalaPayController.java)
package com.shunp.web.controller.pay;
import com.alibaba.fastjson2.JSONObject;
import com.laop.sdk.LKLSDK;
import com.laop.sdk.exception.SDKException;
import com.laop.sdk.request.V3CcssCounterOrderSpecialCreateEncryRequest;
import com.shunp.common.core.controller.BaseController;
import com.shunp.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@RestController
@RequestMapping("/opmanager/pay/lakala")
@Slf4j
@Tag(name = "拉卡拉支付")
public class LakalaPayController extends BaseController {
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
@PostMapping("/counter/order/create/encry")
@Operation(summary = "收银台订单创建(全报文加密)")
public AjaxResult createCounterOrderEncry(@RequestBody CounterOrderCreateRequest request) {
try {
String orderNo = request.getOutOrderNo() != null && !request.getOutOrderNo().trim().isEmpty()
? request.getOutOrderNo()
: generateOrderNo();
log.info("开始创建收银台订单(全报文加密),订单号:{},商户号:{},交易设备 ID:{}", orderNo, request.getMerchantNo(), request.getVposId());
V3CcssCounterOrderSpecialCreateEncryRequest lakalaRequest = new V3CcssCounterOrderSpecialCreateEncryRequest();
lakalaRequest.setOutOrderNo(orderNo);
lakalaRequest.setMerchantNo(request.getMerchantNo());
lakalaRequest.setVposId(request.getVposId());
lakalaRequest.setChannelId(request.getChannelId());
lakalaRequest.setTotalAmount(request.getTotalAmount());
if (request.getOrderEfficientTime() != null) {
lakalaRequest.setOrderEfficientTime(request.getOrderEfficientTime());
} else {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, 7);
lakalaRequest.setOrderEfficientTime(dateFormat.format(calendar.getTime()));
}
lakalaRequest.setNotifyUrl(request.getNotifyUrl());
lakalaRequest.setOrderInfo(request.getOrderInfo());
String response = LKLSDK.httpPost(lakalaRequest, true, true);
log.info("收银台订单创建完成,响应:{}", response);
return AjaxResult.success("订单创建成功", response);
} catch (SDKException e) {
log.error("收银台订单创建失败,SDK 异常:{}", e.getMessage(), e);
return AjaxResult.error("订单创建失败:" + e.getMessage());
} catch (Exception e) {
log.error("收银台订单创建失败,系统异常:{}", e.getMessage(), e);
return AjaxResult.error("订单创建失败:" + e.getMessage());
}
}
@PostMapping("/notify")
@Operation(summary = "拉卡拉支付回调通知")
public String paymentNotify(HttpServletRequest request) {
try {
log.info("收到拉卡拉支付回调通知");
String notifyBody = LKLSDK.notificationHandle(request);
log.info("回调签名验证成功,通知内容:{}", notifyBody);
PaymentNotifyData notifyData = JSONObject.parseObject(notifyBody, PaymentNotifyData.class);
log.info("支付回调信息 - 商户订单号:{},支付订单号:{},支付状态:{},支付金额:{},支付时间:{}",
notifyData.getOutOrderNo(), notifyData.getPayOrderNo(), notifyData.getPayStatus(),
notifyData.getPayAmount(), notifyData.getPayTime());
if ("SUCCESS".equals(notifyData.getPayStatus())) {
log.info("支付成功,订单号:{},金额:{}", notifyData.getOutOrderNo(), notifyData.getPayAmount());
} else if ("FAILED".equals(notifyData.getPayStatus())) {
log.warn("支付失败,订单号:{}", notifyData.getOutOrderNo());
} else {
log.warn("未知支付状态:{},订单号:{}", notifyData.getPayStatus(), notifyData.getOutOrderNo());
}
return "success";
} catch (SDKException e) {
log.error("回调签名验证失败:{}", e.getMessage(), e);
return "fail";
} catch (Exception e) {
log.error("处理支付回调异常:{}", e.getMessage(), e);
return "fail";
}
}
private String generateOrderNo() {
return "LKL" + System.currentTimeMillis() + (int) (Math.random() * 1000);
}
@Data
public static class CounterOrderCreateRequest {
private String outOrderNo;
private String merchantNo;
private String vposId;
private String channelId;
private Long totalAmount;
private String orderEfficientTime;
private String notifyUrl;
private String orderInfo;
private Integer supportCancel;
private Integer supportRefund;
private Integer supportRepeatPay;
private String callbackUrl;
private String counterParam;
private String counterRemark;
}
@Data
public static class PaymentNotifyData {
private String outOrderNo;
private String payOrderNo;
private String payStatus;
private String payAmount;
private String payTime;
private String merchantNo;
private String vposId;
private String payChannel;
private String payMethod;
private String attach;
}
}
回调处理
回调地址配置
lakalaRequest.setNotifyUrl("http://your-domain/opmanager/pay/lakala/notify");
回调数据格式
{
"out_order_no": "LKL1703123456789",
"pay_order_no": "26011611012001101011002063626",
"pay_status": "SUCCESS",
"pay_amount": "1",
"pay_time": "20260116145443",
"merchant_no": "8222900701106PZ",
"vpos_id": "2021052614391",
"pay_channel": "ALIPAY",
"pay_method": "SCAN",
"attach": "附加数据"
}
回调处理逻辑
- 签名验证:使用
LKLSDK.notificationHandle() 验证回调签名
- 数据解析:将 JSON 字符串解析为
PaymentNotifyData 对象
- 业务处理:
- 支付成功:更新订单状态为已支付
- 支付失败:记录失败原因
- 其他状态:记录日志并告警
- 响应处理:返回 "success" 告知拉卡拉处理成功
完整代码示例
1. 前端调用示例
const createOrder = async (orderData) => {
const response = await fetch('/opmanager/pay/lakala/counter/order/create/encry', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
merchantNo: '8222900701106PZ',
vposId: '2021052614391',
channelId: '2021052614391',
totalAmount: 100,
notifyUrl: 'http://your-domain/opmanager/pay/lakala/notify',
orderInfo: '商品购买'
})
});
const result = await response.json();
if (result.code === 200) {
const payUrl = result.data.counter_url;
window.location.href = payUrl;
}
};
2. 回调处理业务逻辑示例
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Transactional
public void updateOrderStatus(String orderNo, String status) {
Order order = orderMapper.selectByOrderNoForUpdate(orderNo);
if (order != null && "PENDING".equals(order.getStatus())) {
order.setStatus(status);
order.setPayTime(new Date());
orderMapper.updateById(order);
sendPaymentSuccessNotification(order);
}
}
}
注意事项
1. 安全配置
- 私钥证书妥善保管,不要在日志中输出
- 使用 HTTPS 协议传输敏感数据
- 定期更新证书,避免过期
2. 订单处理
- 实现幂等性,避免重复处理同一订单
- 添加订单级锁,防止并发更新问题
- 记录详细的支付流水日志
3. 异常处理
- 妥善处理网络异常和超时
- 实现重试机制,但避免无限重试
- 监控支付成功率和异常情况
4. 回调处理
- 回调接口必须返回 "success",否则拉卡拉会重试
- 回调处理要快速响应,避免超时
- 验证回调数据的完整性和正确性
常见问题
Q1: SDK 初始化失败怎么处理?
A: 检查配置文件中的证书格式是否正确,私钥是否匹配,网络连接是否正常。
Q2: 订单创建失败的原因?
A: 可能是参数格式错误、金额超出限制、商户状态异常等。检查返回的错误信息。
Q3: 回调没有收到怎么办?
A: 检查回调地址是否正确可访问,防火墙是否阻止了请求,服务器是否正常运行。
Q4: 如何处理重复回调?
A: 在业务逻辑中检查订单状态,只有在特定状态下才处理回调,避免重复处理。
Q5: 支付状态不一致怎么办?
A: 以拉卡拉回调为准,主动查询订单状态进行校对,记录异常情况并告警。
本文档提供完整的拉卡拉支付集成方案,如有问题请参考官方文档或联系技术支持。
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online