# 开票申请
# 开蓝票
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/insertWithArray?appid=XXXXX
TIP
每次请求的数据不能超过10张单据
此接口只能用来开具蓝票
签名载荷(Payload)中必须包含属性requestdatas,它的值为表单参数requestdatas的MD5值,具体使用方式参考样例代码示例
# 请求体参数
# 表单说明
参数 | 描述 | 说明 |
---|---|---|
requestdatas | 开票申请发票信息,包含发票头、发票明细 | |
邮件推送配置(可选) | ||
sms | 短信推送配置(可选) | |
url | 回调服务配置(可选) | |
delurl | 退回业务系统回调服务配置(可选) | |
autoAudit | 是否自动审核(可选) | true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认如果不传,代表true |
nc6x-ubl | ncc友企联回调参数 |
# 发票头
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPQQLSH | String | 20 | 是 | 发票请求流水号 | ||
FPLX | String | 1 | 否 | 1 | 发票类型 | 1:增值税电子普通发票; 2:增值税电子专用发票; 3:增值税普通发票; 4:增值税专用发票 、增值税专用发票(机动车); 5:机动车销售统一发票; 8:增值税电子普通发票(成品油); 9:成品油普通发票(卷式); 10:成品油普通发票; 11:成品油专用发票; 12:增值税普通发票(卷式); 15:二手车销售统一发票; |
XSF_NSRSBH | String | 20 | 是 | 销售方纳税人识别号 | ||
XSF_MC | String | 100 | 否 | 销售方名称 | 如果为空,获取发票平台配置的销售方名称 | |
XSF_DZDH | String | 100 | 否 | 销售方地址、电话 | 如果为空,获取发票平台配置的销售方地址及电话 | |
XSF_YHZH | String | 100 | 否 | 销售方银行、账号 | 如果为空,获取发票平台配置的销售方银行及账号 | |
GMF_NSRSBH | String | 20 | 否 | 购买方纳税人识别号 | ||
GMF_MC | String | 100 | 是 | 购买方名称 | ||
GMF_DZDH | String | 100 | 否 | 购买方地址、电话 | ||
GMF_YHZH | String | 100 | 否 | 购买方银行、账号 | ||
KPR | String | 8 | 否 | 开票人 | 如果为空,获取发票平台配置的开票人 | |
SKR | String | 8 | 否 | 收款人 | 如果为空,获取发票平台配置的收款人 | |
FHR | String | 8 | 否 | 复核人 | 如果为空,获取发票平台配置的复核人 | |
JSHJ | Double | 15,2 | 是 | 价税合计 | 两位小数。明细价税合计之和必须与总的价税合计一致 | |
HJJE | Double | 15,2 | 否 | 合计金额 | 两位小数。为空时,根据明细项目进行合计。 | |
HJSE | Double | 15,2 | 否 | 合计税额 | 两位小数。为空时,根据明细项目进行合计。 | |
BZ | String | 200 | 否 | 备注 | ||
LYID | String | 100 | 否 | 请求来源唯一标识 | ||
BMB_BBH | String | 20 | 否 | 编码版本号 | 增加商品编码功能后,税局下载的商品编码表版本 | |
ORGCODE | String | 50 | 否 | 开票点编码 | 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点 | |
WXORDERID | String | 否 | 微信订单号 | 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包 | ||
WXAPPID | String | 否 | 微信商户id | 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包 | ||
WXAUTHID | String | 否 | 微信批量插入卡包的授权id | 如果使用批量插入卡包的授权,需要传此参数,否则不需要 | ||
ZDYBZ | String | 200 | 否 | 自定义备注 | ||
SGBZ | String | 1 | 否 | 收购标志 | 2-农产品收购 | |
ZSFS | String | 1 | 否 | 0 | 征税方式 | 0-普通征税 2-差额征税 |
# 发票明细
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPHXZ | Integer | 1 | 否 | 0 | 发票行性质 | 0-正常行1-折扣行2-被折扣行;如果是 |
XMMC | String | 90 | 是 | 项目名称 | ||
XMBM | String | 90 | 是 | 项目编码 | 如果项目名称为空,会根据项目编码匹配云平台的商品档案 | |
GGXH | String | 40 | 否 | 规格型号 | ||
DW | String | 20 | 否 | 单位 | ||
XMSL | Double | 15,6 | 否 | 项目数量 | ||
XMDJ | Double | 15,6 | 否 | 项目单价 | 项目单价为空时,根据项目金额反算。不为空时不进行计算 | |
XMJE | Double | 15,2 | 否 | 项目金额 | 项目金额为空,根据项目价税合计反算。不为空时不进行计算 | |
XMJSHJ | Double | 15,2 | 是 | 项目价税合计 | ||
SL | Double | 6,6 | 是 | 税率 | 6 位小数,例 17%为 0.17 | |
SE | Double | 15,2 | 否 | 税额 | 税额为空,根据价税合计反算。不为空时不进行计算。 | |
HH | string | 60 | 否 | 行号 | 有折扣时需要必输 | |
ZKHHH | string | 60 | 否 | 折扣行行号 | 有折扣时需要必输 | |
SPBM | string | 19 | 是 | 商品编码 | ||
ZXBM | string | 20 | 否 | 10 | 商品自行编码 | |
YHZCBS | int | 否 | 0 | 销售优惠标识 | 0:不使用,1:使用 | |
LSLBS | string | 1 | 否 | 零税率标识 | 空:非零利率,0:出口退税,1:免税,2:不征收,3普通零税率 | |
ZZSTSGL | string | 50 | 否 | 优惠政策说明 | ||
KCE | Double | 15,2 | 否 | 扣除额 | 扣除额 |
# 邮件推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 短信推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 回调服务配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
url | String | 500 | 是 | 回调url | 开票申请调用方配置回调url,当电子发票开具成功后,调用此服务,POST电子发票相关信息。信息包含:1、pdf版式文件2、发票数据、3、发票下载链接 ncc通过友企联方案回调可不传此参数 | |
intf | String | 200 | 否 | ncc回调接口名 | ncc通过友企联方案回调时必传 | |
method | String | 200 | 否 | ncc回调方法名 | ncc通过友企联方案回调时必传 |
# 退回回调推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
url | String | 500 | 是 | 回调url,使用业务系统退回功能时,退回将会调用此服务,post方式,相关信息1.fpqqlsh发票请求流水号 2.xsfNsrsbh销售方纳税人识别号 | 接收地址,多个地址使用","分隔开 |
# 请求样例
标准表单参数样例
requestdatas=[ {
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"XSF_MC" : "销售方名称",
"XSF_DZDH" : "销售方地址、电话",
"XSF_YHZH" : "销售方银行、账号",
"GMF_NSRSBH" : "购买方纳税人识别号",
"GMF_MC" : "购买方名称",
"GMF_DZDH" : "购买方地址、电话",
"GMF_YHZH" : "购买方银行、账号",
"KPR" : "开票人",
"SKR" : "收款人",
"FHR" : "复核人",
"JSHJ" : "价税合计",
"HJJE" : "合计金额",
"HJSE" : "合计税额",
"BZ" : "备注",
"BMB_BBH" : "10.0",
"items" : [{
"FPHXZ" : "发票行性质",
"XMMC" : "项目名称",
"GGXH" : "规格型号",
"DW" : "单位",
"XMSL" : "项目数量",
"XMDJ" : "项目单价",
"XMJE" : "项目金额",
"XMJSHJ" : "项目价税合计",
"SL" : "税率",
"SE" : "税额",
"HH":"行号",
"SPBM":"商品编码",
"ZXBM":"自行编码"
"KCE":300
}
]
}]
&email=[
{
"fpqqlsh":"12345678901234567890",
"address": "x1@yonyou.com,x2@yonyou.com"
},
{
"fpqqlsh":"12345678901234567890",
"address": "x3@yonyou.com"
}
]
&sms=[{
"fpqqlsh":"12345678901234567890",
"address": "13511111111,13511111112"
}]
&url=[{
"fpqqlsh":"12345678901234567890",
"url": "http://url.com/msg"
}]
&delurl=[{
"fpqqlsh":"12345678901234567890",
"url": "http://url.com/msg"
}]
&nc6x-ubl=[{
"fpqqlsh":"12345678901234567890",
"method":"queryUserPermAppPksOrCodes",
"intf":"nccloud.pubitf.baseapp.apprbac.IAppAndOrgPermQueryPubService"
}]
&autoAudit=true
最小请求参数样例
TIP
销售方地址及电话、银行及账号、销售方名称可以通过销售方纳税人识别号获取
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"GMF_MC" : "购买方名称",
"JSHJ" : 117,
"items" :
[
{
"XMMC" : "项目名称",
"XMJSHJ" : 117,
"SL" : 0.17,
"SPBM" : "1010101020000000000"
}
]
}]
包含折扣行开票样例
TIP
包含折扣行的单据需要增加以下信息 FPHXZ:发票行性质 HH:行号 ZKHHH:折扣行行号 折扣行上规则型号、单位、数量、单价 不用设值。折扣行的项目名称必须与被折扣行一样。被折扣行记录对应折扣行行号 。
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"GMF_MC" : "购买方名称",
"JSHJ" : 799,
"items" :
[
{
"FPHXZ" : 2,
"XMMC" : "项目名称",
"XMSL" : 1,
"XMJSHJ" : 899,
"SL" : 0.17,
"HH":"1",
"ZKHHH":"2"
},
{
"FPHXZ" : 1,
"XMMC" : "项目名称",
"XMJSHJ" : -100,
"SL" : 0.17,
"HH":"2",
}
]
}]
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 |
# 返回样例
{
"code": "0000",
"msg": "success"
}
# 开蓝票-自动拆分
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/insertWithSplit?appid=XXXXX
TIP
每次请求的数据不能超过10张单据
此接口只能用来开具蓝票
签名载荷(Payload)中必须包含属性requestdatas,它的值为表单参数requestdatas的MD5值,具体使用方式参考样例代码示例
# 请求体参数
# 表单说明
参数 | 描述 | 说明 |
---|---|---|
requestdatas | 开票申请发票信息,包含发票头、发票明细 | |
邮件推送配置(可选) | ||
sms | 短信推送配置(可选) | |
url | 回调服务配置(可选) | |
autoAudit | 是否自动审核(可选) | true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认 如果不传,代表true |
nc6x-ubl | ncc友企联回调参数 |
# 发票头
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPQQLSH | String | 20 | 是 | 发票请求流水号 | ||
FPLX | String | 1 | 否 | 1 | 发票类型 | 1:增值税电子普通发票;3:增值税普通发票;4:增值税专用发票 |
XSF_NSRSBH | String | 20 | 是 | 销售方纳税人识别号 | ||
XSF_MC | String | 100 | 是 | 销售方名称 | 如果为空,获取发票平台配置的销售方名称 | |
XSF_DZDH | String | 100 | 是 | 销售方地址、电话 | 如果为空,获取发票平台配置的销售方地址及电话 | |
XSF_YHZH | String | 100 | 否 | 销售方银行、账号 | 如果为空,获取发票平台配置的销售方银行及账号 | |
GMF_NSRSBH | String | 20 | 否 | 购买方纳税人识别号 | ||
GMF_MC | String | 100 | 是 | 购买方名称 | ||
GMF_DZDH | String | 100 | 否 | 购买方地址、电话 | ||
GMF_YHZH | String | 100 | 否 | 购买方银行、账号 | ||
KPR | String | 8 | 否 | 开票人 | 如果为空,获取发票平台配置的开票人 | |
SKR | String | 8 | 否 | 收款人 | 如果为空,获取发票平台配置的收款人 | |
FHR | String | 8 | 否 | 复核人 | 如果为空,获取发票平台配置的复核人 | |
JSHJ | Double | 15,2 | 是 | 价税合计 | 两位小数。明细价税合计之和必须与总的价税合计一致 | |
HJJE | Double | 15,2 | 否 | 合计金额 | 两位小数。为空时,根据明细项目进行合计。 | |
HJSE | Double | 15,2 | 否 | 合计税额 | 两位小数。为空时,根据明细项目进行合计。 | |
BZ | String | 230 | 否 | 备注 | 长度不能大于230 | |
LYID | String | 100 | 否 | 请求来源唯一标识 | ||
BMB_BBH | String | 20 | 否 | 13.0 | 编码版本号 | 增加商品编码功能后,税局下载的商品编码表版本 |
ORGCODE | String | 50 | 否 | 开票点编码 | 支持一个公司多个开票点场景。如果为空,获取默认的开票点 | |
WXORDERID | String | 否 | 微信订单号 | 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包 | ||
WXAPPID | String | 否 | 微信商户id | 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包 |
# 发票明细
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPHXZ | Integer | 1 | 否 | 0 | 发票行性质 | 0-正常行1-折扣行2-被折扣行;如果是 |
XMMC | String | 90 | 是 | 项目名称 | ||
GGXH | String | 40 | 否 | 规格型号 | ||
DW | String | 20 | 否 | 单位 | ||
XMSL | Double | 15,6 | 否 | 项目数量 | ||
XMDJ | Double | 15,6 | 否 | 项目单价 | 项目单价为空时,根据项目金额反算。不为空时不进行计算 | |
XMJE | Double | 15,2 | 否 | 项目金额 | 项目金额为空,根据项目价税合计反算。不为空时不进行计算 | |
XMJSHJ | Double | 15,2 | 是 | 项目价税合计 | ||
SL | Double | 6,6 | 是 | 税率 | 6 位小数,例 17%为 0.17 | |
SE | Double | 15,2 | 否 | 税额 | 税额为空,根据价税合计反算。不为空时不进行计算。 | |
HH | string | 60 | 否 | 行号 | 有折扣时需要必输 | |
ZKHHH | string | 60 | 否 | 折扣行行号 | 有折扣时需要必输 | |
SPBM | string | 19 | 是 | 商品编码 | ||
ZXBM | string | 20 | 否 | 10 | 商品自行编码 | |
YHZCBS | int | 否 | 0 | 销售优惠标识 | 0:不使用,1:使用 | |
LSLBS | string | 1 | 否 | 零税率标识 | 空:非零利率,0:出口退税,1:免税,2:不征收,3普通零税率 | |
ZZSTSGL | string | 50 | 否 | 优惠政策说明 | ||
KCE | Double | 15,2 | 否 | 扣除额 | 扣除额 |
# 邮件推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 | |
title | String | 100 | 否 | 标题 | ||
content | String | 1000 | 否 | 消息内容 |
# 短信推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 | |
title | String | 100 | 否 | 标题 | ||
content | String | 1000 | 否 | 消息内容 |
# 回调服务配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
url | String | 500 | 是 | 回调url | 开票申请调用方配置回调url,当电子发票开具成功后,调用此服务,POST电子发票相关信息。信息包含:1、pdf版式文件2、发票数据、3、发票下载链接ncc通过友企联方案回调可不传此参数 | |
intf | String | 200 | 否 | ncc回调接口名 | ncc通过友企联方案回调时必传 | |
method | String | 200 | 否 | ncc回调方法名 | ncc通过友企联方案回调时必传 |
# 请求样例
标准表单参数样例
requestdatas=[ {
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"XSF_MC" : "销售方名称",
"XSF_DZDH" : "销售方地址、电话",
"XSF_YHZH" : "销售方银行、账号",
"GMF_NSRSBH" : "购买方纳税人识别号",
"GMF_MC" : "购买方名称",
"GMF_DZDH" : "购买方地址、电话",
"GMF_YHZH" : "购买方银行、账号",
"KPR" : "开票人",
"SKR" : "收款人",
"FHR" : "复核人",
"JSHJ" : "价税合计",
"HJJE" : "合计金额",
"HJSE" : "合计税额",
"BZ" : "备注",
"BMB_BBH" : "10.0",
"items" : [{
"FPHXZ" : "发票行性质",
"XMMC" : "项目名称",
"GGXH" : "规格型号",
"DW" : "单位",
"XMSL" : "项目数量",
"XMDJ" : "项目单价",
"XMJE" : "项目金额",
"XMJSHJ" : "项目价税合计",
"SL" : "税率",
"SE" : "税额",
"HH":"行号",
"SPBM":"商品编码",
"ZXBM":"自行编码"
"KCE":300
}
]
}]
&email=[
{
"fpqqlsh":"12345678901234567890",
"address": "x1@yonyou.com,x2@yonyou.com",
"title": "电子发票",
"content": "订单XXX电子发票。"
},
{
"fpqqlsh":"12345678901234567890",
"address": "x3@yonyou.com",
"title": "电子发票",
"content": "订单XXX电子发票。"
}
]
&sms=[{
"fpqqlsh":"12345678901234567890",
"address": "13511111111,13511111112",
"title": "电子发票",
"content": "订单XXX电子发票。"
}]
&url=[{
"fpqqlsh":"12345678901234567890",
"url": "http://url.com/msg"
}]
&nc6x-ubl=[{
"fpqqlsh":"12345678901234567890",
"method":"queryUserPermAppPksOrCodes",
"intf":"nccloud.pubitf.baseapp.apprbac.IAppAndOrgPermQueryPubService"
}]
&autoAudit=true
最小请求参数样例
TIP
销售方地址及电话、银行及账号、销售方名称可以通过销售方纳税人识别号获取
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"GMF_MC" : "购买方名称",
"JSHJ" : 117,
"items" :
[
{
"XMMC" : "项目名称",
"XMSL" : 1,
"XMJSHJ" : 117,
"SL" : 0.17
}
]
}]
包含折扣行开票样例
TIP
包含折扣行的单据需要增加以下信息 FPHXZ:发票行性质 HH:行号 ZKHHH:折扣行行号 折扣行上规则型号、单位、数量、单价 不用设值。折扣行的项目名称必须与被折扣行一样。被折扣行记录对应折扣行行号 。
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"GMF_MC" : "购买方名称",
"JSHJ" : 799,
"items" :
[
{
"FPHXZ" : 2,
"XMMC" : "项目名称",
"XMSL" : 1,
"XMJSHJ" : 899,
"SL" : 0.17,
"HH":"1",
"ZKHHH":"2"
},
{
"FPHXZ" : 1,
"XMMC" : "项目名称",
"XMJSHJ" : -100,
"SL" : 0.17,
"HH":"2",
}
]
}]
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 | |
datas | JSONArray | 拆分结果数据 |
# 返回样例
{
"code": "0000",
"msg": "success",
"datas": [
{
"bmbBbh": "13.0",
"bz": "BZSHY",
"corpId": "9bf8bdc0-0647-4863-8c04-4c24c3487827",
"fhr": "FHR",
"fpjz": "0",
"fplx": "1",
"fpqqlsh": "shy100000000000006",
"fpzt": "2",
"gmfDzdh": "address",
"gmfMc": "shy00004",
"gmfNsrsbh": "214356654322666",
"gmfYhzh": "213456543233523",
"hjje": 30000,
"hjse": 5100,
"id": 957786,
"jshj": 35100,
"kplx": 0,
"kpr": "KPR",
"lylx": "1",
"orgId": 37,
"orgcode": "HX",
"skr": "SKR",
"splitInvoice": true,
"xsfDzdh": "XSFDZDH",
"xsfMc": "测试3",
"xsfNsrsbh": "111222333456333",
"xsfYhzh": "6217000010005805406",
"zdrq": 1505400195352,
"zsfs": "0",
"items": [
{
"dw": "DW",
"fphxz": 0,
"ggxh": "GGXH",
"hh": "1",
"hid": 957786,
"id": 1330772,
"kce": 0,
"se": 5100,
"sl": 0.17,
"spbm": "1060301010100000000",
"xmdj": 15000,
"xmhsdj": 17550,
"xmje": 30000,
"xmjshj": 35100,
"xmmc": "JYZBSS",
"xmsl": 2,
"yhzcbs": 0,
"zkhhh": "",
"zxbm": "10"
}
]
}
]
}
# 开票状态查询
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/queryInvoiceStatus?appid=XXXXX
# 请求体参数
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 |
# 请求样例
表单参数如下
fpqqlsh=12345678901234567890
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
fpqqlsh | String | 发票请求流水号 | |
code | String | 状态码 | 0000-查询成功;1002-数据不存在;详见状态码说明 |
msg | String | 信息说明 | |
statuscode | String | 开票状态码 | 1-待开票(需要开票员确认开票);2-开票中;3-开票失败;4-开票成功 |
status | String | 开票状态 | 待开票;开票中;开票失败;开票成功 |
errmsg | String | 开票失败明细 | 开票状态为失败时,此处为明细信息 |
data | String | 开票内容 | 开票状态为开票成功时,返回开票信息,为JSONObject,信息格式与回调服务相同。 |
# 返回样例
样例1(开票中)
{
"fpqqlsh": "12345678901234567890",
"code": "0000",
"msg": "查询成功",
"statuscode": "1",
"status": "待开票"
}
样例2(开票失败)
{
"fpqqlsh": "12345678901234567890",
"code": "0000",
"msg": "查询成功",
"statuscode": "3",
"status": "开票失败",
"errmsg":"税控设备错误"
}
# 发票红冲请求
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/red?appid=XXXXX``
TIP
每次请求的数据不能超过10张单据
# 请求体参数
# 表单说明
参数 | 描述 | 说明 |
---|---|---|
requestdatas | 发票红冲参数 | |
邮件推送配置(可选) | ||
sms | 短信推送配置(可选) | |
url | 回调服务配置(可选) | |
autoAudit | 是否自动审核(可选) | true:自动审核,false:不自动审核。 如果不传,代表false |
nc6x-ubl | ncc友企联回调参数 |
# 发票红冲参数
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPQQLSH | String | 20 | 是 | 发票请求流水号 | 注意不是蓝字的发票请求流水号,是本次发票红冲的请求流水号 | |
fpDm | String | 12 | 是 | 蓝字发票代码 | 被红冲的发票代码 | |
fpHm | String | 8 | 是 | 蓝字发票号码 | 被红冲的发票号码 | |
ORGCODE | String | 50 | 否 | 开票点编码 | 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点 | |
KPR | String | 8 | 否 | 开票人 | 如果为空,获取发票平台配置的开票人 | |
SKR | String | 8 | 否 | 收款人 | 如果为空,获取发票平台配置的收款人 | |
FHR | String | 8 | 否 | 复核人 | 如果为空,获取发票平台配置的复核人 | |
hzxxbbh | String | 30 | 否 | 红字信息表编号 | 专票红冲时必传 |
# 邮件推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 短信推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 回调服务配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
url | String | 500 | 是 | 回调url | 开票申请调用方配置回调url,当电子发票开具成功后,调用此服务,POST电子发票相关信息。信息包含:1、pdf版式文件2、发票数据、3、发票下载链接ncc通过友企联方案回调可不传此参数 | |
intf | String | 200 | 否 | ncc回调接口名 | ncc通过友企联方案回调时必传 | |
method | String | 200 | 否 | ncc回调方法名 | ncc通过友企联方案回调时必传 |
# 请求样例
表单参数如下
requestdatas=[
{
"fpHm": "03091804",
"fpDm": "000000000000",
"FPQQLSH": "test1101",
"hzxxbbh":"1101081902045091"
}
]
&email=[
{
"fpqqlsh":"12345678901234567890",
"address": "x1@yonyou.com,x2@yonyou.com"
},
{
"fpqqlsh":"12345678901234567890",
"address": "x3@yonyou.com"
}
]
&sms=[{
"fpqqlsh":"12345678901234567890",
"address": "13511111111,13511111112"
}]
&url=[{
"fpqqlsh":"12345678901234567890",
"url": "http://url.com/msg"
}]
&nc6x-ubl=[{
"fpqqlsh":"12345678901234567890",
"method":"queryUserPermAppPksOrCodes",
"intf":"nccloud.pubitf.baseapp.apprbac.IAppAndOrgPermQueryPubService"
}]
&autoAudit=true
# 返回结果说明
参数 类型 描述 说明 code String 状态码 详见状态码说明 msg String 信息说明
# 返回样例
{
"code": "0000",
"msg": "success"
}
# 开票申请审核通过
此服务与开票蓝票请求服务、发票红冲请求服务配合使用。对于autoAudit=false的开票请求,通过调用此服务驱动开票。autoAudit=true时,不用调用此服务。
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/issue?appid=XXXXX
# 请求体参数
# 表单说明
参数 | 描述 | 说明 |
---|---|---|
requestdatas | 发票开具动作参数 |
# 发票开具动作参数
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPQQLSH | String | 20 | 是 | 发票请求流水号 | ||
XSF_NSRSBH | String | 20 | 是 | 销售方纳税人识别号 | ||
JSHJ | double | 是 | 价税合计 |
# 请求样例
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"XSF_NSRSBH" : "销售方纳税人识别号",
"JSHJ":1234
}]
# 返回结果说明
参数 类型 描述 说明 code String 状态码 详见状态码说明 msg String 信息说明
# 返回样例
{
"code": "0000",
"msg": "success"
}
# 电子发票部分红冲
# URL
POST
Content-Type:application/x-www-form-urlencoded; charset=UTF-8
http://{HOST}:{PORT}/output-tax/api/invoiceApply/part-red?appid=XXXXX
# 请求体参数
# 表单说明
参数 | 描述 | 说明 |
---|---|---|
requestdatas | 开票申请发票信息,包含发票头、发票明细 | |
邮件推送配置(可选) | ||
sms | 短信推送配置(可选) | |
url | 回调服务配置(可选) | |
autoAudit | 是否自动审核(可选) | true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认 如果不传,代表true |
nc6x-ubl | ncc友企联回调参数 |
# 发票头
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPQQLSH | String | 20 | 是 | 发票请求流水号 | 注意不是蓝字的发票请求流水号,是本次发票红冲的请求流水号 | |
fpDm | String | 12 | 是 | 蓝字发票代码 | 被红冲的发票代码 | |
fpHm | String | 8 | 是 | 蓝字发票号码 | 被红冲的发票号码 | |
KPR | String | 8 | 否 | 开票人 | 如果为空,获取发票平台配置的开票人 | |
SKR | String | 8 | 否 | 收款人 | 如果为空,获取发票平台配置的收款人 | |
FHR | String | 8 | 否 | 复核人 | 如果为空,获取发票平台配置的复核人 | |
JSHJ | Double | 15,2 | 是 | 价税合计 | 两位小数。明细价税合计之和必须与总的价税合计一致;价税合计小于0,并且绝对值不能大于蓝票 | |
HJJE | Double | 15,2 | 否 | 合计金额 | 两位小数。为空时,根据明细项目进行合计。合计金额小于0,并且绝对值不能大于蓝票 | |
HJSE | Double | 15,2 | 否 | 合计税额 | 两位小数。为空时,根据明细项目进行合计。 | |
LYID | String | 100 | 否 | 请求来源唯一标识 | ||
BMB_BBH | String | 20 | 否 | 13.0 | 编码版本号 | 增加商品编码功能后,税局下载的商品编码表版本 |
ORGCODE | String | 50 | 否 | 开票点编码 | 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点 | |
hzxxbbh | String | 30 | 否 | 红字信息表编号 | 专票红冲时必传 |
# 发票明细
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
XMMC | String | 90 | 是 | 项目名称 | ||
XMBM | String | 90 | 否 | 项目编码 | 如果项目名称为空,会根据项目编码匹配云平台的商品档案 | |
GGXH | String | 40 | 否 | 规格型号 | ||
DW | String | 20 | 否 | 单位 | ||
XMSL | Double | 15,6 | 否 | 项目数量 | 项目数量必须小于0 | |
XMDJ | Double | 15,6 | 否 | 项目单价 | 项目单价为空时,根据项目金额反算。不为空时不进行计算; | |
XMJE | Double | 15,2 | 否 | 项目金额 | 项目金额为空,根据项目价税合计反算。不为空时不进行计算;金额必须小于0 | |
XMJSHJ | Double | 15,2 | 是 | 项目价税合计 | 价税合计必须小于0 | |
SL | Double | 6,6 | 是 | 税率 | 6 位小数,例 17%为 0.17 | |
SE | Double | 15,2 | 否 | 税额 | 税额为空,根据价税合计反算。不为空时不进行计算。 | |
HH | string | 60 | 否 | 行号 | 有折扣时需要必输 | |
SPBM | string | 19 | 是 | 商品编码 |
# 邮件推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 短信推送配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
address | String | 500 | 是 | 接收地址 | 接收地址,多个地址使用","分隔开 |
# 回调服务配置
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
fpqqlsh | String | 20 | 是 | 发票请求流水号 | 必须和上面的开票请求对应 | |
url | String | 500 | 是 | 回调url | 开票申请调用方配置回调url,当电子发票开具成功后,调用此服务,POST电子发票相关信息。信息包含:1、pdf版式文件2、发票数据、3、发票下载链接ncc通过友企联方案回调可不传此参数 | |
intf | String | 200 | 否 | ncc回调接口名 | ncc通过友企联方案回调时必传 | |
method | String | 200 | 否 | ncc回调方法名 | ncc通过友企联方案回调时必传 |
# 返回结果说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 详见状态码说明 |
msg | String | 信息说明 |
# 返回样例
{
"code": "0000",
"msg": "success"
}
# 请求样例
标准表单参数样例
requestdatas=[ {
"FPQQLSH" : "12345678901234567890",
"fpHm": "03091804",
"fpDm": "000000000000",
"KPR" : "开票人",
"SKR" : "收款人",
"FHR" : "复核人",
"JSHJ" : "价税合计",
"HJJE" : "合计金额",
"HJSE" : "合计税额",
"hzxxbbh":"1101081902045091",
"items" : [{
"XMMC" : "项目名称",
"GGXH" : "规格型号",
"DW" : "单位",
"XMSL" : "项目数量",
"XMDJ" : "项目单价",
"XMJE" : "项目金额",
"XMJSHJ" : "项目价税合计",
"SL" : "税率",
"SE" : "税额",
"HH":"行号",
"SPBM":"商品编码"
}
]
}]
&email=[
{
"fpqqlsh":"12345678901234567890",
"address": "x1@yonyou.com,x2@yonyou.com"
},
{
"fpqqlsh":"12345678901234567890",
"address": "x3@yonyou.com"
}
]
&sms=[{
"fpqqlsh":"12345678901234567890",
"address": "13511111111,13511111112"
}]
&url=[{
"fpqqlsh":"12345678901234567890",
"url": "http://url.com/msg"
}]
&nc6x-ubl=[{
"fpqqlsh":"12345678901234567890",
"method":"queryUserPermAppPksOrCodes",
"intf":"nccloud.pubitf.baseapp.apprbac.IAppAndOrgPermQueryPubService"
}]
&autoAudit=true
最小请求参数样例
TIP
销售方地址及电话、银行及账号、销售方名称可以通过销售方纳税人识别号获取
requestdatas=[{
"FPQQLSH" : "12345678901234567890",
"fpHm": "03091804",
"fpDm": "000000000000",
"JSHJ" : -117,
"items" :
[
{
"XMMC" : "项目名称",
"SPBM" : "1010101020000000000",
"XMJSHJ" : -117,
"SL" : 0.17
}
]
}]
# 附录
# NCC友企联回调数据样例
{
"method": "queryUserPermAppPksOrCodes",
"interface": "nccloud.pubitf.baseapp.apprbac.IAppAndOrgPermQueryPubService",
"serviceMethodArgInfo": [
{
"agg": false,
"argType": "java.lang.String",
"isArray": false,
"isPrimitive": false,
"argValue": "{\"data\":\"{\"ewm\":\"01,10,68974368,010011652011,20160906,,12345678901234567890,\",\"fhr\":\"ljm111\",\"fpDm\":\"010011652011\",\"fpHm\":\"68974368\",\"fpMw\":\"<-<>48938<4+<14>735+<2554*8-1-<+15<*026+848686/2/3//0>+*>>>356*<757/47>90+<25<<3575**934<+15<*026+848686--57\",\"fplx\":\"1\",\"fpjz\":\"0\",\"fpqqlsh\":\"12345677901234567897\",\"bmbBbh\":\"10.0\",\"gmfMc\":\"测试开票用户\",\"hjje\":100.00,\"hjse\":17.00,\"jqbh\":\"111111111111\",\"jshj\":117,\"jym\":\"12345678901234567890\",\"kplx\":0,\"kpr\":\"2\",\"kprq\":\"20160906202718\",\"skr\":\"ljm111\",\"corpId\":\"5bdfc3b7-fd88-4947-8a50-2474b97d7eef\",\"orgId\":\"12010101\",\"xsfDzdh\":\"北京市海淀区 110\",\"xsfMc\":\"北京\",\"xsfNsrsbh\":\"111112222233333\",\"xsfYhzh\":\"1 1\",\"yfpDm\":\"\",\"yfpHm\":\"\",\"items\":[{\"fphxz\":0,\"se\":17.00,\"sl\":0.17,\"xmdj\":100.000000,\"xmje\":100.00,\"xmjshj\":117,\"xmmc\":\"项目名称\",\"spbm\":\"3070101000000000000\",\"zxbm\":\"10\",\"yhzcbs\":0,\"xmsl\":1}]}\", \"code\":\"0000\", \"msg\": \"开票成功\", \"fpqqlsh\": \"12345677901234567897\", \"pdf\": \"pdf文件流\",\"shareurl\": \"http://192.168.52.101/invoiceent-web/s/ab799b5413221cc19088a465d62f8c4f\",\"sharecode\": \"E3N2\" }"
}
]
}
# 常规回调数据样例
{
"data": "{\"ewm\":\"01,10,68974368,010011652011,20160906,,12345678901234567890,\",\"zdybz\":\"自定义备注\",\"fhr\":\"ljm111\",\"fpDm\":\"010011652011\",\"fpHm\":\"68974368\",\"fpMw\":\"<-<>48938<4+<14>735+<2554*8-1-<+15<*026+848686/2/3//0>+*>>>356*<757/47>90+<25<<3575**934<+15<*026+848686--57\",\"fplx\":\"1\",\"fpjz\":\"0\",\"fpqqlsh\":\"12345677901234567897\",\"bmbBbh\":\"10.0\",\"gmfMc\":\"测试开票用户\",\"hjje\":100.00,\"hjse\":17.00,\"jqbh\":\"111111111111\",\"jshj\":117,\"jym\":\"12345678901234567890\",\"kplx\":0,\"kpr\":\"2\",\"kprq\":\"20160906202718\",\"skr\":\"ljm111\",\"corpId\":\"5bdfc3b7-fd88-4947-8a50-2474b97d7eef\",\"orgId\":\"12010101\",\"xsfDzdh\":\"北京市海淀区 110\",\"xsfMc\":\"北京\",\"xsfNsrsbh\":\"111112222233333\",\"xsfYhzh\":\"1 1\",\"yfpDm\":\"\",\"yfpHm\":\"\",\"items\":[{\"fphxz\":0,\"se\":17.00,\"sl\":0.17,\"xmdj\":100.000000,\"xmje\":100.00,\"xmjshj\":117,\"xmmc\":\"项目名称\",\"spbm\":\"3070101000000000000\",\"zxbm\":\"10\",\"yhzcbs\":0,\"xmsl\":1}]}",
"code": "0000",
"msg": "开票成功",
"fpqqlsh": "12345677901234567897",
"pdf": "pdf文件流",
"shareurl": "http://192.168.52.101/invoiceent-web/s/ab799b5413221cc19088a465d62f8c4f",
"sharecode": "E3N2"
}
# 回调服务参数
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 开票信息码 | 0000代表开票成功 |
msg | String | 开票失败信息 | |
fpqqlsh | String | 发票请求流水号 | |
String | pdf文件 | 使用Base64解码为二进制流,可以保存为pdf文件 | |
shareurl | String | 发票提取url | |
sharecode | String | 发票提取码 | 与提取url配合使用 |
data | String | 发票详细信息 | 回调服务发票详细信息 |
# 回调服务发票详细信息
- 发票头数据
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
fpqqlsh | String | 发票请求流水号 | |
jqbh | String | 机器编号 | |
fpHm | String | 发票号码 | |
fpDm | String | 发票代码 | |
fpMw | String | 发票密文 | |
jym | String | 校验码 | |
kprq | String | 开票日期 | 格式YYYYMMDDHHMMSS |
gmfDzdh | String | 购买方地址、电话 | |
gmfMc | String | 购买方名称 | |
gmfNsrsbh | String | 购买方纳税人识别号 | |
gmfYhzh | String | 购买方银行、账户 | |
xsfDzdh | String | 销售方地址、电话 | |
xsfMc | String | 销售方名称 | |
xsfNsrsbh | String | 销售纳税人识别号 | |
xsfYhzh | String | 销售方银行、账户 | |
yfpDm | String | 原发票代码 | |
yfpHm | String | 原发票号码 | |
hjje | double | 合计金额 | |
hjse | double | 合计税额 | |
jshj | double | 价税合计 | |
kpr | String | 开票人 | |
fhr | String | 复核人 | |
skr | String | 收款人 | |
bz | String | 备注 | |
zfbz | String | 作废标志 | Y:是;N否 |
bred | String | 是否红冲 | Y:是;N否 |
zdybz | String | 自定义备注 | |
items | Object | 发票明细 | 发票明细数据 |
- 发票明细数据
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
xmmc | String | 项目名称 | |
ggxh | String | 规格型号 | |
dw | String | 单位 | |
xmsl | doube | 项目数量 | |
xmdj | doube | 项目单价 | |
xmje | doube | 项目金额 | |
xmjshj | String | 项目价税合计 | |
sl | String | 税率 | |
se | String | 税额 | |
hh | string | 行号 |
# 回调服务返回结果
回调服务对回调信息处理后,需要返回处理结果信息。 返回参数约定如下:
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 处理结果编码 | 0000:表示回调服务处理成功 |
msg | String | 处理结果明细信息 | 回调处理的明细信息。如果存在错误,此处为错误信息 |
# 样例代码
样例代码演示的是开票服务的调用,请根据实际应用场景参考使用 样例代码获取可以发邮件至wangweir@yonyou.com,注意标明具体编程语言
# 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.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;
import java.util.Map.Entry;
/**
* @date 2018/5/25
* 该样例代码为调用开蓝票接口代码,适用于JDK1.6及更高版本,jdk1.6版本需要对签名方法稍做修改,修改方法在签名方法内已经写明
* 请求参数的注意事项也在参数构建的过程中写明,请详细阅读样例代码。
*/
public class InsertWithArray {
//测试环境有测试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 + "/output-tax/api/invoiceApply/insertWithArray?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 InsertWithArray().callInvoiceApply();
} 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 callInvoiceApply() throws Exception {
// 提供两种构建HttpClient实例的方法,如果使用被注释掉的方法构建实例报证书不被信任的错误,那么请使用未被注释的构建方法
// HttpClient httpClient = HttpClients.custom().build();
HttpClient httpClient = createSSLClientDefault(); //信任所有https证书
HttpPost httpPost = new HttpPost(URL);
// 构造POST表单Map
Map<String, String> paramsMap = buildPostParam();
// 签名
String sign = this.sign(paramsMap);
httpPost.addHeader("sign", sign);
// 转换POST表单参数
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<Entry<String, String>> iterator = paramsMap.entrySet().iterator();
while (iterator.hasNext()) {
Entry<String, String> elem = iterator.next();
list.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
}
if (list.size() > 0) {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
}
// 发送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(Map<String, String> paramsMap) throws Exception {
PrivateKey privateKey = loadPrivateKeyOfCA();
Map<String, Object> claims =
JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
.addJwtId().addIssuedAt().setExpirySeconds(3600).setNotBeforeSeconds(3600).getClaims();
// 需要将表单参数requestdatas的数据进行md5加密,然后放到签名数据的requestdatas中。
// 此签名数据必须存在,否则在验证签名时会不通过。
String value = paramsMap.get("requestdatas");
claims.put("requestdatas", getMD5(value));
// 使用jdk1.6版本时,删除下面代码的中.compressWith(CompressionCodecs.DEFLATE)
String compactJws = Jwts.builder().signWith(SignatureAlgorithm.RS512, privateKey)
.setClaims(claims).compressWith(CompressionCodecs.DEFLATE).compact();
return compactJws;
}
// /**
// * 当在linux环境下运行代码,签名方法报空指针异常的时候,采用该签名方法可以避免这个问题
// * 使用该方法需要添加新的maven依赖,如下:
// * <dependency>
// * <groupId>com.auth0</groupId>
// * <artifactId>java-jwt</artifactId>
// * <version>3.3.0</version>
// * </dependency>
// * @param paramsMap
// * @return
// * @throws Exception
// */
// private String sign(Map<String, String> paramsMap) throws Exception {
//
// RSAPrivateKey privateKey = loadPrivateKeyOfCA();
// Map<String, Object> claims =
// JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
// .addJwtId().addIssuedAt().setExpirySeconds(300).setNotBeforeSeconds(300).getClaims();
// String token;
// try {
// Algorithm algorithm = Algorithm.RSA512(null, privateKey);
// token = JWT.create()
// .withClaim("requestdatas",getMD5(paramsMap.get("requestdatas")))
// .withHeader(claims)
// .sign(algorithm);
// } catch (JWTCreationException exception){
// //Invalid Signing configuration / Couldn't convert Claims.
// return null;
// }
//
// return token;
// }
/**
* 计算参数MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private static 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 RSAPrivateKey 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();
RSAPrivateKey caprk = (RSAPrivateKey) ks.getKey(alias, PASSWORD.toCharArray());
return caprk;
}
/**
* post表单数据
*
* @return
*/
private Map<String, String> buildPostParam() {
Map<String, String> paramsMap = new HashMap<String, String>();
paramsMap.put("requestdatas", this.buildRequestDatas());
paramsMap.put("email", this.buildEmailConfigs());
paramsMap.put("sms", this.buildSmsConfigs());
paramsMap.put("url", this.buildUrlConfigs());
paramsMap.put("autoAudit", "false");
return paramsMap;
}
/**
* url回掉配置
*
* @return
*/
private String buildUrlConfigs() {
List<Object> datas = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("fpqqlsh", buildFpqqlsh());
data.put("url", "http://117.12.12.3:7787/EinvoiceRESTService/CallBackEInvoices/");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造短信发送信息
*
* @return
*/
private String buildSmsConfigs() {
List<Object> datas = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("fpqqlsh", buildFpqqlsh());
data.put("address", "123123123123");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造email发送信息
*
* @return
*/
private String buildEmailConfigs() {
List<Object> datas = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("fpqqlsh", buildFpqqlsh());
data.put("address", "123@163.com");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造requestdatas
*
* @return
*/
private String buildRequestDatas() {
List<Object> datas = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("FPQQLSH", buildFpqqlsh());
//测试环境请一定要使用测试纳税人识别号
data.put("XSF_NSRSBH", "201609140000001");
data.put("GMF_MC", "购买方名称");
data.put("GMF_DZDH", "购买方地址电话");
//组织编码,测试环境请一定使用测试环境的组织编码
data.put("ORGCODE", "20160914001");
data.put("JSHJ", 1395.00);
data.put("items", buildItems());
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造request发票明细
*
* @return
*/
private List<Object> buildItems() {
List<Object> items = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("XMJSHJ", "1395.00");
data.put("XMMC", "住宅物业管理费1");
//税率16%需要写成0.16的格式
data.put("SL", 0.16);
//SPBM字段为商品税收分类编码,不同的商品会有不同的编码,不对应的话会影响报税,需要咨询下公司财务
data.put("SPBM", "3040502029902000000");
items.add(data);
return items;
}
/**
* 获取发票请求流水号
* 长度不超过20位,长度在1到20位的字母和数字组合,不可以重复的,不要包含window系统文件名限制的特殊字符
*
* @return 发票请求流水号
*/
private String buildFpqqlsh() {
return "164291i05h2080000sKs";
}
}
# 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;
}
}
# Java(适用于JDK1.5)
# Maven配置文件依赖
依赖配置如下,除此之外还需要单独引入JWT的java-jwt-2.2.1-SNAPSHOT.jar包,请联系接口调试人员获取jar包。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.3</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.52</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.0.1</version>
</dependency>
# API调用代码
import com.auth0.jwt.Algorithm;
import com.auth0.jwt.JWTSigner;
import com.google.gson.GsonBuilder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.*;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey;
import java.util.*;
/**
* @author xingguoqing
* 开具蓝票接口,本样例适合JDK1.5,除了需要按照pom文件引入相关jar外,还需要单独引入jwt包(联系接口调试人员)
* @date 2018/5/9 下午1:37
*/
public class InsertWithArray {
//测试环境有测试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 + "/output-tax/api/invoiceApply/insertWithArray?appid=" + APPID;
//pro22.pfx为测试环境通讯证书,正式环境需要替换成正式的
private static String KEYPATH = "src/main/resources/certificate/pro22.pfx";
//证书密码
private static String PASSWORD = "password";
private static class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
private static class TrustAnyHostnameVerifier implements X509HostnameVerifier {
public boolean verify(String hostname, SSLSession session){
return true;
}
public void verify(String s, SSLSocket sslSocket) throws IOException { }
public void verify(String s, X509Certificate x509Certificate) throws SSLException {}
public void verify(String s, String[] strings, String[] strings1) throws SSLException {}
}
private static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, new TrustManager[]{new InsertWithArray.TrustAnyTrustManager()}, new java.security.SecureRandom());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext,new InsertWithArray.TrustAnyHostnameVerifier());
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (Exception e) {
e.printStackTrace();
}
return HttpClients.createDefault();
}
public static void main(String[] args) {
try {
new InsertWithArray().callInvoiceApply();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 调用接口开票
* @throws Exception
*/
public void callInvoiceApply() throws Exception {
HttpClient httpClient = createSSLClientDefault(); //忽略https证书
HttpPost httpPost = new HttpPost(URL);
// 构造POST表单Map
Map<String, String> paramsMap = buildPostParam();
System.out.println(paramsMap);
// 签名
String sign = this.sign(paramsMap);
System.out.println(sign);
httpPost.addHeader("sign", sign);
// 转换POST表单参数
List<NameValuePair> list = new ArrayList<NameValuePair>();
Iterator<Map.Entry<String, String>> iterator = paramsMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> elem = iterator.next();
list.add(new BasicNameValuePair(elem.getKey(), elem.getValue()));
}
if (list.size() > 0) {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
}
// 发送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(Map<String, String> paramsMap) throws Exception {
// 读取CA证书
PrivateKey privateKey = loadPrivateKeyOfCA();
Map<String, Object> claims =
JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
.addJwtId().addIssuedAt().setExpirySeconds(3600).setNotBeforeSeconds(3600).getClaims();
// 需要将表单参数requestdatas的数据进行md5加密,然后放到签名数据的requestdatas中。
// 此签名数据必须存在,否则在验证签名时会不通过。
String value = paramsMap.get("requestdatas").toString();
claims.put("requestdatas", getMD5(value));
JWTSigner signer = new JWTSigner(privateKey);
String compactJws = signer.sign(claims, new JWTSigner.Options().setExpirySeconds(300)
.setNotValidBeforeLeeway(5).setIssuedAt(true).setJwtId(true).setAlgorithm(Algorithm.RS256));
return compactJws;
}
/**
* 计算MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private static 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 RSAPrivateKey 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();
RSAPrivateKey caprk = (RSAPrivateKey) ks.getKey(alias, PASSWORD.toCharArray());
return caprk;
}
/**
* post表单数据
*
* @return
*/
private Map<String, String> buildPostParam() {
Map<String, String> paramsMap = new HashMap<String, String>();
paramsMap.put("requestdatas", this.buildRequestDatas());
// paramsMap.put("email", this.buildEmailConfigs());
// paramsMap.put("sms", this.buildSmsConfigs());
// paramsMap.put("url", this.buildUrlConfigs());
// paramsMap.put("autoAudit", "false");
return paramsMap;
}
/**
* url回掉配置
*
* @return
*/
private String buildUrlConfigs() {
List<Object> datas = new ArrayList();
Map<String, Object> data = new HashMap();
data.put("fpqqlsh", buildFpqqlsh());
data.put("url", "http://117.12.12.3:7787/EinvoiceRESTService/CallBackEInvoices/");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造短信发送信息
*
* @return
*/
private String buildSmsConfigs() {
List<Object> datas = new ArrayList();
Map<String, Object> data = new HashMap();
data.put("fpqqlsh", buildFpqqlsh());
data.put("address", "15611500957");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造email发送信息
*
* @return
*/
private String buildEmailConfigs() {
List<Object> datas = new ArrayList();
Map<String, Object> data = new HashMap();
data.put("fpqqlsh", buildFpqqlsh());
data.put("address", "xinggq7@yonyou.com");
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造requestdatas
*
* @return
*/
private String buildRequestDatas() {
List<Object> datas = new ArrayList();
Map<String, Object> data = new HashMap();
data.put("FPQQLSH", buildFpqqlsh());
//测试环境请一定要使用测试纳税人识别号
data.put("XSF_NSRSBH", "201609140000001");
//组织编码,测试环境请一定使用测试环境的组织编码
data.put("ORGCODE", "20160914001");
data.put("GMF_MC", "123");
data.put("JSHJ", 0);
data.put("items", buildItems());
datas.add(data);
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(datas);
}
/**
* 构造request发票明细
*
* @return
*/
private List<Object> buildItems() {
List<Object> items = new ArrayList();
Map<String, Object> data = new HashMap();
data.put("XMJSHJ", 10951200);
data.put("XMMC", "住宅物业管理费");
//税率17%需要写成0.17的格式
data.put("SL", 0.17);
data.put("XMSL", 146016);
//SPBM字段为商品税收分类编码,不同的商品会有不同的编码,不对应的话会影响报税,需要咨询下公司财务
data.put("SPBM", "3040502029902000000");
items.add(data);
return items;
}
/**
* 获取发票请求流水号
*长度不超过20位,长度在1到20位的字母和数字组合,不可以重复的,不要包含window系统文件名限制的特殊字符
* @return 发票请求流水号
*/
private String buildFpqqlsh() {
return "1k4f91i05h2080000sKs";
}
}
# 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;
}
}
# C#
# 添加引用
使用包管理开发工具NuGet添加依赖。NuGet具体使用方法请自行Google。 在程序包管理器控制台输入下列命令安装依赖 1、Install-Package Newtonsoft.Json 2、Install-Package jose-jwt Newtonsoft.Json用来进行json转换 jose-jwt用来进行jwt签名
# API调用代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using Jose;
using Newtonsoft.Json;
namespace Invoicing
{
internal class Program
{
private static void Main(string[] args)
{
//构造url
var baseUrl = "https://127.0.0.1:8080";
var url = baseUrl + "/output-tax/api/invoiceApply/insertWithArray?appid=123213";
var req = (HttpWebRequest) WebRequest.Create(url);
//请求体参数_requestdatas(发票头+发票明细)
var requestdatas = BuildRequestDatas();
var paraEnvoice = "requestdatas=" + Uri.EscapeDataString(requestdatas);
//json数组格式,可以多张发票,流水号关联
//请求体参数_email(邮件推送配置)
var paraEmail = "email=" + Uri.EscapeDataString(BuildEmailData());
//请求体参数_sms(短信推送配置)
var paraSms = "sms=" + BuildMobileData();
//json数组格式,可以多手机号,以流水号关联
//请求体参数_url(回调服务配置)
var paraUrl = "url=" + HttpUtility.UrlEncode(BuildUrlData()); //json数组格式,可以多url,以流水号关联
//拼接参数字符串
var param = paraEnvoice + '&' + paraEmail + '&' + paraSms + '&' + paraUrl + "&autoAudit=false";
//设置消息头
// var bs = Encoding.ASCII.GetBytes(param);
var bs = Encoding.UTF8.GetBytes(param);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded;charset=" + Encoding.UTF8.WebName;
req.Headers.Add("sign", Sign(requestdatas)); //放入签名信息在消息头
req.ContentLength = bs.Length;
Console.WriteLine("请求头信息:");
Console.WriteLine(req.Headers.ToString());
Console.WriteLine("---------------------------");
Console.WriteLine("请求体信息:");
Console.WriteLine(param);
Console.WriteLine("---------------------------");
//发送请求
using (var reqStream = req.GetRequestStream())
{
reqStream.Write(bs, 0, bs.Length);
reqStream.Close();
}
//获取请求
using (var response = (HttpWebResponse) req.GetResponse())
{
var responseStream = response.GetResponseStream();
if (responseStream == null)
{
return;
}
using (var reader = new StreamReader(responseStream, Encoding.UTF8))
{
var responseData = reader.ReadToEnd();
Console.WriteLine("返回信息:");
Console.WriteLine(responseData);
Console.WriteLine("---------------------------");
}
}
}
/// <summary>
/// 数据签名
/// </summary>
/// <param name="requestdatas"></param>
/// <returns></returns>
private static string Sign(string requestdatas)
{
var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
//5分钟内有效
var exp = ts.TotalMilliseconds + 360000;
var payload = new Dictionary<string, object>
{
{"sub", "tester"},
{"exp", exp},
{"requestdatas", GetMd5(requestdatas)}
};
var privateKey =
new X509Certificate2("D:/CA/keystore/pro22.pfx", "password",
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet).PrivateKey as
RSACryptoServiceProvider;
var token = JWT.Encode(payload, privateKey, JwsAlgorithm.PS256);
return token;
}
/// <summary>
/// 获取md5值
/// </summary>
/// <param name="requestdatas"></param>
/// <returns></returns>
private static string GetMd5(string requestdatas)
{
using (var md5Hash = MD5.Create())
{
var hash = GetMd5Hash(md5Hash, requestdatas);
return hash;
}
}
private static string GetMd5Hash(MD5 md5Hash, string input)
{
// Convert the input string to a byte array and compute the hash.
var data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (var i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
/// <summary>
/// 构造邮件交付配置信息
/// </summary>
/// <returns></returns>
private static string BuildEmailData()
{
var emailDatas = new List<Dictionary<string, object>>();
var oneEmail = new Dictionary<string, object>
{
{"fpqqlsh", "1234567890"},
{"address", "xxx@yonyou.com"}
};
emailDatas.Add(oneEmail);
return JsonConvert.SerializeObject(emailDatas);
}
/// <summary>
/// 构建短信交付配置
/// </summary>
/// <returns></returns>
private static string BuildMobileData()
{
var mobileDatas = new List<Dictionary<string, object>>();
var oneMobile = new Dictionary<string, object>
{
{"fpqqlsh", "1234567890"},
{"address", "13111111111"}
};
mobileDatas.Add(oneMobile);
return JsonConvert.SerializeObject(mobileDatas);
}
/// <summary>
/// 构建url回调配置
/// </summary>
/// <returns></returns>
private static string BuildUrlData()
{
var urlDatas = new List<Dictionary<string, object>>();
var oneUrl = new Dictionary<string, object>
{
{"fpqqlsh", "1234567890"},
{"url", "http://127.0.0.1:8080/callback"}
};
urlDatas.Add(oneUrl);
return JsonConvert.SerializeObject(urlDatas);
}
/// <summary>
/// 开票请求数据
/// </summary>
/// <returns></returns>
private static string BuildRequestDatas()
{
var requestDatas = new List<Dictionary<string, object>>();
//构造发票头
var oneInvoice = new Dictionary<string, object>
{
{"FPQQLSH", "12345678902222"},
{"XSF_NSRSBH", "5001020100060171"},
{"ORGCODE", "20160914001"},
{"GMF_MC", "购买方名称"},
{"JSHJ", 117}
};
requestDatas.Add(oneInvoice);
var items = new List<Dictionary<string, object>>();
oneInvoice.Add("items", items);
//构造一个发票明细
var oneItem = new Dictionary<string, object>
{
{"XMMC", "项目名称"},
{"SPBM", "1070102039900000000"},
{"XMJSHJ", 117},
{"SL", 0.17}
};
items.Add(oneItem);
return JsonConvert.SerializeObject(requestDatas);
}
}
}
# PHP
# 添加引用
引入JWT.php
<?php
namespace Firebase\JWT;
use \DomainException;
use \InvalidArgumentException;
use \UnexpectedValueException;
use \DateTime;
/**
* JSON Web Token implementation, based on this spec:
* https://tools.ietf.org/html/rfc7519
*
* PHP version 5
*
* @category Authentication
* @package Authentication_JWT
* @author Neuman Vong <neuman@twilio.com>
* @author Anant Narayanan <anant@php.net>
* @license http://opensource.org/licenses/BSD-3-Clause 3-clause BSD
* @link https://github.com/firebase/php-jwt
*/
class JWT
{
/**
* When checking nbf, iat or expiration times,
* we want to provide some extra leeway time to
* account for clock skew.
*/
public static $leeway = 0;
/**
* Allow the current timestamp to be specified.
* Useful for fixing a value within unit testing.
*
* Will default to PHP time() value if null.
*/
public static $timestamp = null;
public static $supported_algs = array(
'HS256' => array('hash_hmac', 'SHA256'),
'HS512' => array('hash_hmac', 'SHA512'),
'HS384' => array('hash_hmac', 'SHA384'),
'RS256' => array('openssl', 'SHA256'),
'RS384' => array('openssl', 'SHA384'),
'RS512' => array('openssl', 'SHA512'),
);
/**
* Decodes a JWT string into a PHP object.
*
* @param string $jwt The JWT
* @param string|array $key The key, or map of keys.
* If the algorithm used is asymmetric, this is the public key
* @param array $allowed_algs List of supported verification algorithms
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
*
* @return object The JWT's payload as a PHP object
*
* @throws UnexpectedValueException Provided JWT was invalid
* @throws SignatureInvalidException Provided JWT was invalid because the signature verification failed
* @throws BeforeValidException Provided JWT is trying to be used before it's eligible as defined by 'nbf'
* @throws BeforeValidException Provided JWT is trying to be used before it's been created as defined by 'iat'
* @throws ExpiredException Provided JWT has since expired, as defined by the 'exp' claim
*
* @uses jsonDecode
* @uses urlsafeB64Decode
*/
public static function decode($jwt, $key, array $allowed_algs = array())
{
$timestamp = is_null(static::$timestamp) ? time() : static::$timestamp;
if (empty($key)) {
throw new InvalidArgumentException('Key may not be empty');
}
$tks = explode('.', $jwt);
if (count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments');
}
list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = static::jsonDecode(static::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid header encoding');
}
if (null === $payload = static::jsonDecode(static::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid claims encoding');
}
if (false === ($sig = static::urlsafeB64Decode($cryptob64))) {
throw new UnexpectedValueException('Invalid signature encoding');
}
if (empty($header->alg)) {
throw new UnexpectedValueException('Empty algorithm');
}
if (empty(static::$supported_algs[$header->alg])) {
throw new UnexpectedValueException('Algorithm not supported');
}
if (!in_array($header->alg, $allowed_algs)) {
throw new UnexpectedValueException('Algorithm not allowed');
}
if (is_array($key) || $key instanceof \ArrayAccess) {
if (isset($header->kid)) {
if (!isset($key[$header->kid])) {
throw new UnexpectedValueException('"kid" invalid, unable to lookup correct key');
}
$key = $key[$header->kid];
} else {
throw new UnexpectedValueException('"kid" empty, unable to lookup correct key');
}
}
// Check the signature
if (!static::verify("$headb64.$bodyb64", $sig, $key, $header->alg)) {
throw new SignatureInvalidException('Signature verification failed');
}
// Check if the nbf if it is defined. This is the time that the
// token can actually be used. If it's not yet that time, abort.
if (isset($payload->nbf) && $payload->nbf > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->nbf)
);
}
// Check that this token has been created before 'now'. This prevents
// using tokens that have been created for later use (and haven't
// correctly used the nbf claim).
if (isset($payload->iat) && $payload->iat > ($timestamp + static::$leeway)) {
throw new BeforeValidException(
'Cannot handle token prior to ' . date(DateTime::ISO8601, $payload->iat)
);
}
// Check if this token has expired.
if (isset($payload->exp) && ($timestamp - static::$leeway) >= $payload->exp) {
throw new ExpiredException('Expired token');
}
return $payload;
}
/**
* Converts and signs a PHP object or array into a JWT string.
*
* @param object|array $payload PHP object or array
* @param string $key The secret key.
* If the algorithm used is asymmetric, this is the private key
* @param string $alg The signing algorithm.
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
* @param mixed $keyId
* @param array $head An array with header elements to attach
*
* @return string A signed JWT
*
* @uses jsonEncode
* @uses urlsafeB64Encode
*/
public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $head = null)
{
$header = array('typ' => 'JWT', 'alg' => $alg);
if ($keyId !== null) {
$header['kid'] = $keyId;
}
if ( isset($head) && is_array($head) ) {
$header = array_merge($head, $header);
}
$segments = array();
$segments[] = static::urlsafeB64Encode(static::jsonEncode($header));
$segments[] = static::urlsafeB64Encode(static::jsonEncode($payload));
$signing_input = implode('.', $segments);
$signature = static::sign($signing_input, $key, $alg);
$segments[] = static::urlsafeB64Encode($signature);
return implode('.', $segments);
}
/**
* Sign a string with a given key and algorithm.
*
* @param string $msg The message to sign
* @param string|resource $key The secret key
* @param string $alg The signing algorithm.
* Supported algorithms are 'HS256', 'HS384', 'HS512' and 'RS256'
*
* @return string An encrypted message
*
* @throws DomainException Unsupported algorithm was specified
*/
public static function sign($msg, $key, $alg = 'HS256')
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch($function) {
case 'hash_hmac':
return hash_hmac($algorithm, $msg, $key, true);
case 'openssl':
$signature = '';
$success = openssl_sign($msg, $signature, $key, $algorithm);
if (!$success) {
throw new DomainException("OpenSSL unable to sign data");
} else {
return $signature;
}
}
}
/**
* Verify a signature with the message, key and method. Not all methods
* are symmetric, so we must have a separate verify and sign method.
*
* @param string $msg The original message (header and body)
* @param string $signature The original signature
* @param string|resource $key For HS*, a string key works. for RS*, must be a resource of an openssl public key
* @param string $alg The algorithm
*
* @return bool
*
* @throws DomainException Invalid Algorithm or OpenSSL failure
*/
private static function verify($msg, $signature, $key, $alg)
{
if (empty(static::$supported_algs[$alg])) {
throw new DomainException('Algorithm not supported');
}
list($function, $algorithm) = static::$supported_algs[$alg];
switch($function) {
case 'openssl':
$success = openssl_verify($msg, $signature, $key, $algorithm);
if ($success === 1) {
return true;
} elseif ($success === 0) {
return false;
}
// returns 1 on success, 0 on failure, -1 on error.
throw new DomainException(
'OpenSSL error: ' . openssl_error_string()
);
case 'hash_hmac':
default:
$hash = hash_hmac($algorithm, $msg, $key, true);
if (function_exists('hash_equals')) {
return hash_equals($signature, $hash);
}
$len = min(static::safeStrlen($signature), static::safeStrlen($hash));
$status = 0;
for ($i = 0; $i < $len; $i++) {
$status |= (ord($signature[$i]) ^ ord($hash[$i]));
}
$status |= (static::safeStrlen($signature) ^ static::safeStrlen($hash));
return ($status === 0);
}
}
/**
* Decode a JSON string into a PHP object.
*
* @param string $input JSON string
*
* @return object Object representation of JSON string
*
* @throws DomainException Provided string was invalid JSON
*/
public static function jsonDecode($input)
{
if (version_compare(PHP_VERSION, '5.4.0', '>=') && !(defined('JSON_C_VERSION') && PHP_INT_SIZE > 4)) {
/** In PHP >=5.4.0, json_decode() accepts an options parameter, that allows you
* to specify that large ints (like Steam Transaction IDs) should be treated as
* strings, rather than the PHP default behaviour of converting them to floats.
*/
$obj = json_decode($input, false, 512, JSON_BIGINT_AS_STRING);
} else {
/** Not all servers will support that, however, so for older versions we must
* manually detect large ints in the JSON string and quote them (thus converting
*them to strings) before decoding, hence the preg_replace() call.
*/
$max_int_length = strlen((string) PHP_INT_MAX) - 1;
$json_without_bigints = preg_replace('/:\s*(-?\d{'.$max_int_length.',})/', ': "$1"', $input);
$obj = json_decode($json_without_bigints);
}
if (function_exists('json_last_error') && $errno = json_last_error()) {
static::handleJsonError($errno);
} elseif ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input');
}
return $obj;
}
/**
* Encode a PHP object into a JSON string.
*
* @param object|array $input A PHP object or array
*
* @return string JSON representation of the PHP object or array
*
* @throws DomainException Provided object could not be encoded to valid JSON
*/
public static function jsonEncode($input)
{
$json = json_encode($input);
if (function_exists('json_last_error') && $errno = json_last_error()) {
static::handleJsonError($errno);
} elseif ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input');
}
return $json;
}
/**
* Decode a string with URL-safe Base64.
*
* @param string $input A Base64 encoded string
*
* @return string A decoded string
*/
public static function urlsafeB64Decode($input)
{
$remainder = strlen($input) % 4;
if ($remainder) {
$padlen = 4 - $remainder;
$input .= str_repeat('=', $padlen);
}
return base64_decode(strtr($input, '-_', '+/'));
}
/**
* Encode a string with URL-safe Base64.
*
* @param string $input The string you want encoded
*
* @return string The base64 encode of what you passed in
*/
public static function urlsafeB64Encode($input)
{
return str_replace('=', '', strtr(base64_encode($input), '+/', '-_'));
}
/**
* Helper method to create a JSON error.
*
* @param int $errno An error number from json_last_error()
*
* @return void
*/
private static function handleJsonError($errno)
{
$messages = array(
JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON',
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
JSON_ERROR_UTF8 => 'Malformed UTF-8 characters' //PHP >= 5.3.3
);
throw new DomainException(
isset($messages[$errno])
? $messages[$errno]
: 'Unknown JSON error: ' . $errno
);
}
/**
* Get the number of bytes in cryptographic strings.
*
* @param string
*
* @return int
*/
private static function safeStrlen($str)
{
if (function_exists('mb_strlen')) {
return mb_strlen($str, '8bit');
}
return strlen($str);
}
}
# API调用代码
<?php
require 'vendor/autoload.php';
use \Firebase\JWT\JWT;
include('JWT.php');
class InvoiceDemo {
private static $appid = 'commontesterCA';
private static $baseUrl = 'https://yesfp.yonyoucloud.com/output-tax/api/invoiceApply/';
private static $keyfile='D:/web/irm-cams.web/cn/pro22.pfx';
private static $blueApi = 'insertWithArray';
//开蓝票接口
public function blueInvoice() {
$fpqqlsh = $this->buildFpqqlsh();
$requestdatas = array(
array(
"FPQQLSH" => $fpqqlsh,
"XSF_NSRSBH" => "201609140000001",
"ORGCODE" => "20160914001",
"GMF_MC" => "天津国联鸿泰科技有限公司",
"GMF_DZDH" => "天津市河北区王串场街王串场四号路4号增19号 86-022-84847456",
"GMF_YHZH" => "中国建设银行股份有限公司天津河北支行 12050166080000000517",
"ZDYBZ" => "这是放射所报名费xx单号的开票",
"JSHJ" => 780.00,
"items" => array(
array(
"XMMC" => "技术服务费",
"SPBM" => "3040101",
"XMJSHJ" => 117,
"SL" => 0.06,
)
)
)
);
$url = array(
array(
"fpqqlsh"=>$fpqqlsh,
"url" => "http://bjxr246.cname.zaojiaojia.net/callback.php?callbackUrl"
)
);
$email=array(
array(
"fpqqlsh"=>$fpqqlsh,
"address"=>"315389520@qq.com"
)
);
$params = array(
'requestdatas'=>json_encode($requestdatas),
'email'=>json_encode($email),
'url'=>json_encode($url),
"autoAudit" => 'true'
);
return $this->exec(self::$blueApi, $params);
}
//获取发票请求流水号(唯一)
private function buildFpqqlsh(){
return "2018052615510000004";
}
protected function exec($api, array $params) {
$api = self::$baseUrl . $api . "?appid=" . self::$appid;
$options = array(
'header'=>array(
'sign'=>$this->sign($params),
)
);
return self::post($api, $params, $options);
}
//jwt签名
private function sign(array $params){
$ts = time();
$signParams = array(
'sub'=>'tester',
'iss'=>'einvoice',
'aud'=>'einvoice',
'jti'=>$ts,
'iat'=>$ts,
'exp'=>$ts+300,
'nbf'=>$ts-300
);
// 需要将表单参数requestdatas的数据进行md5加密,然后放到签名数据的requestdatas中。
// 此签名数据必须存在,否则在验证签名时会不通过。
$requestdatas=$params['requestdatas'];
if(!empty($requestdatas)) {
$signParams['requestdatas'] = md5($requestdatas);
}
//读取CA证书与PEM格式证书需要根据实际证书使用情况而定,目前这两种都支持
$privateKey = $this->loadPrivateKeyOfCA(self::$keyfile);
// $privateKey = $this->loadPrivateKeyOfPem(self::$keyfile);
$sign = JWT::encode($signParams, $privateKey, 'RS256');
return $sign;
}
//读取PEM编码格式
private function loadPrivateKeyOfPem($file) {
if(!file_exists($file)) {
throw new \Exception("Error: Key file $file is not exists.");
}
if(!$key = file_get_contents($file)) {
throw new \Exception("Error: Key file $file is empty.");
}
return $key;
}
//读取证书私钥
private function loadPrivateKeyOfCA($file) {
if(!file_exists($file)) {
throw new \Exception("Error: Cert file $file is not exists.");
}
if (!$cert_store = file_get_contents($file)) {
throw new \Exception("Error: Unable to read the cert file $file .");
}
if (openssl_pkcs12_read($cert_store, $cert_info, "password")) {
return $cert_info['pkey'];
} else {
throw new \Exception("Error: Unable to read the cert store from $file .");
}
}
private static function post($url, $params, array $options=null) {
$ch = curl_init();
self::setOption($ch, $options);
curl_setopt($ch, CURLOPT_URL,$url);
curl_setopt($ch, CURLOPT_POST, count($params));
$params = http_build_query($params);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$content = curl_exec($ch);
$errorCode = curl_errno($ch);
curl_close($ch);
return array($errorCode, $content);
}
private static function setOption($ch, array $options=null) {
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
if($options === null) {
$options = array();
}
if(isset($options["cookie"]) && is_array($options["cookie"])) {
$cookieArr = array();
foreach($options["cookie"] as $key=>$value) {
$cookieArr[] = "$key=$value";
}
$cookie = implode("; ", $cookieArr);
curl_setopt($ch, CURLOPT_COOKIE, $cookie);
}
$timeout = 30;
if(isset($options["timeout"])) {
$timeout = $options["timeout"];
}
if(isset($options["ua"])) {
curl_setopt($ch, CURLOPT_USERAGENT, $options["ua"]);
}
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
if(isset($options['header'])) {
curl_setopt($ch, CURLOPT_HEADER, true);
$header = array();
foreach($options['header'] as $k=>$v) {
$header[] = $k.": ".$v;
}
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
}
}
}
$invoiceDemo=new InvoiceDemo();
$result=$invoiceDemo->blueInvoice();
var_dump($result);
?>