以支付接口为核心:支付系统与 ERP 对接的全流程方案

企业数字化转型中,支付系统与 ERP 的对接核心是围绕支付全链路数据流 的打通。常见的支付类接口系统通常以支付为核心,通过下单接口 发起交易、查询接口 获取状态、通知接口推送结果,形成完整的支付数据流闭环。本文基于这些核心接口,详解如何实现从订单创建到财务对账的全流程自动化,确保业务与财务数据的一致性。

一、整体对接架构:以支付接口为核心的数据流闭环

  • 下单接口[api/v3/ccss/counter/order/special_create]):创建支付订单,生成支付凭证
  • 查询接口[api/v3/ccss/counter/order/query]):主动查询订单支付状态
  • 通知接口(回调地址):异步推送支付结果(支付成功 / 失败)

这些接口构成了支付系统与 ERP 对接的核心数据通道,整体架构需实现三大目标:

  • 订单信息通过下单接口同步至支付系统
  • 支付结果通过通知 / 查询接口反馈至 ERP
  • 交易数据通过对账接口实现财务核对

架构分层与接口映射

对接环节 核心功能 开放平台核心接口(*聚合收银台为例) 数据流向
订单与支付发起 ERP 订单同步至支付系统,发起支付 下单接口[api/v3/ccss/counter/order/special_create] ERP → 支付系统(拉卡拉)
支付状态同步 支付结果反馈至 ERP 系统 通知接口 + 查询接口 支付系统 → ERP
财务对账 交易数据与 ERP 财务数据比对 对账接口 [api/v3/ccss/counter/order/query] 双向比对

二、订单同步与支付发起:基于下单接口的流程设计

订单同步的核心是将 ERP 系统的订单信息通过拉卡拉下单接口传递至支付系统,生成可支付的订单凭证(如二维码、支付链接),为用户支付环节提供基础。

(一)核心流程与接口参数

  1. 订单数据准备

    ERP 系统需整理订单核心信息,包括:

    • merchant_no(商户编号):拉卡拉分配的唯一标识
    • out_order_no(商户订单号):ERP 系统生成的唯一订单号
    • total_amount(订单金额):单位为元,精确到分
    • order_efficient_time(订单有效期):格式yyyyMMddHHmmss
    • order_info(商品名称):订单标题,不超过 64 字符
    • notifyUrl(回调地址):支付结果通知接收地址(必填)
  2. 接口调用逻辑

    ERP 系统通过 HTTP POST 请求调用拉卡拉下单接口,参数以 JSON 格式传递,并通过签名机制确保数据安全。接口返回结果包含qrCode(支付二维码)或payUrl(支付链接),ERP 系统将其下发至用户端完成支付。

(二)Java 下单接口调用示例

java 复制代码
public class PaymentInitiator {
    // 拉卡拉下单接口地址
    private static final String CREATE_ORDER_URL = "https://open.lakala.com/api/pay/create";
    private static final String API_KEY = "your_api_key"; // 商户密钥
    private static final String MERCHANT_NO = "M20230700001";

    /**
     * 调用拉卡拉下单接口,创建支付订单
     */
    public PayResult createPaymentOrder(ERPOrder erpOrder) throws Exception {
        // 1. 构建请求参数
        JSONObject requestParams = new JSONObject();
        requestParams.put("merchantNo", MERCHANT_NO);
        requestParams.put("outTradeNo", erpOrder.getOrderNo()); // ERP订单号
        requestParams.put("totalAmount", erpOrder.getAmount()); // 订单金额(元)
        requestParams.put("subject", erpOrder.getGoodsName());
        requestParams.put("notifyUrl", "https://your-domain.com/payment/notify"); // 回调地址
        requestParams.put("timestamp", System.currentTimeMillis() / 1000); // 秒级时间戳

        // 2. 生成签名(拉卡拉标准签名算法)
        String sign = generateSign(requestParams);
        requestParams.put("sign", sign);

        // 3. 发送HTTPS请求
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(CREATE_ORDER_URL);
        httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
        httpPost.setEntity(new StringEntity(requestParams.toString(), "UTF-8"));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        String result = EntityUtils.toString(response.getEntity(), "UTF-8");
        response.close();
        httpClient.close();

        // 4. 解析响应结果
        JSONObject resultJson = JSONObject.parseObject(result);
        if ("0000".equals(resultJson.getString("code"))) {
            // 下单成功,返回支付凭证
            JSONObject data = resultJson.getJSONObject("data");
            return new PayResult(
                erpOrder.getOrderNo(),
                data.getString("qrCode"), // 支付二维码
                data.getString("tradeNo") // 拉卡拉交易号
            );
        } else {
            // 下单失败,抛出异常
            throw new RuntimeException("下单失败:" + resultJson.getString("msg"));
        }
    }

    // 签名生成(按拉卡拉要求:参数ASCII排序+MD5加密)
    private String generateSign(JSONObject params) throws Exception {
        Set<String> keySet = params.keySet();
        List<String> keyList = new ArrayList<>(keySet);
        Collections.sort(keyList); // ASCII升序排序

        StringBuilder sb = new StringBuilder();
        for (String key : keyList) {
            if (!"sign".equals(key) && params.get(key) != null) {
                sb.append(key).append("=").append(params.getString(key)).append("&");
            }
        }
        sb.append("key=").append(API_KEY);

        // MD5加密并转为大写
        MessageDigest md = MessageDigest.getInstance("MD5");
        byte[] bytes = md.digest(sb.toString().getBytes("UTF-8"));
        StringBuilder signBuilder = new StringBuilder();
        for (byte b : bytes) {
            String hex = Integer.toHexString(b & 0xFF);
            if (hex.length() == 1) signBuilder.append("0");
            signBuilder.append(hex);
        }
        return signBuilder.toString().toUpperCase();
    }
}

(三)异常处理机制

  • 接口调用超时:设置 30 秒超时时间,超时后自动重试(最多 3 次),重试间隔依次为 1s、3s、5s
  • 下单失败处理:记录失败日志,标记订单状态为 "支付发起失败",支持手动触发重新下单
  • 幂等性保障 :通过outTradeNo(商户订单号)确保唯一,避免重复下单

三、支付状态同步:查询接口与通知接口的协同

支付状态同步是确保 ERP 系统及时获取交易结果的关键,通过通知接口 (异步推送)和查询接口(主动查询)双重机制,保障状态更新的及时性与准确性。

(一)通知接口:异步接收支付结果

拉卡拉支付系统在用户完成支付后,会主动向notifyUrl推送支付结果,这是状态同步的主要方式。

  1. 通知数据格式

    回调数据包含以下核心字段

    • outTradeNo:商户订单号(ERP 订单号)
    • tradeNo:拉卡拉交易号
    • tradeStatus:交易状态(SUCCESS表示成功)
    • totalAmount:支付金额
    • trade_time:交易完时间
  2. 回调处理实现(Spring Boot)

typescript 复制代码
@RestController
public class PaymentNotifyController {
    private static final String API_KEY = "your_api_key";

    @Autowired
    private OrderService orderService;

    @PostMapping("/payment/notify")
    public String handlePaymentNotify(@RequestBody JSONObject notifyData) {
        // 1. 记录原始通知数据(用于问题排查)
        log.info("支付结果通知:" + notifyData.toString());

        // 2. 验证签名(防止伪造请求)
        if (!verifySign(notifyData)) {
            log.error("签名验证失败:" + notifyData.getString("outTradeNo"));
            return "fail"; // 签名失败,拉卡拉会重试通知
        }

        // 3. 解析通知数据
        String orderNo = notifyData.getString("outTradeNo");
        String tradeStatus = notifyData.getString("tradeStatus");
        String tradeNo = notifyData.getString("tradeNo");
        String payTime = notifyData.getString("gmtPayment");

        // 4. 更新订单状态(核心业务逻辑)
        if ("SUCCESS".equals(tradeStatus)) {
            boolean updateResult = orderService.updateOrderToPaid(
                orderNo, tradeNo, payTime, notifyData.getString("totalAmount")
            );
            if (updateResult) {
                return "success"; // 处理成功,拉卡拉停止重试
            }
        }

        // 处理失败,拉卡拉会在24小时内重试(最多8次)
        return "fail";
    }

    // 签名验证(与下单接口签名算法一致)
    private boolean verifySign(JSONObject data) {
        String receivedSign = data.getString("sign");
        if (receivedSign == null) return false;

        // 移除sign字段后重新计算签名
        JSONObject tempData = (JSONObject) data.clone();
        tempData.remove("sign");
        String calculatedSign = generateSign(tempData); // 复用下单接口的签名方法

        return receivedSign.equals(calculatedSign);
    }
}

二)查询接口:主动获取支付状态

为应对通知接口可能的延迟或丢失,需通过查询接口主动校验订单状态,作为补充机制。

  1. 查询时机
    • 下单后 30 分钟未收到回调通知
    • 用户反馈支付成功但系统未更新状态
    • 每日定时对 "待支付" 订单进行批量查询
  2. 查询接口调用实现
typescript 复制代码
public class PaymentQueryClient {
    private static final String QUERY_URL = "https://open.lakala.com/api/pay/query";
    private static final String API_KEY = "your_api_key";

    /**
     * 查询订单支付状态
     */
    public QueryResult queryPaymentStatus(String orderNo) throws Exception {
        // 1. 构建查询参数
        JSONObject queryParams = new JSONObject();
        queryParams.put("merchantNo", "M20230700001");
        queryParams.put("outTradeNo", orderNo); // 商户订单号
        queryParams.put("sign", generateSign(queryParams)); // 生成签名

        // 2. 调用查询接口
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(QUERY_URL);
        httpPost.setHeader("Content-Type", "application/json");
        httpPost.setEntity(new StringEntity(queryParams.toString(), "UTF-8"));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        String result = EntityUtils.toString(response.getEntity(), "UTF-8");
        response.close();
        httpClient.close();

        // 3. 解析查询结果
        JSONObject resultJson = JSONObject.parseObject(result);
        if ("0000".equals(resultJson.getString("code"))) {
            JSONObject data = resultJson.getJSONObject("data");
            return new QueryResult(
                data.getString("outTradeNo"),
                data.getString("tradeStatus"), // SUCCESS/REFUND/CLOSED等
                data.getString("gmtPayment")
            );
        } else {
            throw new RuntimeException("查询失败:" + resultJson.getString("msg"));
        }
    }
}

(三)双重机制保障

  • 优先依赖通知接口:实时性高,减少主动查询的资源消耗
  • 定时查询补充:对超过 30 分钟未收到通知的订单,调用查询接口主动获取状态,避免漏单

四、财务对账:基于对账接口的自动化核对

财务对账通过拉卡拉对账接口获取官方交易记录,与 ERP 系统的收款记录比对,确保资金数据一致

(一)对账流程设计

  1. 数据获取
    每日凌晨调用拉卡拉对账接口(/api/v3/bmmp4/checkFile/apply),获取前一天的全量交易记录,包含:
  • 正常支付订单:订单号、金额、手续费、支付时间
  • 退款订单:退款金额、退款时间、原订单号
  • 结算信息:实际到账金额、结算日期
  1. 比对规则
比对维度 规则说明 差异处理方式
订单存在性 双方订单号必须匹配 标记 "漏单" 或 "虚增订单"
金额一致性 支付金额、退款金额需完全一致 标记 "金额不符",触发财务核查
状态一致性 交易状态(成功 / 失败 / 退款)需一致 标记 "状态不符",人工核实

(二)对账任务实现(Quartz)

typescript 复制代码
public class ReconciliationJob implements Job {
    private static final String RECONCILIATION_URL = "https://open.lakala.com/api/pay/reconciliation";
    private static final String API_KEY = "your_api_key";

    @Autowired
    private ReconciliationService reconciliationService;

    @Override
    public void execute(JobExecutionContext context) {
        try {
            // 1. 计算对账日期(前一天)
            LocalDate checkDate = LocalDate.now().minusDays(1);
            String dateStr = checkDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));

            // 2. 调用拉卡拉对账接口获取数据
            List<JSONObject> lakalaRecords = getLakalaReconciliationData(dateStr);

            // 3. 获取ERP系统收款记录
            List<JSONObject> erpRecords = reconciliationService.getErpPaymentRecords(checkDate);

            // 4. 执行比对逻辑
            ReconciliationResult result = reconciliationService.compareRecords(
                lakalaRecords, erpRecords, checkDate
            );

            // 5. 处理比对结果
            if (result.hasDiscrepancies()) {
                // 保存差异记录
                reconciliationService.saveDiscrepancies(result.getDiscrepancies());
                // 发送差异告警
                notificationService.sendReconciliationAlert(result);
            } else {
                log.info("对账完成,日期:" + dateStr + ",无差异");
            }
        } catch (Exception e) {
            log.error("对账任务执行失败", e);
            notificationService.sendSystemAlert("对账任务异常:" + e.getMessage());
        }
    }

    // 调用拉卡拉对账接口
    private List<JSONObject> getLakalaReconciliationData(String dateStr) throws Exception {
        JSONObject params = new JSONObject();
        params.put("merchantNo", "M20230700001");
        params.put("billDate", dateStr); // 对账日期(yyyyMMdd)
        params.put("sign", generateSign(params)); // 签名

        // 发送请求(HTTP实现略,参考下单接口)
        String response = httpClient.post(RECONCILIATION_URL, params.toString());
        JSONObject resultJson = JSONObject.parseObject(response);

        if ("0000".equals(resultJson.getString("code"))) {
            return resultJson.getJSONArray("data").toJavaList(JSONObject.class);
        } else {
            throw new RuntimeException("获取对账数据失败:" + resultJson.getString("msg"));
        }
    }
}

五、关键保障措施

(一)接口调用的稳定性保障

  1. 超时与重试机制

    • 所有接口调用设置 30 秒超时
    • 非幂等接口(如退款)不重试,幂等接口(如下单、查询)最多重试 3 次
  2. 限流保护

    • 控制接口调用频率(如每秒不超过 100 次),避免触发拉卡拉平台限流
    • 大促期间提前申请接口调用额度提升

(二)数据安全保障

  1. 签名与加密

    • 所有接口参数必须通过签名验证,防止数据篡改
    • 敏感信息(如用户手机号)传输前加密,落地存储时脱敏
  2. 日志与审计

    • 记录所有接口调用日志(请求参数、响应数据、处理结果),保留至少 6 个月
    • 定期审计接口调用记录,排查异常请求

(三)监控与告警

  • 接口健康度监控:调用成功率、响应时间(阈值:成功率≥99.9%,响应时间 < 500ms)
  • 业务指标监控:订单支付成功率、对账差异率
  • 异常告警:接口连续失败、对账差异金额超阈值时,通过短信 / 钉钉实时通知

总结

拉卡拉开放平台的支付接口(下单、查询、通知、对账)构成了支付系统与 ERP 对接的核心骨架。通过订单同步触发下单接口、支付结果依赖通知与查询接口、财务对账基于对账接口的流程设计,可实现全链路自动化。

企业在落地时需注意:

  1. 优先使用通知接口同步状态,辅以查询接口确保数据完整
  2. 严格遵守签名机制,保障接口调用安全
  3. 对账环节需覆盖全量交易(包括退款、冲正),确保资金数据准确

通过这套方案,企业可将订单处理时效提升至秒级,对账效率提升 90% 以上,显著降低人工成本与财务风险,为数字化转型提供坚实的支付数据支撑。

大家在对接支付接口时都遇到过哪些问题?评论区留言,下一篇将会针对性讲解~

拉卡拉开放平台

开发者中心

手把手教你把三方支付接口打包成 TypeScript SDK

相关推荐
高阳言编程5 小时前
6. 向量处理机
架构
.Shu.7 小时前
Redis Reactor 模型详解【基本架构、事件循环机制、结合源码详细追踪读写请求从客户端连接到命令执行的完整流程】
数据库·redis·架构
gnip8 小时前
Jenkins部署前端项目实战方案
前端·javascript·架构
尚书9 小时前
全局核心状态 + 局部功能内聚模块化混合架构
架构
车厘小团子9 小时前
🎨 前端多主题最佳实践:用 Less Map + generate-css 打造自动化主题系统
前端·架构·less
颜颜yan_11 小时前
企业级时序数据库选型指南:从传统架构向智能时序数据管理的转型之路
数据库·架构·时序数据库
京东云开发者11 小时前
EXCEL导入—设计与思考
java·架构
一语长情12 小时前
Netty流量整形:保障微服务通信稳定性的关键策略
java·后端·架构
顾林海17 小时前
从"面条代码"到"精装别墅":Android MVPS架构的逆袭之路
android·面试·架构