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

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、最后测试效果图


相关推荐
武子康14 分钟前
大数据-258 离线数仓 - Griffin架构 配置安装 Livy 架构设计 解压配置 Hadoop Hive
java·大数据·数据仓库·hive·hadoop·架构
zpjing~.~1 小时前
Mongo 分页判断是否有下一页
数据库
2401_857600951 小时前
技术与教育的融合:构建现代成绩管理系统
数据库·oracle
豪宇刘1 小时前
MyBatis的面试题以及详细解答二
java·servlet·tomcat
秋恬意1 小时前
Mybatis能执行一对一、一对多的关联查询吗?都有哪些实现方式,以及它们之间的区别
java·数据库·mybatis
潇湘秦2 小时前
一文了解Oracle数据库如何连接(1)
数据库·oracle
雅冰石2 小时前
oracle怎样使用logmnr恢复误删除的数据
数据库·oracle
web前端神器2 小时前
mongodb给不同的库设置不同的密码进行连接
数据库·mongodb
从以前2 小时前
Berlandesk 注册系统算法实现与解析
数据库·oracle
Muko_0x7d22 小时前
Mongodb
数据库·mongodb