# 未开票收入管理
# 未开票查询
# URL
POST
Content-Type:application/json
http://{HOST}:{PORT}/output-tax/api/invoice-will/result
# 请求体参数
参数 | 是否必填 | 类型 | 描述 | 说明 |
---|---|---|---|---|
lyid | 否 | String | 单据号 | |
djqqlsh | 是 | String | 单据请求流水号 ,单据请求流水号和单据号不能同时为空 | |
orgCode | 是 | String | 组织编码 | |
pageNum | 是 | int | 页码 | |
pageSize | 是 | String | 每页数据数量 |
# 请求体样例
{
"lyid":"单据",
"djqqlsh":"",
"orgCode":"组织编码",
"pageNum":1,
"pageSize":15
}
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 | |
accountNote | String | 记账备注 | |
accountStatus | String | 记账状态:1-未记账 2-已记账 | |
accountTime | String | 记账日期 yyyy-MM-dd | |
accountUser | String | 记账人 | |
accountVoucherNo | String | 记账凭证号 | |
bz | String | 备注 | |
djqqlsh | String | 单据请求流水号 | |
fhr | String | 复核人 | |
fplx | String | ||
gmfDzdh | String | 购买方地址电话 | |
gmfMc | String | 购买方名称 | |
gmfNsrsbh | String | 购买方纳税人识别号 | |
gmfYhzh | String | 购买方银行账户 | |
hisJe | BigDecimal | 已开票金额 | |
hisSe | BigDecimal | 已开票税额 | |
je | BigDecimal | 单据金额 | |
jshj | BigDecimal | 单据价税合计 | |
kpr | String | 开票人 | |
lyid | String | 单据号 | |
orgName | String | 组织编码 | |
se | BigDecimal | 税额 | |
skr | String | 收款人 | |
unJe | BigDecimal | 待开票金额 | |
xsfDzdh | String | 销售方地址电话 | |
xsfMc | String | 销售方名称 | |
xsfNsrsbh | String | 销售方纳税人识别号 | |
xsfYhzh | String | 销售方银行账户 | |
lc | Integer | 联次 2-二联 3-三联 5-五联 | |
zdrq | String | 制单日期 | |
dw | String | 单位 | |
hh | String | 行号 | |
hisJe | BigDecimal | 已开票金额 | |
hisJshj | BigDecimal | 已开票价税合计 | |
se | BigDecimal | 税额 | |
sl | BigDecimal | 税率 | |
spbm | String | 商品编码 | |
spssflbm | String | 商品税收分类编码 | |
xmje | BigDecimal | 项目金额 | |
xmjshj | BigDecimal | 项目价税合计 | |
xmmc | String | 项目名称 | |
bred | String | 是否被红冲 Y-被红冲 N-正常 | |
fpDm | String | 发票代码 | |
fpHm | String | 发票号码 | |
fplx | String | 发票类型 | |
fpqqlsh | String | 开票请求流水号 | |
hh | String | 行号 | |
se | BigDecimal | 税额 | |
sl | BigDecimal | 税率 | |
status | String | 发票状态 1-待开票 2-开票中 3-开票失败 4-开票成功 | |
xmje | BigDecimal | 项目金额 | |
xmjshj | BigDecimal | 项目价税合计 | |
zfbz | String | 是否作废 Y=已作废,N-未作废, |
# 返回样例
{
"code":"0000",
"datas":{
"dtos":[
{
"accountNote":"32",
"accountStatus":"2",
"accountTime":1592150400000,
"accountVoucherNo":"32",
"bz":"",
"djqqlsh":"1272434265238585345",
"fhr":"",
"fplx":"",
"gmfDzdh":"",
"gmfMc":"123123",
"gmfNsrsbh":"",
"gmfYhzh":"",
"hisJe":10,
"hisSe":1,
"invoiceWillBs":[
{
"dw":"",
"hh":"1",
"hisJe":10,
"hisJshj":11,
"records":[
{
"bred":"N",
"fpDm":"144005044197",
"fpHm":"65264302",
"fplx":"1",
"fpqqlsh":"1272447230817132544",
"hh":"0",
"se":1,
"sl":0.1,
"status":"4",
"xmje":10,
"xmjshj":11,
"zfbz":"N"
}
],
"se":1,
"sl":0.1,
"spbm":"污水处理费",
"spssflbm":"2010300000000000000",
"xmje":10,
"xmjshj":11,
"xmmc":"污水处理费"
}
],
"je":10,
"jshj":11,
"kpr":"",
"lyid":"",
"orgName":"0000ceshi",
"se":1,
"skr":"",
"unJe":0,
"xsfDzdh":"北京市海淀区北清路68号 010-86396688",
"xsfMc":"太极计算机股份有限公司",
"xsfNsrsbh":"111222333456111",
"xsfYhzh":"一",
"zdrq":"2020-06"
}
],
"totalCount":1
},
"msg":"SUCCESS"
}
# 未开票管理新增单据
# URL
POST
Content-Type:application/json; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoice-will/save
# 请求体
{
"lyid":"单据",
"lydjh":"来源单据号",
"djqqlsh":"单据请求流水号",
"orgCode":"组织编码",
"gmfMc":"购买方名称",
"gmfDzdh":"",
"gmfDz":"购买方地址", (数电专用字段)
"gmfDh":"购买方电话", (数电专用字段)
"gmfNsrsbh":"", (发票类型31数电专票,购买方纳税人识别号必填)
"gmfYhzh":"",
"gmfYh":"购买方银行",(数电专用字段)
"gmfZh":"购买方账号",(数电专用字段)
"revphone":"联系人手机号",
"revemail":"联系人邮箱",
"lc":"",
"bz":"",
"fplx":"", (发票类型31数电专票,发票类型32数电普票,发票类型是数电发票,购买方地址电话和银行账号需要分开传值。)
"zdrq ":"",
"revurl1":"开票、作废回调地址",
"invoiceWillBs":[{
"xmmc":"项目(商品)名称",
"xmjshj":"价税合计",
"hh":"行号",
"spbm":"商品编码",
"xmhsdj":"商品含税单价",
"xmsl":"商品数量",
"se":"税额",
"spssflbm":"商品税收分类编码",(商品税收分类编码必填)
"sl":"税率",
"dw":"计量单位",
"ggxh":"规格型号"
}]
}
# 请求体参数
参数 | 是否必填 | 类型 | 描述 | 说明 |
---|---|---|---|---|
lyid | 否 | String | 单据号 | |
lydjh | 否 | String | 来源单据号 | |
djqqlsh | 是 | String | 单据请求流水号 | |
orgCode | 是 | String | 组织编码 | |
gmfMc | 是 | String | 购买方名称 | |
gmfDzdh | 否 | String | 购买方地址电话 | |
gmfDz | 否 | String | 购买方地址 | |
gmfdh | 否 | String | 购买方电话 | |
gmfNsrsbh | 否 | String | 购买方纳税人识别号 | |
gmfYhzh | 否 | String | 购买方银行账户 | |
gmfYh | 否 | String | 购买方银行 | |
gmfzh | 否 | String | 购买方账户 | |
revphone | 否 | String | 联系人手机号 | |
revemail | 否 | String | 联系人邮箱 | |
lc | 否 | String | 联次 | |
bz | 否 | String | 联次 | |
fplx | 否 | String | 发票类型 | |
xmmc | 是 | String | 商品名称 | 匹配商品档案,档案中商品名称不唯一, |
spbm | 否 | String | 商品编码 | 匹配商品档案,档案中商品编码唯一 |
xmjshj | 是 | BigDecimal | 项目价税合计 | |
hh | 是 | String | 行号 | |
xmhsdj | 否 | BigDecimal | 商品含税单价 | |
xmsl | 否 | BigDecimal | 商品数量 | |
se | 否 | BigDecimal | 税额 | |
spssflbm | 是 | String | 商品税收分类编码 | |
sl | 否 | BigDecimal | 税率 | |
dw | 否 | String | 计量单位 | |
ggxh | 否 | String | 规格型号 | |
zzstsgl | 否 | String | 优惠政策说明 | |
zdrq | 是 | String | 制单日期 | 格式为yyyy-MM-dd |
revurl1 | 否 | String | 待开票明细结果回传地址 | 合法url地址 |
优惠政策或税率等匹配平台物料档案的前置条件
TIP
商品税收分类编码spssflbm是空 或者 税率sl是空 或者 (商品编码(物料编码)spbm不为空 但是优惠政策说明zzstsgl为空,且税率sl是3%或5%) 再或者 (来源单据类型为供应链lydjlx=1 并且 规格型号为空)或者 (物料ID:productId不为空 并且 税率sl是空或税率是0)
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 |
# 返回样例
{
"code":"0000",
"msg":"success"
}
# 待开票明细回传结果样例
[{
"data":"{\"djqqlsh\":\"2023033101480035\",\"corpId\":\"um86vloe\",\"orgId\":1467284815433695241,\"orgName\":\"百望888开票点\",\"code\":\"1641688270826569728\",\"fplx\":\"3\",\"xsfNsrsbh\":\"111222333456888\",\"xsfMc\":\"用友税务云\",\"xsfDzdh\":\"测试地址和电话 13144445555\",\"xsfYhzh\":\"北京啦啦啦 345677890098990\",\"gmfNsrsbh\":\"1QAZ2WSX3EDC4RF\",\"gmfMc\":\"htt0331003\",\"gmfDzdh\":\"北清路68号13141422677\",\"gmfYhzh\":\"北京银行6345678987655678\",\"kpr\":\"111\",\"skr\":\"htt收款222\",\"fhr\":\"htt复核人\",\"zdybz\":\"自定义备注写了100字以内呢哈哈哈哈\",\"lylx\":\"2\",\"zdzfbz\":\"N\",\"lyid\":\"2237227707797763\",\"lydjh\":\"NBJS20221122120801\",\"creator\":\"348af872-f245-4eed-ab16-ed36f6cf5e37\",\"zdrq\":\"2023-03-23\",\"accountStatus\":\"1\",\"je\":400.00,\"jshj\":800.00,\"createTime\":1680243960000,\"ts\":1680246797000,\"isBilling\":\"1\",\"sqr\":\"北京人\",\"sqbm\":\"北京部门我访问的哦文件啦啦队冷冻机房了安家费了98698哦肌司研……%*&……(\",\"gmfDz\":\"北清路68号\",\"gmfDh\":\"13141422677\",\"gmfYh\":\"北京银行\",\"gmfZh\":\"6345678987655678\",\"se\":400.00,\"hisJe\":3.00,\"unJe\":397.00,\"hisSe\":0.00,\"redInfos\":[],\"enableRed\":false,\"invoiceWillBs\":[{\"id\":1692594719821922312,\"hh\":\"1\",\"spbm\":\"06000001\",\"xmmc\":\"餐费\",\"spssflbm\":\"3070401000000000000\",\"ggxh\":\"千克*箱\",\"dw\":\"套\",\"xmhsdj\":100.0000000000,\"xmsl\":2.0000000000,\"xmje\":200.00,\"sl\":0.000000,\"se\":0.00,\"xmjshj\":200.00,\"hisJe\":3.00,\"hisJshj\":3.00,\"hisZke\":0.00,\"hisSpsl\":0.0300000000,\"taxclassname\":\"餐饮服务\",\"taxclassjc\":\"餐饮服务\",\"createTime\":1680243960000,\"lymxid\":\"2237227707797771\",\"lyid\":\"2237227707797770\",\"ts\":1680246792000,\"isBilling\":\"1\",\"records\":[{\"fplx\":\"3\",\"hh\":\"1\",\"fpqqlsh\":\"1641700152848969728\",\"fpDm\":\"780000000000\",\"fpHm\":\"70000061\",\"kprq\":\"2023-03-31 15:13:15\",\"kprq4DMDB\":1680246795000,\"xmsl\":0.0100000000,\"xmhsdj\":100.0000000000,\"xmje\":1.00,\"xmjshj\":1.00,\"se\":0.00,\"sl\":0.000000,\"status\":\"4\",\"zfbz\":\"N\",\"bred\":\"N\",\"czrq\":\"2023-03-31\",\"createTime\":\"2023-03-31 03:13:12\",\"ts\":1680246796000,\"bid\":1692594719821922312},{\"fplx\":\"3\",\"hh\":\"2\",\"fpqqlsh\":\"1641700152848969728\",\"fpDm\":\"780000000000\",\"fpHm\":\"70000061\",\"kprq\":\"2023-03-31 15:13:15\",\"kprq4DMDB\":1680246795000,\"xmsl\":0.0200000000,\"xmhsdj\":100.0000000000,\"xmje\":2.00,\"xmjshj\":2.00,\"se\":0.00,\"sl\":0.000000,\"status\":\"4\",\"zfbz\":\"N\",\"bred\":\"N\",\"czrq\":\"2023-03-31\",\"createTime\":\"2023-03-31 03:13:12\",\"ts\":1680246796000,\"bid\":1692594719821922312}],\"lslbs\":\"1\",\"fphxz\":0},{\"hh\":\"2\",\"spbm\":\"JF01000216\",\"xmmc\":\"糯米糍粑\",\"spssflbm\":\"1030201020000000000\",\"ggxh\":\"千克*箱\",\"xmhsdj\":300.5000000000,\"xmsl\":2.0000000000,\"xmje\":200.00,\"sl\":0.000000,\"se\":400.00,\"xmjshj\":600.00,\"hisJe\":0.00,\"hisJshj\":0.00,\"zke\":1.00,\"hisZke\":0.00,\"hisSpsl\":0E-10,\"taxclassjc\":\"焙烤食品\",\"createTime\":1680243960000,\"lymxid\":\"2237227707797771\",\"lyid\":\"2237227707797770\",\"ts\":1680243960000,\"lslbs\":\"3\",\"fphxz\":0,\"zkhbs\":\"Y\"}]}",
"code":"0000",
"msg":"开票成功",
"fpqqlsh":"2023033101480035",
"corpid":"um86vloe"
}]
# 待开票明细回传参数说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 开票信息码 | 0000代表成功 |
msg | String | 开票失败信息 | |
fpqqlsh | String | 发票请求流水号 | |
corpid | String | 租户id | |
data | String | 发票详细信息 |
# 待开票明细回传data说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 | |
accountNote | String | 记账备注 | |
accountStatus | String | 记账状态:1-未记账 2-已记账 | |
accountTime | String | 记账日期 yyyy-MM-dd | |
accountUser | String | 记账人 | |
accountVoucherNo | String | 记账凭证号 | |
bz | String | 备注 | |
djqqlsh | String | 单据请求流水号 | |
fhr | String | 复核人 | |
fplx | String | ||
gmfDzdh | String | 购买方地址电话 | |
gmfMc | String | 购买方名称 | |
gmfNsrsbh | String | 购买方纳税人识别号 | |
gmfYhzh | String | 购买方银行账户 | |
hisJe | BigDecimal | 已开票金额 | |
hisSe | BigDecimal | 已开票税额 | |
je | BigDecimal | 单据金额 | |
jshj | BigDecimal | 单据价税合计 | |
kpr | String | 开票人 | |
lyid | String | 单据号 | |
orgName | String | 组织编码 | |
se | BigDecimal | 税额 | |
skr | String | 收款人 | |
unJe | BigDecimal | 待开票金额 | |
xsfDzdh | String | 销售方地址电话 | |
xsfMc | String | 销售方名称 | |
xsfNsrsbh | String | 销售方纳税人识别号 | |
xsfYhzh | String | 销售方银行账户 | |
lc | Integer | 联次 2-二联 3-三联 5-五联 | |
zdrq | String | 制单日期 | |
dw | String | 单位 | |
hh | String | 行号 | |
hisJe | BigDecimal | 已开票金额 | |
hisJshj | BigDecimal | 已开票价税合计 | |
se | BigDecimal | 税额 | |
sl | BigDecimal | 税率 | |
spbm | String | 商品编码 | |
spssflbm | String | 商品税收分类编码 | |
xmje | BigDecimal | 项目金额 | |
xmjshj | BigDecimal | 项目价税合计 | |
xmmc | String | 项目名称 | |
bred | String | 是否被红冲 Y-被红冲 N-正常 | |
fpDm | String | 发票代码 | |
fpHm | String | 发票号码 | |
fplx | String | 发票类型 | |
fpqqlsh | String | 开票请求流水号 | |
hh | String | 行号 | |
se | BigDecimal | 税额 | |
sl | BigDecimal | 税率 | |
status | String | 发票状态 1-待开票 2-开票中 3-开票失败 4-开票成功 | |
xmje | BigDecimal | 项目金额 | |
xmjshj | BigDecimal | 项目价税合计 | |
zfbz | String | 是否作废 Y=已作废,N-未作废, |
# URL
POST
Content-Type:application/x-www-form-urlencoded
http://{HOST}:{PORT}/output-tax/api/invoiceApply/queryInvoiceStatus
# 请求体参数
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 |
# 请求样例
表单参数如下
fpqqlsh=12345678901234567890
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
fpqqlsh | String | 发票请求流水号 | |
code | String | 状态码 | 详见返回状态码说明 |
msg | String | 信息说明 | |
statuscode | String | 开票状态码 | 1-待开票(需要开票员确认开票);2-开票中;3-开票失败;4-开票成功 |
status | String | 开票状态 | 待开票;开票中;开票失败;开票成功 |
errmsg | String | 开票失败明细 | 开票状态为失败时,此处为明细信息 |
data | String | 开票内容 | 回调内容与开票回调相同 |
# 返回样例
样例1(开票中)
{
"fpqqlsh": "12345678901234567890",
"code": "0000",
"msg": "查询成功",
"statuscode": "1",
"status": "待开票"
}
样例2(开票失败)
{
"fpqqlsh": "12345678901234567890",
"code": "0000",
"msg": "查询成功",
"statuscode": "3",
"status": "开票失败",
"errmsg":"税控设备错误"
}
# 未开票记录变更查询
# URL
POST
Content-Type:application/json; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoice-will/changes
# 请求体参数
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|---|
beginTime | String | 20 | 是 | 查询未开票变更开始时间 时间格式yyyyMMdd hh:mm:ss | |
orgCode | String | 20 | 否 | 查询组织编码非必填 不填写则查询全部开票组织的未开票记录在变更时间之后的 | |
pageNum | Integer | - | 是 | 页数 | |
pageSize | Integer | - | 是 | 每页条数 | |
endTime | String | 20 | 是 | 查询未开票变更结束时间 时间格式yyyyMMdd hh:mm:ss |
# 请求样例
{
"pageSize" : 15,
"pageNum" : 2,
"beginTime":"2020-10-24 16:31:15",
"endTime":"2020-12-24 16:31:15"
}
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
djqqlsh | String | 单据请求流水号 | 单据请求流水号可用于查询该单据的开票明细信息 |
code | String | 状态码 | 详见返回状态码说明 |
msg | String | 信息说明 | |
orgCode | String | 组织编码 | 此单据对应的组织编码 |
totalCount | Integer | 总条数 | 查询变更的总的记录数 |
返回样例
{
"code": "0000",
"datas": {
"invoiceWillChangeList": [
{
"djqqlsh": "1331153392887709697",
"orgCode": "ceshiview"
},
{
"djqqlsh": "1331153932052905985",
"orgCode": "ceshiview"
}
],
"totalCount": 2
},
"msg": "SUCCESS"
}
# 附录
# 样例代码
# Java(适用于JDK1.6及其更高版本)
# Maven配置文件依赖
依赖配置如下
<!-- httpclient,发送HTTP请求 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5</version>
</dependency>
<!-- gson,json转换工具 -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.7</version>
</dependency>
<!-- jjwt,Java Web Token签名工具包 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
# API调用代码
import com.google.gson.GsonBuilder;
import com.yonyou.einvoice.einvoiceApply.JwtParamBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.util.io.pem.PemReader;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @date 2018/5/25
* <p>
* 扫码开票接口测试代码,适用于JDK1.6及更高版本,jdk1.6版本需要对签名方法稍做修改,修改方法在签名方法内已经写明
* 请求参数的注意事项也在参数构建的过程中写明,请详细阅读样例代码。
*/
public class InsertForQRInvoice {
//测试环境有测试appid和证书,正式环境有正式appid和证书,请务必对应使用
//测试环境appid就用这个,正式环境需要替换成正式的
private static String APPID = "commontesterCA";
//这个是测试环境的域名,正式环境为https://fapiao.yonyoucloud.com
private static String DOMAIN = "https://yesfp.yonyoucloud.com";
private static String URL = DOMAIN + "/input-tax/api/pit/report/import?appid=" + APPID;
//pro22.pfx为测试环境通讯证书,正式环境需要替换成正式的
private static String KEYPATH = "src/main/resources/certificate/pro22.pfx";
//证书密码
private static String PASSWORD = "password";
public static void main(String[] args) {
try {
new InsertForQRInvoice().callQRInvoiceApply();
} catch (Exception e) {
e.printStackTrace();
}
}
private static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return HttpClients.createDefault();
}
public void callQRInvoiceApply() throws Exception {
// 提供两种构建HttpClient实例的方法,如果使用被注释掉的方法构建实例报证书不被信任的错误,那么请使用未被注释的构建方法
// HttpClient httpClient = HttpClients.custom().build();
HttpClient httpClient = createSSLClientDefault(); //信任所有https证书
HttpPost httpPost = new HttpPost(URL);
// 构造POST请求体
String body = this.buildRequestDatas();
// 签名
String sign = this.sign(body);
httpPost.addHeader("sign", sign);
httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
StringEntity se = new StringEntity(body.toString(), "UTF-8");
se.setContentType("text/json");
se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
httpPost.setEntity(se);
// 发送http post请求,并得到响应结果
HttpResponse response = httpClient.execute(httpPost);
String result = "";
if (response != null) {
HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "UTF-8");
System.out.println(result);
}
}
}
/**
* 签名
*
* @param paramsMap 表单参数
* @return 签名值
* @throws Exception
*/
private String sign(String paramsMap) throws Exception {
// 读取CA证书与PEM格式证书需要根据实际证书使用情况而定,目前这两种都支持
PrivateKey privateKey = loadPrivateKeyOfCA();
// PrivateKey privateKey = loadPrivateKeyOfPem();
Map<String, Object> claims =
JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
.addJwtId().addIssuedAt().setExpirySeconds(300).setNotBeforeSeconds(300).getClaims();
// 此签名数据必须存在,否则在验证签名时会不通过。
claims.put("requestdatas", getMD5(paramsMap));
// 使用jdk1.6版本时,删除下面代码的中.compressWith(CompressionCodecs.DEFLATE)
String compactJws = Jwts.builder().signWith(SignatureAlgorithm.RS512, privateKey)
.setClaims(claims).compressWith(CompressionCodecs.DEFLATE).compact();
return compactJws;
}
/**
* 计算MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private String getMD5(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
byte[] buf = null;
buf = str.getBytes("utf-8");
MessageDigest md5 = null;
md5 = MessageDigest.getInstance("MD5");
md5.update(buf);
byte[] tmp = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte b : tmp) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
/**
* 读取证书私钥
*
* @return
* @throws UnrecoverableKeyException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
*/
protected PrivateKey loadPrivateKeyOfCA() throws UnrecoverableKeyException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException {
FileInputStream in = new FileInputStream(KEYPATH);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(in, PASSWORD.toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey caprk = (PrivateKey) ks.getKey(alias, PASSWORD.toCharArray());
return caprk;
}
/**
* 构造请求的json数据
*
* @return
*/
private String buildRequestDatas() {
return "这应该是一个json格式字符串";
}
# API相关工具类
package com.yonyou.einvoice.test;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author wangweir
*
*/
public class JwtParamBuilder {
/** JWT {@code Issuer} claims parameter name: <code>"iss"</code> */
public static final String ISSUER = "iss";
/** JWT {@code Subject} claims parameter name: <code>"sub"</code> */
public static final String SUBJECT = "sub";
/** JWT {@code Audience} claims parameter name: <code>"aud"</code> */
public static final String AUDIENCE = "aud";
/** JWT {@code Expiration} claims parameter name: <code>"exp"</code> */
public static final String EXPIRATION = "exp";
/** JWT {@code Not Before} claims parameter name: <code>"nbf"</code> */
public static final String NOT_BEFORE = "nbf";
/** JWT {@code Issued At} claims parameter name: <code>"iat"</code> */
public static final String ISSUED_AT = "iat";
/** JWT {@code JWT ID} claims parameter name: <code>"jti"</code> */
public static final String ID = "jti";
private Map<String, Object> claims;
private final long now;
private JwtParamBuilder() {
claims = new HashMap<>();
now = System.currentTimeMillis() / 1000l;
}
public static JwtParamBuilder build() {
return new JwtParamBuilder();
}
public JwtParamBuilder addIssuedAt() {
claims.put(ISSUED_AT, now);
return this;
}
public JwtParamBuilder setExpirySeconds(final Integer expirySeconds) {
claims.put(EXPIRATION, now + expirySeconds);
return this;
}
public JwtParamBuilder setNotBeforeSeconds(final Integer beforeSeconds) {
claims.put(NOT_BEFORE, now - beforeSeconds);
return this;
}
public JwtParamBuilder setSubject(String sub) {
addOneClaim(SUBJECT, sub);
return this;
}
public JwtParamBuilder setIssuer(String iss) {
addOneClaim(ISSUER, iss);
return this;
}
public JwtParamBuilder setAudience(String aud) {
addOneClaim(AUDIENCE, aud);
return this;
}
public JwtParamBuilder addJwtId() {
return setJwtId(UUID.randomUUID().toString());
}
public JwtParamBuilder setJwtId(String jwtid) {
addOneClaim(ID, UUID.randomUUID().toString());
return this;
}
public JwtParamBuilder claim(String name, Object value) {
if (value == null) {
this.claims.remove(name);
} else {
this.claims.put(name, value);
}
return this;
}
private void addOneClaim(String key, String value) {
if (value != null && value.length() > 0) {
claims.put(key, value);
}
}
/**
* @return the claims
*/
public Map<String, Object> getClaims() {
return claims;
}
}