API开发文档

API开发文档简介

本文阅读对象:使用支付平台商户自服务系统的技术架构师、研发工程师、系统运维工程师。通过本文档,商户可了解支付平台接入的技术、接入的产品业务、接入的流程、接入规范等信息,以便于商户顺利完成接入工作。


编辑代理表单界面

重要参数说明:

  • API密钥(API密钥):用于API接口签名验证的密钥,请妥善保管,不要泄露给第三方。
  • 支付回调地址(支付回调地址):订单完成后平台回调此地址,用于接收订单完成通知。必须是公网可访问的HTTPS地址。
  • IP白名单(IP白名单):允许访问API接口的IP地址列表,格式为JSON数组。例如:192.168.1.1,10.0.0.1

平台接口说明:

  • 创建订单接口:https://www.leenopay.com/create/order
  • POST请求,用于创建支付订单,商户调用此接口创建订单后,平台会返回支付跳转URL
  • 订单查询接口:https://www.leenopay.com/search/order
  • POST请求,用于查询订单状态、金额、支付时间等信息

接入网关

请登录商户中心,API管理》API开发文档,查看代理ID(pay_agentID)、商户密钥(APISecret)等参数。

接口基础地址: https://www.leenopay.com

配置说明:产品方需要在代理信息中配置以下URL:
  • CallbackURL:订单完成回调URL(订单完成后平台回调此地址,也可在创建订单时通过 pay_notifyUrl 参数指定)

接口约定

  • 接口提交方法: POST
  • 接口请求体格式: JSON
  • 接口响应格式: JSON
  • 字符编码: UTF-8
  • 签名算法: MD5

签名算法

1. 筛选

获取所有发送或者接收到中根据参数说明需要参与签名(部分参数不参与签名,表格中有说明)的参数。

注意: sign 字段不参与签名计算。

2. 排序

将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

示例:

  • Python: 用 sorted(dict.items()) 按键升序生成新字典
  • Golang: 用 sort.Strings(keys) 对提取的键升序排序
  • Java: 用 new TreeMap<>()(默认按键自然升序)
  • PHP: 用 ksort()

3. 拼接

将排序后的参数与其对应值,组合成"参数=参数值"的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。再将"&key=商户密钥"拼接在待签名字符串最后面。

4. 生成

将拼接后的待签名字符串用MD5加密,然后把加密后的MD5字符串转换为大写,得到签名字符串。

签名示例

假设有以下参数:

  • pay_agentID: AGENT001
  • pay_externalOrderNo: ORDER123456
  • pay_amount: 100.00
  • 商户密钥: SECRET_KEY_12345
stringSignTemp = "pay_agentID=AGENT001&pay_amount=100.00&pay_externalOrderNo=ORDER123456
                    &key=SECRET_KEY_12345"
sign = MD5(stringSignTemp).toUpperCase()
最终签名: A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6

创建订单接口

接口地址

POST https://www.leenopay.com/create/order

说明:接口基础地址为 https://www.leenopay.com,完整接口路径为 /create/order

请求参数

参数 类型 必填 参与签名 描述
pay_agentID string 平台分配的代理ID
pay_externalOrderNo string 商户订单号,需保证唯一性
pay_userID string 用户ID
pay_userName string 用户名称
pay_userAvatar string 用户头像
pay_amount float64 订单金额,单位:元。必须是整数金额(如1元、100元),不支持小数金额(如1.5元、2.3元)。在JSON中使用浮点数格式表示,如1元应表示为1.00
pay_notifyUrl string 订单成功通知地址(请求方式:POST JSON)
pay_orderTitle string 订单标题
pay_orderDesc string 订单描述
pay_currency string 货币类型,默认:CNY
pay_productName string 产品名称
pay_phone string 客户手机号
pay_remark string 是(不为空时) 备注(不为空时参与签名)
pay_extraData string 是(不为空时) 扩展数据(JSON格式,不为空时参与签名)
sign string 签名字符串,请查看签名算法

签名参数说明

参与签名的参数:

  • 必填字段
    • pay_agentID
    • pay_externalOrderNo
    • pay_userID
    • pay_userName
    • pay_userAvatar
    • pay_amount
    • pay_notifyurl(注意:JSON字段名为pay_notifyUrl,但签名时使用pay_notifyurl
  • 可选字段(不为空时参与签名):
    • pay_remark
    • pay_extraData

拼接签名字符串样例

pay_agentID=AGENT001&pay_amount=100.00&pay_externalOrderNo=ORDER20240101001&pay_notifyurl=https://example.com/notify&pay_userAvatar=avatar_url_123&pay_userID=USER123&pay_userName=张三&key=SECRET_KEY_12345

请求示例

{
    "pay_agentID": "AGENT001",
    "pay_externalOrderNo": "ORDER20240101001",
    "pay_userID": "USER123",
    "pay_userName": "张三",
    "pay_userAvatar": "avatar_url_123",
    "pay_amount": 100.00,
    "pay_notifyUrl": "https://example.com/notify",
    "pay_orderTitle": "测试订单",
    "pay_currency": "CNY",
    "sign": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
}

返回值

成功响应

参数 类型 必填 描述
code string 状态,取值范围: success-成功, error-失败
msg string 状态描述
data string 数据内容,为JSON字符串(需要二次解析)

data字段解析后的结构:

参数 类型 必填 描述
pay_orderNo string 平台订单号
pay_externalOrderNo string 商户订单号
pay_url string 支付跳转URL
sign string 签名字符串

返回值示例

{
    "code": "success",
    "msg": "通过订单号创建订单成功",
    "data": "{\"pay_orderNo\":\"PLATFORM_ORDER_001\",\"pay_externalOrderNo\":\"ORDER20240101001\",
\"pay_url\":\"https://pay.example.com/pay?orderNo=PLATFORM_ORDER_001\",
\"sign\":\"B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7\"}"
}

订单查询接口

接口地址

POST https://www.leenopay.com/search/order

说明:接口基础地址为 https://www.leenopay.com,完整接口路径为 /search/order

请求参数

参数 类型 必填 参与签名 描述
pay_agentID string 平台分配的代理ID
pay_orderNo string 是(不为空时) 平台订单号(不为空时参与签名)
pay_externalOrderNo string 商户订单号(必填,参与签名)
sign string 签名字符串,请查看签名算法

签名参数说明

参与签名的参数:

  • 必填字段
    • pay_agentID
    • pay_externalOrderNo
  • 可选字段(不为空时参与签名):
    • pay_orderNo

拼接签名字符串样例

pay_agentID=AGENT001&pay_externalOrderNo=ORDER20240101001&key=SECRET_KEY_12345

请求示例

{
    "pay_agentID": "AGENT001",
    "pay_externalOrderNo": "ORDER20240101001",
    "sign": "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6"
}

返回值

成功响应

参数 类型 必填 参与签名 描述
code string 状态,取值范围: success-成功, error-失败
msg string 状态描述
data string 数据内容,为JSON字符串(需要二次解析)

data字段解析后的结构:

参数 类型 必填 参与签名 描述
pay_orderNo string 平台订单号
pay_externalOrderNo string 商户订单号
pay_userID string 用户ID
pay_serviceID string 客服ID(不参与签名)
pay_agentID string 代理ID
pay_amount float64 实际结算金额,单位:元(扣除费率后)
pay_originalAmount float64 原始订单金额,单位:元
pay_rate float64 费率(百分比,如2.5表示2.5%)
pay_rateAmount float64 费率金额,单位:元
pay_status uint64 订单状态,0-待处理,1-处理中,2-已支付,3-已完成,4-已取消,5-已过期
pay_serviceStatus uint64 客服处理状态,0-待客服处理,1-客服已接单,2-客服处理中,3-客服处理完成,4-客服拒绝
pay_remark string 是(不为空时) 备注(不为空时参与签名)
pay_extraData string 是(不为空时) 扩展数据(JSON格式,不为空时参与签名)
pay_payAt string 支付时间,格式:YYYY-MM-DD HH:mm:ss
sign string 签名字符串,请查看签名算法

返回值示例

{
    "code": "success",
    "msg": "查询订单成功",
    "data": "{"pay_orderNo":"PLATFORM_ORDER_001","pay_externalOrderNo":"ORDER20240101001",
"pay_userID":"USER123","pay_serviceID":"","pay_agentID":"AGENT001","pay_amount":97.50,
"pay_originalAmount":100.00,"pay_rate":2.5,"pay_rateAmount":2.50,"pay_status":3,
"pay_serviceStatus":3,"pay_remark":"订单备注","pay_extraData":"","pay_payAt":"2024-01-01 12:00:00","sign":"C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8"}"
}

响应数据签名说明

查询订单接口返回的data字段(JSON字符串)中包含的以下字段参与签名验证:

  • 必填字段
    • pay_orderNo
    • pay_externalOrderNo
    • pay_userID
    • pay_agentID
    • pay_amount(实际结算金额)
    • pay_originalAmount(原始订单金额)
    • pay_rate(费率)
    • pay_rateAmount(费率金额)
    • pay_status(订单状态)
    • pay_serviceStatus(客服处理状态)
    • pay_payAt(支付时间)
  • 可选字段(不为空时参与签名):
    • pay_remark
    • pay_extraData
  • 不参与签名
    • pay_serviceID(客服ID)
    • sign(签名字段本身)

订单完成回调

回调说明

当客服点击下发订单,订单成功完成后,系统会自动向商户配置的回调地址发送订单完成通知。

请求方法: POST
请求体编码格式: JSON

接收到服务器点对点通讯时,在页面输出 OK(没有双引号,OK 两个字母大写),系统采用指数退避重试机制:

  • 第1次重试:10秒后
  • 第2次重试:30秒后
  • 第3次重试:60秒后

如果所有重试均失败,任务将被存入Redis队列,等待后续处理。

回调地址

回调地址在创建订单时通过 pay_notifyUrl 参数指定,也可以使用订单信息中的 callbackURL 字段。

回调参数

参数 类型 必填 参与签名 描述
pay_orderNo string 平台订单号
pay_externalOrderNo string 商户订单号
pay_status string 订单状态,字符串格式:0-待处理,1-处理中,2-已支付,3-已完成,4-已取消,5-已过期
pay_amount float64 实际结算金额(扣除费率后),单位:元
pay_originalAmount float64 原始订单金额,单位:元
pay_rate float64 费率(百分比,如2.5表示2.5%)
pay_rateAmount float64 费率金额,单位:元
pay_payAt string 支付时间,格式:YYYY-MM-DD HH:mm:ss
pay_remark string 是(不为空时) 备注(不为空时参与签名)
sign string 签名字符串,请查看签名算法

签名参数说明

参与签名的参数:

  • 必填字段
    • pay_orderNo
    • pay_externalOrderNo
    • pay_status
    • pay_amount(实际结算金额)
    • pay_originalAmount(原始订单金额)
    • pay_rate(费率)
    • pay_rateAmount(费率金额)
    • pay_payAt
  • 可选字段(不为空时参与签名):
    • pay_remark

拼接签名字符串样例

pay_amount=97.50&pay_externalOrderNo=ORDER20240101001&pay_orderNo=PLATFORM_ORDER_001&pay_originalAmount=100.00&pay_payAt=2024-01-01 12:00:00&pay_rate=2.5&pay_rateAmount=2.50&pay_remark=订单已完成&pay_status=3&key=SECRET_KEY_12345

注意:如果 pay_remark 为空,则不参与签名,签名字符串中不包含该参数。

回调示例

{
    "pay_orderNo": "PLATFORM_ORDER_001",
    "pay_externalOrderNo": "ORDER20240101001",
    "pay_status": "3",
    "pay_amount": 97.50,
    "pay_originalAmount": 100.00,
    "pay_rate": 2.5,
    "pay_rateAmount": 2.50,
    "pay_payAt": "2024-01-01 12:00:00",
    "pay_remark": "订单已完成",
    "sign": "D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9"
}

回调响应

商户收到回调后,应返回HTTP状态码 200 并在响应体中输出 OK(没有双引号,OK 两个字母大写),表示成功接收。如果返回非200状态码或响应内容不是OK,系统将进行重试。

重要提示:

客服在与客户沟通后,可能会根据客户的实际需求调整订单金额。因此,最终订单金额以回调通知中的金额为准,包括 pay_amount(实际结算金额)和 pay_originalAmount(原始订单金额)。请商户务必以回调数据中的金额进行结算,而非创建订单时的初始金额。


状态说明

订单状态(pay_status)

说明
0 待处理
1 处理中
2 已支付
3 已完成
4 已取消
5 已过期

客服处理状态(pay_serviceStatus)

说明
0 待客服处理
1 客服已接单
2 客服处理中
3 客服处理完成
4 客服拒绝

错误码说明

通用错误

code msg 说明
error 读取请求体失败 请求体读取错误
error 解析请求数据失败 JSON解析错误
error 签名不能为空 未提供签名
error 签名校验失败 签名验证不通过
error 无效的代理ID或代理未配置密钥 代理ID不存在或未配置密钥

创建订单错误

code msg 说明
error 代理ID(pay_agentID)不能为空 缺少必填参数
error 产品方订单号(pay_externalOrderNo)不能为空 缺少必填参数
error 用户ID(pay_userID)不能为空 缺少必填参数
error 用户名称(pay_userName)不能为空 缺少必填参数
error 用户头像(pay_userAvatar)不能为空 缺少必填参数
error 订单金额(pay_amount)必须大于0 金额无效(必须大于0)
error 订单成功通知地址(pay_notifyurl)不能为空 缺少回调地址

查询订单错误

code msg 说明
error 代理ID(pay_agentID)不能为空 缺少必填参数
error 外部订单号(pay_externalOrderNo)不能为空 缺少必填参数
error 订单不存在 订单未找到
error 订单不属于该代理 订单与代理ID不匹配

示例代码

Python 签名生成示例

import hashlib

def generate_sign(params, merchant_key):
    """
    生成MD5签名
    :param params: 参数字典
    :param merchant_key: 商户密钥
    :return: 签名字符串(大写)
    """
    # 1. 移除空值和sign字段
    filtered_params = {
        k: v for k, v in params.items()
        if k != 'sign' and v and str(v).strip()
    }
    
    # 2. 排序
    sorted_params = sorted(filtered_params.items())
    
    # 3. 拼接参数
    sign_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
    
    # 4. 拼接密钥
    sign_str += f"&key={merchant_key}"
    
    # 5. MD5加密并转大写
    md5_hash = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
    return md5_hash.upper()

Java 签名生成示例

import java.security.MessageDigest;
import java.util.*;

public class SignUtil {
    public static String generateSign(Map<String, String> params, String merchantKey) {
        // 1. 移除空值和sign字段
        Map<String, String> filteredParams = new TreeMap<>();
        for (Map.Entry<String, String> entry : params.entrySet()) {
            if (!entry.getKey().equals("sign") && 
                entry.getValue() != null && 
                !entry.getValue().trim().isEmpty()) {
                filteredParams.put(entry.getKey(), entry.getValue());
            }
        }
        
        // 2. 排序(TreeMap自动按ASCII排序)
        // 3. 拼接参数
        StringBuilder sb = new StringBuilder();
        int index = 0;
        for (Map.Entry<String, String> entry : filteredParams.entrySet()) {
            if (index > 0) {
                sb.append("&");
            }
            sb.append(entry.getKey()).append("=").append(entry.getValue());
            index++;
        }
        
        // 4. 拼接密钥
        sb.append("&key=").append(merchantKey);
        
        // 5. MD5加密并转大写
        return md5(sb.toString()).toUpperCase();
    }
    
    private static String md5(String input) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(input.getBytes("UTF-8"));
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException("MD5加密失败", e);
        }
    }
}

Go 签名生成示例

package main

import (
    "crypto/md5"
    "encoding/hex"
    "sort"
    "strings"
)

func GenerateSign(params map[string]string, merchantKey string) string {
    // 1. 移除空值和sign字段
    keys := make([]string, 0, len(params))
    for k, v := range params {
        if k != "sign" && strings.TrimSpace(v) != "" {
            keys = append(keys, k)
        }
    }
    
    // 2. 排序
    sort.Strings(keys)
    
    // 3. 拼接参数
    var builder strings.Builder
    for i, k := range keys {
        if i > 0 {
            builder.WriteString("&")
        }
        builder.WriteString(k)
        builder.WriteString("=")
        builder.WriteString(params[k])
    }
    
    // 4. 拼接密钥
    builder.WriteString("&key=")
    builder.WriteString(merchantKey)
    signPlain := builder.String()
    
    // 5. MD5加密并转大写
    sum := md5.Sum([]byte(signPlain))
    return strings.ToUpper(hex.EncodeToString(sum[:]))
}

注意事项

  1. 签名安全: 商户密钥请妥善保管,不要泄露给第三方
  2. 回调地址: 回调地址必须是公网可访问的HTTPS地址
  3. 幂等性: 创建订单时,pay_externalOrderNo 必须唯一,重复提交可能导致订单创建失败
  4. 时间格式: 时间字段格式为 YYYY-MM-DD HH:mm:ss
  5. 金额单位: 所有金额单位均为"元"(例如:100.00元),使用浮点数表示,保留两位小数
  6. 字符编码: 所有请求和响应均使用UTF-8编码
  7. 响应解析: 注意 data 字段是JSON字符串,需要二次解析
  8. 回调重试: 请确保回调接口能够正确处理重试请求,避免重复处理订单
  9. 回调响应: 回调接口必须返回HTTP状态码200,并在响应体中输出OK(没有双引号,OK 两个字母大写)

技术支持

如有问题,请联系技术支持团队。

文档版本: v1.0
最后更新: 2024-01-01