【乐企】有关乐企能力测试接口对接(详细)

1、申请密钥

2、验证本地服务器与乐企服务器的连通性

乐企服务器生产和测试域名均为:https://lqpt.chinatax.gov.cn:8443。开发者可以在"能力中心"查看基础公用能力详情,按照能力接入和开发指引完成接口对接,验证服务器连通性和证书配置正确性。

能力编码:能力是乐企平台提供某一具体业务的形式,某一具体能力由一个或多个相互关联的服务(接口)和调用规则组成,能力编码是乐企平台对某一具体能力的唯一识别编号,可在能力订阅请求页面和测试中心详情页查询:

服务编码:服务是构成乐企平台某一具体能力的接口,服务(接口)分为实时和异步,实时接口可实时返回请求所需数据,异步接口返回流水号或批次流水号,可在处理完成后调用对应查询接口获得所需数据,服务编码是乐企平台某一具体能力中的服务(接口)的唯一识别码,可在能力详情页面和测试中心详情页查询。

用例编码 :用例是乐企能力订阅沙箱测试的场景及要求描述,用例编码是乐企平台某一具体能力用例的唯一识别码,可在测试中心详情页查询。用例编码在沙箱能力请求的报文头中,和沙箱环境测试标识配套,两者互斥使用,用例编码仅在乐企沙箱订阅测试使用。

接口开发背景:

是帮助别的公司搭建乐企平台,客户在申请乐企资质的时候 填写的ip是他们自己服务器的ip(内网ip)还需要使用VPN访问,这就给本地开发测试造成很大的麻烦,我们这边的解决思路是:在客户的内网环境的nginx配置代理:

location /leqi-api/ {

​ proxy_pass https://lqpt.chinatax.gov.cn:8843/

}

之后本地通过url进行请求,url对应的ip是客户的内网ip(但凡是公网ip都不用这么麻烦!)

3、后端代码

基础入参:

java 复制代码
 @Data
public class BaseLeQiReq {
    private String url;
    /**
     * 能力编码
     */
    private String nlbm;
    /**
     * 服务编码
     */
    private String fwbm;
    /**
     * 用例编码
     */
    private String ylbm;
    /**
     * 接入单位id
     */
    private String jrdybm;
    /**
     * 使用单位id
     */
    private String sydwbm;

    /**
     * 请求参数(单个对象)
     */
    private JSONObject param;
    /**
     * 请求参数(数组)
     */
    private List<JSONObject> paramList;
}

基础出参

java 复制代码
@Data
public class BaseLeQiResp {
    private ResponseData Response;

    @Data
    public static class ResponseData {
        private String RequestId;
        /**
         * 成功数据
         * returncode: "00"
         */
        private String Data;
        /**
         * 失败数据(存在Error节点标识失败)
         */
        private JSONObject Error;
    }
}

加密工具类(直接按照乐企平台给出的示例代码即可)

java 复制代码
import org.apache.commons.lang3.ObjectUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import org.bouncycastle.util.encoders.Hex;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Base64;


/**
 * User: yanjun.hou
 * Date: 2024/8/27 09:51
 * Description:
 */
public class SM4Util {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private static final Charset ENCODING = StandardCharsets.UTF_8;
    public static final String ALGORITHM_NAME = "SM4";
    // 加密算法/分组加密模式/分组填充方式
    // PKCS5Padding-以8个字节为一组进行分组加密
    // 定义分组加密模式使用:PKCS5Padding
    public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";


    /**
     * 生成ECB暗号
     *
     * @param algorithmName 算法名称
     * @param mode          模式
     * @param key           密码
     * @explain ECB模式(电子密码本模式:Electronic codebook)
     */
    private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
        Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
        Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
        cipher.init(mode, sm4Key);
        return cipher;
    }

    /**
     * sm4加密
     *
     * @param hexKey   16进制密钥(忽略大小写)
     * @param paramStr 待加密字符串
     * @return 返回Base64后加密字符串
     * @explain 加密模式:ECB
     */
    public static String encryptEcb(String hexKey, String paramStr) throws Exception {
        // 16进制字符串-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // String-->byte[]
        byte[] srcData = paramStr.getBytes(ENCODING);
        // 加密后的数组
        byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
        // byte[]-->hexString
        String cipherText = Base64.getEncoder().encodeToString(cipherArray);
        return cipherText;
    }

    /**
     * 加密模式为ECB
     *
     * @param key  2进制密钥
     * @param data 2进制原文
     * @return 二进制密文
     */
    public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
        return cipher.doFinal(data);
    }

    /**
     * sm4解密
     *
     * @param hexKey     16进制密钥
     * @param cipherText 16进制的加密字符串(忽略大小写)
     * @return 解密后的字符串
     * @explain 解密模式:采用ECB
     */
    @SuppressWarnings("UnnecessaryLocalVariable")
    public static String decryptEcb(String hexKey, String cipherText) throws Exception {
        // hexString-->byte[]
        byte[] keyData = ByteUtils.fromHexString(hexKey);
        // hexString-->byte[]
        byte[] cipherData = Base64.getDecoder().decode(cipherText);
        // 解密
        byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
        // 接收解密后的字符串 byte[]-->String
        String decryptStr = new String(srcData, ENCODING);
        return decryptStr;
    }

    /**
     * sm4解密
     *
     * @param key        2进制密钥
     * @param cipherText 2进制密文
     * @return 解密后的2进制原文
     */
    public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
        Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
        return cipher.doFinal(cipherText);
    }
}

调用接口工具类

java 复制代码
import cn.hutool.core.date.DateUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.leqi.domain.req.BaseLeQiReq;
import com.ruoyi.leqi.domain.resp.BaseLeQiResp;
import netscape.javascript.JSObject;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import java.util.UUID;


/**
 * User: yanjun.hou
 * Date: 2024/8/27 09:59
 * Description:
 */
public class InvokeLeQiUtils {
    static Logger logger = LoggerFactory.getLogger(InvokeLeQiUtils.class);
    static String secret = "xxx";//乐企秘钥
    static String jrdwptbh = "xxx";//接入单位id
    static String sydwptbh = "xxx";//使用单位id
    static String ylbm = "911001";//用例编码
    static String nsrsbh = "xxx";//纳税人识别号
    static String reqUrl = "https://lqpt.chinatax.gov.cn:8443/access/sandbox/v2/invoke";//沙箱环境

    public static JSONObject invokeApi(BaseLeQiReq baseLeQiReq) {
        String nlbm = baseLeQiReq.getNlbm();
        String fubm = baseLeQiReq.getFwbm();
        if (ObjectUtils.isNotEmpty(baseLeQiReq.getUrl())) {
            reqUrl = baseLeQiReq.getUrl();
        }
        String url = String.format("%s/%s/%s", reqUrl, nlbm, fubm);
        if (reqUrl.contains("kzfw")) {//如果是控制服务,则不拼接能力编码
            url = String.format("%s/%s", reqUrl, fubm);
        }
        if (ObjectUtils.isNotEmpty(baseLeQiReq.getYlbm())) {
            ylbm = baseLeQiReq.getYlbm();
        }

        return invoke(url, baseLeQiReq, secret, jrdwptbh, sydwptbh, ylbm);
    }

    /**
     * 调用乐企接口
     *
     * @param url         接口地址
     * @param baseLeQiReq
     * @param key         密钥
     * @param jrdwptbh    接入单位ID
     * @param sydwptbh    使用单位ID
     * @param ylbm        用例编码
     * @return
     */
    private static JSONObject invoke(String url, BaseLeQiReq baseLeQiReq, String key, String jrdwptbh, String sydwptbh, String ylbm) {
        try {
            if (ObjectUtils.isNotEmpty(baseLeQiReq.getFwbm())) {
                switch (baseLeQiReq.getFwbm()) {
                    case "QDFPPLFM":
                        handlerParams(baseLeQiReq, jrdwptbh, sydwptbh);
                    case "XZTHSXED":
                        handlerParams(baseLeQiReq, jrdwptbh, sydwptbh);
                    default:
                }
            }
            String param = JSON.toJSONString(baseLeQiReq.getParam());
            //如果接口接收的参数是list 比如【数字化电子发票上传】接口
            if (ObjectUtils.isNotEmpty(baseLeQiReq.getParamList())) {
                param = JSON.toJSONString(baseLeQiReq.getParamList());
            }
            String encParam = SM4Util.encryptEcb(key, param);

            logger.info(String.format("InvokeLeQiUtils | invoke 请求乐企服务地址:%s ;请求乐企原始报文:%s ;请求乐企加密报文:%s ;", url, param, encParam));

            HttpRequest request = HttpRequest.post(url)
                    .header("Content-Type", "application/json;charset=UTF-8")
                    .header("fwbm", baseLeQiReq.getFwbm())//添加服务编码
                    .header("jrdwptbh", jrdwptbh)//添加接入平台编号
                    .header("sydwptbh", sydwptbh)//添加使用平台编号
                    .header("nlbm", baseLeQiReq.getNlbm())//添加能力编码
                    .header("ylbm", ylbm)//添加用例编码
                    .header("access_signature", "")// 添加访问签名
                    .header("sxcsbz", "") // 添加沙箱测试标志
                    .charset(StandardCharsets.UTF_8)
                    .timeout(90000);//超时,毫秒;
            if (ObjectUtils.isNotEmpty(encParam)) {
                request.body(encParam);
            }

            String encRes = request.execute().body();


            logger.info(String.format("InvokeLeQiUtils | invoke 请求乐企响应加密报文:%s ", encRes));


            JSONObject jsonObject = JSON.parseObject(encRes);
            if (ObjectUtils.isEmpty(jsonObject.getString("body"))) {
                return jsonObject;
            }
            if (!"200".equals(jsonObject.getString("httpStatusCode"))) {
                return jsonObject;
            }
            BaseLeQiResp resp = JSON.parseObject(jsonObject.getString("body"), BaseLeQiResp.class);
            if (ObjectUtils.isEmpty(resp.getResponse()) || ObjectUtils.isEmpty(resp.getResponse().getData())) {
                return JSONObject.parseObject(JSON.toJSONString(resp));
            }

            String decRes = SM4Util.decryptEcb(key, resp.getResponse().getData());

            logger.info(String.format("InvokeLeQiUtils | invoke 请求乐企响应解密报文:%s ;", decRes));

            return JSON.parseObject(decRes);
        } catch (Exception e) {
            logger.error(String.format("InvokeLeQiUtils | invoke 请求乐企接口异常,异常信息:%s ", e));
            throw new RuntimeException(e);
        }
    }

    /**
     * 处理参数缺失,按照规则提供默认值
     * @param baseLeQiReq
     * @param jrdwptbh
     * @param sydwptbh
     */
    private static void handlerParams(BaseLeQiReq baseLeQiReq, String jrdwptbh, String sydwptbh) {
        if (ObjectUtils.isNotEmpty(baseLeQiReq.getParam())) {
            JSONObject param = baseLeQiReq.getParam();
            if (ObjectUtils.isEmpty(param.getString("ywlsh"))) {
                param.put("ywlsh", sydwptbh + jrdwptbh + UUID.randomUUID().toString().replaceAll("-", ""));
            }
        }
    }

}

4、接口调用

示例图

说明:其中参数中的【url】使用的是对应在乐企申请资质的时候报备的ip 由于我们这边的特殊性质(内网+不是自己服务器ip)所以加了这个url进行转发,实际情况看自己使用(我代码里有判断,如果参数存在url则按照url请求,否则按照税局的沙箱环境地址请求);

响应参数:我这边只取了乐企接口返回的body解密的数据

首先调用【开票控制服务】的几个接口

1、获取数字化电子发票批量预赋码信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "QDFPPLFM",
	"param": {
		"nsrsbh": "x x x x",
		"lysl": "5000",
		"ywlsh": ""
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

2、查询授信额度

java 复制代码
{
    "nlbm": "202007",
    "fwbm": "CXSXED",
    "param": {
        "nsrsbh": "x x x x"
    },
    "url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

3、下载或退回授信额度

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "XZTHSXED",
	"param": {
		"nsrsbh": "x x x x",
		"ptbh": "x x x x",
		"sqlx": "0",
		"sqed": "1100.00",
		"ywlsh": ""
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

4、调整授信额度有效期

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "TZSXEDYXQ",
	"param": {
		"xsfnsrsbh": "x x x x",
		"sxedsq": "2024-08"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

5、查询纳税人风险信息

java 复制代码
{
    "nlbm": "202007",
    "fwbm": "CXNSRFXXX",
    "param": {
        "nsrsbh": "x x x x"
    },
    "url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

6、查询纳税人基本信息

java 复制代码
{
    "nlbm": "202007",
    "fwbm": "CXNSRJBXX",
    "param": {
        "nsrsbh": "x x x x"
    },
    "url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

7、查询可用税率信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXKYSL",
	"param": {},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

8、查询税收分类编码信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXSSFLBM",
	"param": {
		"nsrsbh": "x x x x",
		"sjc": "",
		"sjswjgdm": "14200000000"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

9、查询数字化电子红字确认单列表信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXQDHZQRDLB",
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke",
	"param": {
		"yhjslx": "0",
		"xsfnsrsbh": "x x x x",
		"xsfmc": "****集团有限责任公司",
		"gmfnsrsbh": "",
		"gmfmc": "",
		"lrfsf": "0",
		"lrrqq": "2024-01-01",
		"lrrqz": "2024-09-01",
		"lzfpdm": "",
		"lzfphm": "",
		"hzfpxxqrdbh": "",
		"hzqrxxztDm": "",
		"fppzDm": "",
		"pageNumber": "1",
		"pageSize": "10"
	}
}

10、查询数字化电子红字确认单明细信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXQDHZQRDMX",
	"param": {
		"uuid": "321312312312312 ",
		"xsfnsrsbh": "x x x x"
	}
}

11、数字化电子发票上传

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "QDFPSC",
	"paramList": [
		{
			"fphm": "24997000006472271411",//对应【获取数字化电子发票批量预赋码信息】起始号段 【开始>=该值<=结束】都可以 
			"lzfpbz": "0",
			"ptbh": "x x x x",
			"fppz": "02",
			"gmfzrrbz": "N",
			"tdys": "",
			"qyDm": "420600000000",
			"cezslxDm": "",
			"sgfplxDm": "",
			"ckywsyzcDm": "",
			"zzsjzjtDm": "",
			"xsfnsrsbh": "x x x x",
			"xsfmc": "***有限责任公司",
			"xsfdz": "",
			"xsfdh": "",
			"xsfkhh": "",
			"xsfzh": "",
			"gmfnsrsbh": "9******372095",//对应[税控蓝票初始化的购方税号]
			"gmfmc": "北京***科技有限公司",
			"gmfdz": "",
			"gmfdh": "",
			"gmfkhh": "",
			"gmfzh": "",
			"gmfjbr": "",
			"jbrsfzjhm": "",
			"gmfjbrlxdh": "",
			"hjje": 100,
			"hjse": 13,
			"jshj": 113,
			"skyhmc": "",
			"skyhzh": "",
			"jsfs": "",
			"ysxwfsd": "",
			"kpr": "张三",
			"kprzjhm": "412725****57***",
			"kprzjlx": "201",
			"dylzfphm": "",
			"hzqrxxdbh": "",
			"hzqrduuid": "",
			"bz": "备注",
			"ip": "报备乐企对应ip",
			"macdz": "20-16-1E-00-02-12",
			"cpuid": "",
			"zbxlh": "",
			"kprq": "2024-08-28 11:11:11",
			"sfzsxsfyhzhbq": "",
			"sfzsgmfyhzhbq": "",
			"skrxm": "",
			"fhrxm": "",
			"fpmxList": [
				{
					"mxxh": "1",
					"dylzfpmxxh": "",
					"hwhyslwfwmc": "*信息技术服务*技术服务费",
					"spfwjc": "信息技术服务",
					"xmmc": "技术服务费",
					"ggxh": "",
					"dw": "",
					"sl": "1",
					"dj": "100",
					"je": 100,
					"slv": 0.13,
					"se": 13,
					"hsje": 113,
					"kce": "",
					"sphfwssflhbbm": "3040201010000000000",
					"fphxz": "00",
					"yhzcbs": ""
				}
			],
			"fjysList": [
				{
					"fjysmc": "",
					"fjyslx": "",
					"fjysz": ""
				}
			]
		}
	],
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

12、查询数字化电子发票上传结果

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXQDFPSCJG",
	"param": {
		"sllsh": "902aedfe8dc141928b170bf3fd8bd0fd"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

13、数字化电子红字确认单申请

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "QDHZQRDSQ",
	"param": {
		"lrfsf": "0",
		"xsfnsrsbh": "x x x x",
		"xsfmc": "****集团有限责任公司",
		"gmfnsrsbh": "91990****372095",
		"gmfmc": "北京**科技有限公司",
		"lzfpdm": "",
		"lzfphm": "24997000006472271411",
		"sfzzfpbz": "N",
		"lzkprq": "2024-08-28 11:11:11",
		"lzhjje": "113.00",
		"lzhjse": "13.00",
		"lzfppzDm": "01",
		"lzfpTdyslxDm": "",
		"hzcxje": "-100.00",
		"hzcxse": "-13.00",
		"chyyDm": "01",
		"hzqrdmxList": [
			{
				"lzmxxh": "1",
				"xh": "1",
				"sphfwssflhbbm": "3040201010000000000",
				"hwhyslwfwmc": "*信息技术服务*技术开发费",
				"spfwjc": "信息技术服务",
				"xmmc": "技术开发费",
				"ggxh": "",
				"dw": "",
				"fpspdj": "",
				"fpspsl": "",
				"je": "100.00",
				"sl1": "0.13",
				"se": "13"
			}
		]
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

14、数字化电子红字确认单确认

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "QDHZQRDQR",
	"param": {
		"xsfnsrsbh": "x x x x",
		"uuid": "9d1c747182d043b892bcf9e4816b7cfe",
		"hzqrdbh": "99000024088000309024",
		"qrlx": "C"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

15、上传发票汇总确认信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "SCFPHZQRXX",
	"param": {
		"xsfnsrsbh": "x x x x",
		"xsfsjswjgdm": "14200000000",
		"ywlx": "0",
		"ptbh": "x x x x",
		"yf": "2024-08",
		"lzfpsl": "0",
		"lzfpje": "0.00",
		"lzfpse": "0.00",
		"hzfpsl": "0",
		"hzfpje": "0.00",
		"hzfpse": "0.00"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

16、查询发票汇总确认信息

java 复制代码
{
	"nlbm": "202007",
	"fwbm": "CXFPHZQRXX",
	"param": {
		"xsfnsrsbh": "x x x x",
		"xsfsjswjgdm": "14200000000",
		"ptbh": "x x x x",
		"yf": "2024-08"
	},
	"url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

17、查询差额征税编码

java 复制代码
{
  "nlbm": "202007",
  "fwbm": "CXCEZSBM",
  "param": {},
  "url": "http://报备乐企对应ip端口/leqi-api/access/sandbox/v2/invoke"
}

5、最后测试效果图


相关推荐
01传说3 分钟前
vue3 配置安装 pnpm 报错 已解决
java·前端·vue.js·前端框架·npm·node.js
hunzi_141 分钟前
搭建商城系统
java·uni-app·php
workflower1 小时前
MDSE和敏捷开发相互矛盾之处:方法论本质的冲突
数据库·软件工程·敏捷流程·极限编程
Boilermaker19921 小时前
【Java EE】Mybatis-Plus
java·开发语言·java-ee
Tony小周2 小时前
实现一个点击输入框可以弹出的数字软键盘控件 qt 5.12
开发语言·数据库·qt
xdscode2 小时前
SpringBoot ThreadLocal 全局动态变量设置
java·spring boot·threadlocal
lifallen2 小时前
Paimon 原子提交实现
java·大数据·数据结构·数据库·后端·算法
丶小鱼丶2 小时前
链表算法之【合并两个有序链表】
java·算法·链表
TDengine (老段)2 小时前
TDengine 数据库建模最佳实践
大数据·数据库·物联网·时序数据库·tdengine·涛思数据
张先shen2 小时前
Elasticsearch RESTful API入门:全文搜索实战(Java版)
java·大数据·elasticsearch·搜索引擎·全文检索·restful