# 开票申请

# 对接必读

开票相关接口关于字段为空不返回情况的说明,对接前务必查看对接必读

# 开蓝票

# 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值,具体使用方式参考样例代码示例

表达提交前,需使用URLEncoder.encode(...)方法进行编码

# 请求体参数

# 表单说明

参数 描述 说明
requestdatas 开票申请发票信息,包含发票头发票明细
email 邮件推送配置(可选)
sms 短信推送配置(可选)
url 回调服务配置(可选)
delurl 退回业务系统回调服务配置(可选)
autoAudit 是否自动审核(可选) true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认如果不传,代表true
nc6x-ubl ncc友企联回调参数

# 发票头

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号
FPLX String 1 1 发票类型 1:增值税电子普通发票;
2:增值税电子专用发票;
3:增值税普通发票;
4:增值税专用发票 、增值税专用发票(机动车);
5:机动车销售统一发票;
8:增值税电子普通发票(成品油);
10:成品油普通发票;
11:成品油专用发票;
15:二手车销售统一发票;
31:数电专用发票;
32:数电普通发票;
33:数电纸质发票(增值税专用发票);
34:数电纸质发票(普通发票);
XSF_NSRSBH String 20 销售方纳税人识别号
XSF_MC String 100 销售方名称 如果为空,获取发票平台配置的销售方名称
XSF_DZDH String 100 销售方地址、电话 如果为空,获取发票平台配置的销售方地址及电话
XSF_DZ String 300 销售方地址 如果为空,获取发票平台配置的销售方地址 数电专用字段
XSF_DH String 60 销售方电话 如果为空,获取发票平台配置的销售方电话 数电专用字段
XSF_YHZH String 100 销售方银行、账号 如果为空,获取发票平台配置的销售方银行及账号
XSF_YH String 120 销售方银行 如果为空,获取发票平台配置的销售方银行 数电专用字段
XSF_ZH String 100 销售方账号 如果为空,获取发票平台配置的销售方账号 数电专用字段
GMF_NSRSBH String 20 购买方纳税人识别号 发票类型31 数电专用发票(必填)
GMF_MC String 100 购买方名称
GMF_DZDH String 100 购买方地址、电话 数电票此字段非必填
GMF_YHZH String 100 购买方银行、账号 数电票此字段非必填
GMF_DZ String 300 购买方地址 数电专用字段
GMF_DH String 60 购买方电话 数电专用字段
GMF_YH String 120 购买方银行 数电专用字段
GMF_ZH String 100 购买方账号 数电专用字段
ALLELCUSERNAME String 300 数电发票用户名 如果发票类型是数电发票用户名必填 ,并且是国密4密文
ALLELCPASSWORD String 300 数电发票密码 如果发票类型是数电发票密码必填 ,并且是国密4密文
KPR String 8 开票人 如果为空,获取发票平台配置的开票人
SKR String 8 收款人 如果为空,获取发票平台配置的收款人
FHR String 8 复核人 如果为空,获取发票平台配置的复核人
SLSM String 1 税率说明 1:当小规模纳税人开具3%税率时需要填写税率说明; 2:前期已开具发票,发生销售折让、中止或者退回等情形需要开具红字发票,或者开票有误需要重新开具; 3:因为实际经营业务需要,放弃享受减按1%征收率征收增值税政策。
JSHJ BigDecimal 15,2 价税合计 两位小数。明细价税合计之和必须与总的价税合计一致
HJJE BigDecimal 15,2 合计金额 两位小数。为空时,根据明细项目进行合计。
HJSE BigDecimal 15,2 合计税额 两位小数。为空时,根据明细项目进行合计。
BZ String 200 备注
LYID String 100 请求来源唯一标识
BMB_BBH String 20 编码版本号 增加商品编码功能后,税局下载的商品编码表版本
ORGCODE String 50 开票点编码 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点
WXORDERID String 微信订单号 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包(参考微信卡包指引) (opens new window)
WXAPPID String 微信商户id 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包(参考微信卡包指引) (opens new window)
WXAUTHID String 微信批量插入卡包的授权id 如果使用批量插入卡包的授权,需要传此参数,否则不需要
ZDYBZ String 200 自定义备注
SGBZ String 1 收购标志 2-农产品收购
TSPZ String 1 特殊票种 0-一般
2-燃油增值税专用发票
8-农产品销售
9-农产品收购
11-烟草发票
12-机动车发票
14-成品油发票
DK-代开发票
16-矿产品发票
E01-成品油发票
E03-建筑服务发票
E04 货物运输
E05-不动产销售
E06-不动产租赁服务发票
E07-代收车船税
E09-旅客运输
E12-自产农产品销售
E16-农产品收购
E17-光伏收购
E18-卷烟发票
ZSFS String 1 0 征税方式 0-普通征税 2-差额征税
PROJECTCODE String 50 项目编号
TDZZSXMBH String 土地增值税项目编号 数电特殊票种-建筑服务必传
BDCDZ String 动产租赁地址 数电特殊票种-不动产租赁服务必传。格式:省市区详细地址
ZLQQ String 租赁日期起 数电特殊票种-不动产租赁服务必传。例:2023-03-22
ZLQZ String 租赁日期止 数电特殊票种-不动产租赁服务必传。例:2024-03-22
KDSBZ String 是否跨地址标志 数电特殊票种-不动产租赁服务及建筑服务必传。枚举Y:是;N:否
SDLC String 数电纸票联次Value 数电纸质发票联次信息。枚举为:
{"label":"2016版增值税普通发票(二联折叠票)","value":"04","type":"3"}
{"label":"2016版增值税普通发票(五联折叠票)","value":"05","type":"3"}
{"label":"增值税专用发票(中文三联无金额限制版)","value":"1130","type":"1"}
{"label":"增值税专用发票(中文六联无金额限制版)","value":"1160","type":"1"}
注:传值为对应的value字段,枚举不完善,如不满足您的需求可联系我们补充
tspzs List 数电特殊票种其他要素属性集合 数电特殊票种-建筑服务及旅客运输等必传。
LYDJH String 200 业务来源单据号

# 发票明细

参数 类型 长度 是否必填 默认值 描述 说明
FPHXZ Integer 2 0 发票行性质 0-正常行1-折扣行2-被折扣行
XMMC String 600 项目名称
XMBM String 90 项目编码 如果项目名称为空,会根据项目编码匹配云平台的商品档案
GGXH String 150 规格型号 非数电发票规格型号最大长度为40个字符,数电发票长度为150个字符
DW String 300 单位 如果是成品油发票,必填且只能是"吨或升"<\br>如果是矿产品发票,必填且只能是"立方米、吨、千克、克或克拉"
XMSL BigDecimal 15,6 项目数量 如果是成品油发票,必填。
XMDJ BigDecimal 15,6 项目单价 项目单价为空时,根据项目金额反算。不为空时不进行计算
XMJE BigDecimal 15,2 项目金额 项目金额为空,根据项目价税合计反算。不为空时不进行计算
XMJSHJ BigDecimal 15,2 项目价税合计
SL BigDecimal 6,6 税率 6 位小数,例 17%为 0.17
SE BigDecimal 15,2 税额 税额为空,根据价税合计反算。不为空时不进行计算。
HH string 60 行号 有折扣时需要必输
ZKHHH string 60 折扣行行号 有折扣时需要必输
SPBM string 19 商品编码 税收分类编码
ZXBM string 20 10 商品自行编码
YHZCBS int 2 0 享受优惠标识 0:不使用,1:使用
LSLBS string 1 零税率标识 空:非零利率,0:出口退税,1:免税,2:不征收,3普通零税率
ZZSTSGL string 50 优惠政策说明 按3%简易征收,按5%简易征收 (此字段非空时,享受优惠标识YHZCBS字段值需为1)
KCE BigDecimal 15,2 扣除额 扣除额
detailMotor Object 明细特殊票种扩展属性 机动车及数电特殊票种-不动产租赁服务及建筑服务必传

# 明细特殊票种扩展属性 -> detailMotor

参数 类型 长度 是否必填 默认值 描述 说明
CQZSBH String 产权证书编号 数电特殊票种-不动产租赁服务必传。没有传 无
JZFWFSD String 建筑服务发生地 数电特殊票种-建筑服务必传
JZXMMC String 建筑项目名称 数电特殊票种-建筑服务必传
CD String 产地 机动车开具时-产地必传
CJHM String 车架号码 机动车开具-车架号码必传
CLLX String 车辆类型 机动车开具-车辆类型必传
CPXH String 厂牌型号 机动车开具-厂牌型号必传
SCQYMC String 生产企业名称 机动车开具-生产企业名称必传
SFZHM String 身份证号码/组织机构代码 机动车开具-身份证号码/组织机构代码非必填
HGZH String 合格证号 机动车开具-合格证号非必填
JKZMSH String 进口证明书号 机动车开具-进口证明书号非必填
SJDH String 商检单号 机动车开具-商检单号非必填
FDJHM String 发动机号码 机动车开具-发动机号码非必填
DUNWEI String 吨位 机动车开具-吨位非必填
XCRS String 限乘人数 机动车开具-限乘人数非必填
DH String 电话 机动车开具-电话非必填
DZ String 地址 机动车开具-地址非必填
KHYH String 开户银行 机动车开具-开户银行非必填
ZH String 账户 机动车开具-账户非必填
SWJGDM String 主管税务机关代码 机动车开具-主管税务机关代码非必填
SWJGMC String 主管税务机关名称 机动车开具-主管税务机关名称非必填
WSPZH String 完税凭证号 机动车开具-完税凭证号非必填
GFDZ String 买方单位/个人住址 机动车开具-买方单位/个人住址非必填
GFDH String 买方电话 机动车开具-买方电话非必填
CPZH String 车牌照号 机动车开具-车牌照号非必填
DJZH String 登记证号 机动车开具-登记证号非必填
CGSMC String 转入地车辆车管所名称 机动车开具-转入地车辆车管所名称非必填
XFDW String 卖方单位/个人 机动车开具-卖方单位/个人非必填
XFHM String 卖方单位代码/身份证号 机动车开具-卖方单位代码/身份证号非必填
XFDZ String 卖方单位/个人住址 机动车开具-卖方单位/个人住址非必填
XFDH String 卖方电话 机动车开具-卖方电话非必填
JYDW String 经营、拍卖单位 机动车开具-经营、拍卖单位非必填
JYDZ String 经营、拍卖单位地址 机动车开具-经营、拍卖单位地址非必填
JYSBH String 经营、拍卖单位纳税人识别号 机动车开具-经营、拍卖单位纳税人识别号非必填
JYYHZH String 开户银行及账号 机动车开具-开户银行及账号非必填
JYDH String 经营、拍卖单位电话 机动车开具-经营、拍卖单位电话非必填
SCMC String 二手车市场 机动车开具-二手车市场非必填
SCSBH String 二手车市场纳税人识别号 机动车开具-二手车市场纳税人识别号非必填
SCDZ String 二手车市场地址 机动车开具-二手车市场地址非必填
SCYHZH String 二手车市场开户银行及账号 机动车开具-二手车市场开户银行及账号非必填
SCDH String 二手车市场电话 机动车开具-二手车市场电话非必填
VEHICLEVERSION String 机动车统一销售发票版本 机动车开具-机动车统一销售发票版本非必填
GMFSFZJLX String 机动车统一销售发票购买方身份证件类型 机动车开具-机动车统一销售发票购买方身份证件类型非必填
GMFSFZJLX String 机动车统一销售发票购买方身份证件类型 机动车开具-机动车统一销售发票购买方身份证件类型非必填

# 数电特殊票种其他要素属性 -> tspzs

参数 类型 长度 是否必填 默认值 描述 说明
JTGJLXDM String 交通工具类型 数电特殊票种-建筑服务及旅客运输等必传
YSMXXH String 运输明细序号 数电特殊票种-建筑服务及旅客运输等必传
QYD String 起运地 数电特殊票种-建筑服务及旅客运输等必传
DDD String 到达地 数电特殊票种-建筑服务及旅客运输等必传
YSGJZL String 运输工具种类 数电特殊票种-建筑服务及旅客运输等必传
YSGJPH String 运输工具牌号 数电特殊票种-建筑服务及旅客运输等必传
YSHWMC String 运输货物名称 数电特殊票种-建筑服务及旅客运输等必传

# 邮件推送配置

参数 类型 长度 是否必填 默认值 描述 说明
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_DZ" : "销售方地址", (数电专用)
    "XSF_DH" : "销售方电话", (数电专用)
	"XSF_YHZH" : "销售方银行、账号",
    "XSF_YH" : "销售方银行",  (数电专用)
    "XSF_ZH" : "销售方账号",   (数电专用)
	"GMF_NSRSBH" : "购买方纳税人识别号",(发票类型31 数电专用发票(必填))
	"GMF_MC" : "购买方名称",
	"GMF_DZDH" : "购买方地址、电话",
    "GMF_DZ" : "购买方地址",(数电专用)
    "GMF_DH" : "购买方电话",(数电专用)
	"GMF_YHZH" : "购买方银行、账号",
    "GMF_YH" : "购买方银行",(数电专用)
    "GMF_ZH" : "购买方账号",(数电专用)
    "ALLELCUSERNAME": "电子税务局用户名(发票类型为数电发票时必传并且是国密四密文)",(数电专用)
    "ALLELCPASSWORD": "电子税务局密码 (发票类型为数电发票时必传并且是国密四密文)",(数电专用)
	"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",
		        }
		    ]
}]

数电不动产接口报文样例

requestdatas=[{
	"FPQQLSH": "202302171613262049",
	"FPLX": "31",
	"XSF_NSRSBH": "441234567AAAAAA",
	"XSF_MC": "htt灰度01AAAA",
	"TSPZ": "E06",//特殊票种-不动产租赁代码
    "BDCDZ": "北京市/北京市辖区/东城区*滨河公园5-4-1803",//不动产租赁地址:省市区详细地址
    "ZLQQ": "2023-03-22",//租赁日期起
    "ZLQZ": "2023-04-30",//租赁日期止
    "KDSBZ": "N",//是否跨地址标志 Y:是;N:否
	"ORGCODE": "htt001",
	"GMF_NSRSBH": "913418025914242188",
	"GMF_MC": "labo1",
	"GMF_DZDH": "",
	"GMF_YHZH": "",
	"KPR": "hett",
	"SKR": "hett",
	"FHR": "hett",
	"JSHJ": 14837.170000000000000,
	"HJJE": 13130.240000000000000,
	"HJSE": 1706.930000000000000,
	"BZ": "",
	"BMB_BBH": "28.0",
	"XSF_DZ": "北京",
	"XSF_DH": "13141422555",
	"XSF_YH": "北京",
	"XSF_ZH": "65456787889",
	"GMF_DZ": "",
	"GMF_DH": null,
	"GMF_YH": "",
	"GMF_ZH": "",
	"items": [{
		"FPHXZ": 0,
		"XMMC": "*经营租赁*小熊",
		"XMBM": "3040502020101000000",
		"GGXH": "",
		"XMJE": 13130.240000000000000,
		"XMJSHJ": 14837.17,
		"SL": 0.13,
		"SE": 1706.930000000000000,
		"HH": 1,
		"SPBM": "3040502020101000000",
		"DW": "公顷",//面积单位,取值为:平方千米、平方米、公顷、亩
		"XMSL": 870.000000000000000,
		"detailMotor": {
			"CQZSBH": "2324423231231231" //产权证书编号
		}
	}]
}]

数电建筑服务接口报文样例

requestdatas=[{
    "FPQQLSH": "202302171613262049",
    "FPLX": "31",
    "XSF_NSRSBH": "441234567AAAAAA",
    "XSF_MC": "htt灰度01AAAA",
    "TSPZ": "E03",//特殊票种-建筑服务代码
    "TDZZSXMBH": "232342131",//土地增值税项目编号
    "KDSBZ": "N",//是否跨地址标志 Y:是;N:否
    "ORGCODE": "htt001",
    "GMF_NSRSBH": "913418025914242188",
    "GMF_MC": "labo1",
    "GMF_DZDH": "",
    "GMF_YHZH": "",
    "KPR": "hett",
    "SKR": "hett",
    "FHR": "hett",
    "JSHJ": 14837.170000000000000,
    "HJJE": 13130.240000000000000,
    "HJSE": 1706.930000000000000,
    "BZ": "",
    "BMB_BBH": "28.0",
    "XSF_DZ": "北京",
    "XSF_DH": "13141422555",
    "XSF_YH": "北京",
    "XSF_ZH": "65456787889",
    "GMF_DZ": "",
    "GMF_DH": null,
    "GMF_YH": "",
    "GMF_ZH": "",
    "items": [{
        "FPHXZ": 0,
        "XMMC": "*经营租赁*小熊",
        "XMBM": "3040502020101000000",
        "GGXH": "",
        "XMJE": 13130.240000000000000,
        "XMJSHJ": 14837.17,
        "SL": 0.13,
        "SE": 1706.930000000000000,
        "HH": 1,
        "SPBM": "3040502020101000000",
        "DW": "公顷",
        "XMSL": 870.000000000000000,
        "detailMotor": {
            "JZFWFSD": "北京市/北京市辖区/东城区",//建筑服务发生地
            "JZXMMC": "地砖"//建筑项目名称
        }
    }]
 }]

数电货物运输服务接口报文样例

requestdatas=[{
    "FPQQLSH": "202302171613262050",
    "FPLX": "31",
    "XSF_NSRSBH": "44123456789012AAAAAA",
    "XSF_MC": "AAAA广东全电",
    "TSPZ": "E04",
    "ORGCODE": "AAAA03",
    "GMF_NSRSBH": "913418025914242188",
    "GMF_MC": "labo1",
    "GMF_DZDH": "",
    "GMF_YHZH": "",
    "KPR": "hett",
    "SKR": "hett",
    "FHR": "hett",
    "JSHJ": 14837.170000000000000,
    "HJJE": 13130.240000000000000,
    "HJSE": 1706.930000000000000,
    "BZ": "",
    "BMB_BBH": "28.0",
    "XSF_DZ": "北京",
    "XSF_DH": "13141422555",
    "XSF_YH": "北京",
    "XSF_ZH": "65456787889",
    "GMF_DZ": "",
    "GMF_DH": null,
    "GMF_YH": "",
    "GMF_ZH": "",
    "items": [{
        "FPHXZ": 0,
        "XMMC": "*运输服务*国内道路货物运输服务",
        "XMBM": "3010102020100000000",
        "GGXH": "",
        "XMJE": 13130.240000000000000,
        "XMJSHJ": 14837.17,
        "SL": 0.13,
        "SE": 1706.930000000000000,
        "HH": 1,
        "SPBM": "3040502020101000000",
        "DW": "吨",
        "XMSL": 870.000000000000000
    }],
    "tspzs": [{
        "YSMXXH": "1",
        "YSGJZL": "铁路运输",
        "YSGJPH": "G443",
        "QYD": "北京市>北京市辖区>东城区",
        "DDD": "河北省>石家庄市>长安区",
        "YSHWMC": "煤矿"
    }, {
        "YSMXXH": "2",
        "YSGJZL": "铁路运输",
        "YSGJPH": "Z3232",
        "QYD": "河北省>石家庄市>桥西区",
        "DDD": "天津市>天津市辖区>和平区",
        "YSHWMC": "瓷器"
    }]
 }]

数电不动产销售接口报文样例

requestdatas=[{
  "FPQQLSH": "202302171613262049",
  "FPLX": "31",
  "XSF_NSRSBH": "441234567AAAAAA",
  "XSF_MC": "htt灰度01AAAA",
  "TSPZ": "E05", //特殊票种-不动产销售代码
  "BDCDZ": "北京市/北京市辖区/东城区*滨河公园5-4-1803", //不动产地址:省市区详细地址
  "WQHTBABH": "312312312123", //网签合同备案编号
  "TDZZSXMBH": "312312312123", //土地增值税项目编号
  "BDCDWDM": "312312312123", //不动产单元代码
  "HDJSJG": "312312312123", //核定计税价格。不属于核定计税不动产销售的可不填写
  "SJCJHSJE": "312312312123", //实际成交含税金额。若按核定计税价格征税的,为必填
  "KDSBZ": "N", //是否跨地址标志 Y:是;N:否
  "ORGCODE": "htt001",
  "GMF_NSRSBH": "913418025914242188",
  "GMF_MC": "labo1",
  "GMF_DZDH": "",
  "GMF_YHZH": "",
  "KPR": "hett",
  "SKR": "hett",
  "FHR": "hett",
  "JSHJ": 14837.170000000000000,
  "HJJE": 13130.240000000000000,
  "HJSE": 1706.930000000000000,
  "BZ": "",
  "BMB_BBH": "28.0",
  "XSF_DZ": "北京",
  "XSF_DH": "13141422555",
  "XSF_YH": "北京",
  "XSF_ZH": "65456787889",
  "GMF_DZ": "",
  "GMF_DH": null,
  "GMF_YH": "",
  "GMF_ZH": "",
  "items": [{
    "FPHXZ": 0,
    "XMMC": "*经营租赁*小熊",
    "XMBM": "3040502020101000000",
    "GGXH": "",
    "XMJE": 13130.240000000000000,
    "XMJSHJ": 14837.17,
    "SL": 0.13,
    "SE": 1706.930000000000000,
    "HH": 1,
    "SPBM": "3040502020101000000",
    "DW": "公顷", //面积单位,取值为:平方千米、平方米、公顷、亩
    "XMSL": 870.000000000000000,
    "detailMotor": {
      "CQZSBH": "2324423231231231" //产权证书编号
    }
  }]
}]

数电旅客运输接口报文样例

requestdatas=[{
  "FPQQLSH": "202302171613262049",
  "FPLX": "31",
  "XSF_NSRSBH": "441234567AAAAAA",
  "XSF_MC": "htt灰度01AAAA",
  "TSPZ": "E09",//特殊票种-旅客运输服务
  "ORGCODE": "htt001",
  "GMF_NSRSBH": "913418025914242188",
  "GMF_MC": "labo1",
  "GMF_DZDH": "",
  "GMF_YHZH": "",
  "KPR": "hett",
  "SKR": "hett",
  "FHR": "hett",
  "JSHJ": 14837.170000000000000,
  "HJJE": 13130.240000000000000,
  "HJSE": 1706.930000000000000,
  "BZ": "",
  "BMB_BBH": "28.0",
  "XSF_DZ": "北京",
  "XSF_DH": "13141422555",
  "XSF_YH": "北京",
  "XSF_ZH": "65456787889",
  "GMF_DZ": "",
  "GMF_DH": null,
  "GMF_YH": "",
  "GMF_ZH": "",
  "items": [{
    "FPHXZ": 0,
    "XMMC": "*运输服务*国际旅客运输",
    "XMBM": "3040502020101000000",
    "GGXH": "",
    "XMJE": 13130.240000000000000,
    "XMJSHJ": 14837.17,
    "SL": 0.13,
    "SE": 1706.930000000000000,
    "HH": 1,
    "SPBM": "3040502020101000000",
    "DW": "",
    "XMSL": 870.000000000000000
  }],
  "tspzs": [{
    "CXRXH": "1",//出行人序号
    "CXR": "张三",//出行人
    "CHUXRQ": "2022-11-01",//出行日期,格式yyyy-MM-dd
    "CXRZJLXDM": "101",//出行人证件类型代码
    "SFZJHM": "1233445",//身份证件号码
    "CFD": "北京市怀柔区",//出发地
    "LKDDD北京市平谷区",//到达地
    "ZWDJ": "二等座",//座位等级若交通工具为火车、飞机、船舶,则该要素为必填,其他选项选填;
    ---交通工具为火车:一等座、二等座、软席(软座、软卧)、硬席(硬座、硬卧)
    ---交通工作为飞机: 经济舱、头等舱、公务舱
    ---交通工具为船舶: 一等舱、二等舱 、三等舱
    "JTGJLXDM": "2"//交通工具类型代码(1:飞机 2:火车 3:长途汽车 4:公共交通 5:出租车 6:汽车 7:船舶 9:其他)
  }]
}]

# 出行人证件类型代码枚举

证据类型代码 证据名称
101 组织机构代码证
102 营业执照
103 税务登记证
199 其他单位证件
201 居民身份证
202 军官证
203 武警警官证
204 士兵证
205 军队离退休干部证
206 残疾人证
207 残疾军人证(1-8级)
208 外国护照
210 港澳居民来往内地通行证
212 中华人民共和国往来港澳通行证
213 台湾居民来往大陆通行证
214 大陆居民往来台湾通行证
215 外国人居留证
216 外交官证
217 使(领事)馆证
218 海员证
219 香港永久性居民身份证
220 台湾身份证
221 澳门特别行政区永久性居民身份证
222 外国人身份证件
224 就业失业登记证
225 退休证
226 离休证
227 中国护照
228 城镇退役士兵自谋职业证
229 随军家属身份证明
230 中国人民解放军军官转业证书
231 中国人民解放军义务兵退出现役证
232 中国人民解放军士官退出现役证
233 外国人永久居留身份证(外国人永久居留证)
234 就业创业证
235 香港特别行政区护照
236 澳门特别行政区护照
237 中华人民共和国港澳居民居住证
238 中华人民共和国台湾居民居住证
239 《中华人民共和国外国人工作许可证》(A类)
240 《中华人民共和国外国人工作许可证》(B类)
241 《中华人民共和国外国人工作许可证》(C类)
291 出生医学证明
299 其他个人证件

数电代收车船税发票报文样例

requestdatas=[{
  "FPQQLSH": "202302171613262049",
  "FPLX": "31",
  "XSF_NSRSBH": "441234567AAAAAA",
  "XSF_MC": "htt灰度01AAAA",
  "TSPZ": "E07",//特殊票种-代收车船税代码
  "BXDH": "89293943482432",//保险单号
  "CPHCBDJH": "豫MUX555",//车牌号/船舶登记号
  "SKSSQ": "2023-01 2023-03",//税款所属期yyyy-MM+空格+yyyy-MM
  "DSCCSJE": "350000",//代收车船税金额
  "ZNJ": "100",//滞纳金
  "JEHJ": "350100",//金额合计
  "CJH": "233134133384938",//车辆识别代码/车架号码
  "ORGCODE": "htt001",
  "GMF_NSRSBH": "913418025914242188",
  "GMF_MC": "labo1",
  "GMF_DZDH": "",
  "GMF_YHZH": "",
  "KPR": "hett",
  "SKR": "hett",
  "FHR": "hett",
  "JSHJ": 14837.170000000000000,
  "HJJE": 13130.240000000000000,
  "HJSE": 1706.930000000000000,
  "BZ": "",
  "BMB_BBH": "28.0",
  "XSF_DZ": "北京",
  "XSF_DH": "13141422555",
  "XSF_YH": "北京",
  "XSF_ZH": "65456787889",
  "GMF_DZ": "",
  "GMF_DH": null,
  "GMF_YH": "",
  "GMF_ZH": "",
  "items": [{
    "FPHXZ": 0,
    "XMMC": "*代收车船*宝马X6",
    "XMBM": "3040502020101000000",
    "GGXH": "",
    "XMJE": 13130.240000000000000,
    "XMJSHJ": 14837.17,
    "SL": 0.13,
    "SE": 1706.930000000000000,
    "HH": 1,
    "SPBM": "3040502020101000000",
    "DW": "辆",
    "XMSL": 870.000000000000000
  }]
}]

数电差额征收-差额开票报文样例

requestdatas=[{
  "FPQQLSH": "202302171613262050",
  "FPLX": "31",
  "XSF_NSRSBH": "44123456789012AAAAAA",
  "XSF_MC": "AAAA广东全电",
  "ZSFS": "2",//征收方式:差额征收-差额开票
  "ORGCODE": "AAAA03",
  "GMF_NSRSBH": "913418025914242188",
  "GMF_MC": "labo1",
  "GMF_DZDH": "",
  "GMF_YHZH": "",
  "KPR": "hett",
  "SKR": "hett",
  "FHR": "hett",
  "JSHJ": 14837.170000000000000,
  "HJJE": 13130.240000000000000,
  "HJSE": 1706.930000000000000,
  "BZ": "",
  "BMB_BBH": "28.0",
  "XSF_DZ": "北京",
  "XSF_DH": "13141422555",
  "XSF_YH": "北京",
  "XSF_ZH": "65456787889",
  "GMF_DZ": "",
  "GMF_DH": null,
  "GMF_YH": "",
  "GMF_ZH": "",
  "items": [{
    "FPHXZ": 0,
    "XMMC": "*运输服务*国内道路货物运输服务",
    "XMBM": "3010102020100000000",
    "GGXH": "",
    "XMJE": 13130.240000000000000,
    "XMJSHJ": 14837.17,
    "SL": 0.13,
    "SE": 1706.930000000000000,
    "HH": 1,
    "SPBM": "3040502020101000000",
    "DW": "吨",
    "XMSL": 870.000000000000000,
    "KCE": 100.00//扣除总额
  }],
  "cepzs": [{
    "XH": 1,//序号
    "PZLX": "01",//凭证类型,01 数电票、02 增值税专用发票、03 增值税普通发票、04 营业税发票、05 财政票据、06 法院裁决书、07 契税完税凭证、08 其他发票类、09 其他扣除凭证
    "FPHM": "23442000000000000293",//全电发票号码
    "FPDM": "",//非全电发票代码
    "ZZFPHM": "",//非全电发票号码
    "PZHM": "",//凭证号码
    "KJRQ": "2023-01-04",//开票日期,格式形如:yyyy-MM-dd
    "HJJE": 1000.00,//合计金额
    "KCE": 100.00,//扣除额
    "BZ": "",//备注
    "LY": "手工录入",//录入方式,取值范围如下:手工录入 勾选录入 模板录入
    "BCKCJE": 100.00,//本次扣除金额
    "PZHJJE": 1000.00//凭证合计金额
  }]
}]

数电差额征收-全额开票报文样例

requestdatas=[{
  "FPQQLSH": "202302171613262050",
  "FPLX": "31",
  "XSF_NSRSBH": "44123456789012AAAAAA",
  "XSF_MC": "AAAA广东全电",
  "ZSFS": "3", //征收方式:差额征收-全额开票
  "ORGCODE": "AAAA03",
  "GMF_NSRSBH": "913418025914242188",
  "GMF_MC": "labo1",
  "GMF_DZDH": "",
  "GMF_YHZH": "",
  "KPR": "hett",
  "SKR": "hett",
  "FHR": "hett",
  "JSHJ": 14837.170000000000000,
  "HJJE": 13130.240000000000000,
  "HJSE": 1706.930000000000000,
  "BZ": "",
  "BMB_BBH": "28.0",
  "XSF_DZ": "北京",
  "XSF_DH": "13141422555",
  "XSF_YH": "北京",
  "XSF_ZH": "65456787889",
  "GMF_DZ": "",
  "GMF_DH": null,
  "GMF_YH": "",
  "GMF_ZH": "",
  "items": [{
    "FPHXZ": 0,
    "XMMC": "*运输服务*国内道路货物运输服务",
    "XMBM": "3010102020100000000",
    "GGXH": "",
    "XMJE": 13130.240000000000000,
    "XMJSHJ": 14837.17,
    "SL": 0.13,
    "SE": 1706.930000000000000,
    "HH": 1,
    "SPBM": "3040502020101000000",
    "DW": "吨",
    "XMSL": 870.000000000000000,
    "KCE": 100.00 //扣除总额,与差额征收差额开票一样
  }],
  "cepzs": [{
    "XH": 1, //序号
    "PZLX": "01", //凭证类型,01 数电票、02 增值税专用发票、03 增值税普通发票、04 营业税发票、05 财政票据、06 法院裁决书、07 契税完税凭证、08 其他发票类、09 其他扣除凭证
    "FPHM": "23442000000000000293", //全电发票号码
    "FPDM": "", //非全电发票代码
    "ZZFPHM": "", //非全电发票号码
    "PZHM": "", //凭证号码
    "KJRQ": "2023-01-04", //开票日期,格式形如:yyyy-MM-dd
    "HJJE": 1000.00, //合计金额
    "KCE": 100.00, //扣除额
    "BZ": "", //备注
    "LY": "手工录入", //录入方式,取值范围如下:手工录入 勾选录入 模板录入
    "BCKCJE": 100.00, //本次扣除金额
    "PZHJJE": 1000.00 //凭证合计金额
  }]

}]

# 返回结果说明

参数 类型 描述 说明
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 开票申请发票信息,包含发票头发票明细
email 邮件推送配置(可选)
sms 短信推送配置(可选)
url 回调服务配置(可选)
autoAudit 是否自动审核(可选) true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认
如果不传,代表true
nc6x-ubl ncc友企联回调参数

# 发票头

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号
FPLX String 1 1 发票类型 1:增值税电子普通发票;
2:增值税电子专用发票;
3:增值税普通发票;
4:增值税专用发票 、增值税专用发票(机动车);
5:机动车销售统一发票;
8:增值税电子普通发票(成品油);
10:成品油普通发票;
11:成品油专用发票;
15:二手车销售统一发票;
31:数电专用发票;
32:数电普通发票;
33:数电纸质发票(增值税专用发票);
34:数电纸质发票(普通发票);
XSF_NSRSBH String 20 销售方纳税人识别号
XSF_MC String 100 销售方名称 如果为空,获取发票平台配置的销售方名称
XSF_DZDH String 100 销售方地址、电话 如果为空,获取发票平台配置的销售方地址及电话
XSF_DZ String 300 销售方地址 如果为空,获取发票平台配置的销售方地址 (数电专用字段)
XSF_DH String 60 销售方电话 如果为空,获取发票平台配置的销售方电话 (数电专用字段)
XSF_YHZH String 100 销售方银行、账号 如果为空,获取发票平台配置的销售方银行及账号
XSF_YH String 120 销售方银行 如果为空,获取发票平台配置的销售方银行 (数电专用字段)
XSF_ZH String 100 销售方账号 如果为空,获取发票平台配置的销售方账号 (数电专用字段)
GMF_NSRSBH String 20 购买方纳税人识别号 发票类型31 数电专用发票(必填)
GMF_MC String 100 购买方名称
GMF_DZDH String 100 购买方地址、电话 数电票此字段非必填
GMF_YHZH String 100 购买方银行、账号 数电票此字段非必填
GMF_DZ String 300 购买方地址 (数电专用字段)
GMF_DH String 60 购买方电话 如果为空,获取发票平台配置的购买方地址 (数电专用字段)
GMF_YH String 120 购买方银行 如果为空,获取发票平台配置的购买方电话(数电专用字段)
GMF_ZH String 100 购买方账号 如果为空,获取发票平台配置的购买方账号(数电专用字段)
ALLELCUSERNAME String 300 数电发票用户名 使用基础通道开具数电票且需要指定税局账号时,该字段必填。
ALLELCPASSWORD String 300 数电发票密码 使用基础通道开具数电票且需要指定税局账号时,该字段必填。
KPR String 8 开票人 如果为空,获取发票平台配置的开票人
SKR String 8 收款人 如果为空,获取发票平台配置的收款人
FHR String 8 复核人 如果为空,获取发票平台配置的复核人
SLSM String 1 税率说明 1:当小规模纳税人开具3%税率时需要填写税率说明 2:前期已开具发票,发生销售折让、中止或者退回等情形需要开具红字发票,或者开票有误需要重新开具; 3:因为实际经营业务需要,放弃享受减按1%征收率征收增值税政策。
JSHJ BigDecimal 15,2 价税合计 两位小数。明细价税合计之和必须与总的价税合计一致
HJJE BigDecimal 15,2 合计金额 两位小数。为空时,根据明细项目进行合计。
HJSE BigDecimal 15,2 合计税额 两位小数。为空时,根据明细项目进行合计。
BZ String 230 备注 长度不能大于230
LYID String 100 请求来源唯一标识
BMB_BBH String 20 13.0 编码版本号 增加商品编码功能后,税局下载的商品编码表版本
ORGCODE String 50 开票点编码 支持一个公司多个开票点场景。如果为空,获取默认的开票点
PROJECTCODE String 50 项目编号 https://developers.weixin.qq.com/doc/offiaccount/WeChat_Invoice/E_Invoice/Instruction.html
WXORDERID String 微信订单号 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包(参考微信卡包指引) (opens new window)
WXAPPID String 微信商户id 商户开票完成后,传入订单号和商户id将自动插入用户微信卡包(参考微信卡包指引) (opens new window)
TSPZ String 1 特殊票种 0-一般
2-燃油增值税专用发票
8-农产品销售
9-农产品收购
11-烟草发票
12-机动车发票
14-成品油发票
DK-代开发票
16-矿产品发票
E01-成品油发票
E03-建筑服务发票
E04 货物运输
E05-不动产销售
E06-不动产租赁服务发票
E07-代收车船税
E09-旅客运输
E12-自产农产品销售
E16-农产品收购
E17-光伏收购
E18-卷烟发票
LYDJH String 200 业务来源单据号

# 发票明细

参数 类型 长度 是否必填 默认值 描述 说明
FPHXZ Integer 2 0 发票行性质 0-正常行1-折扣行2-被折扣行
XMMC String 600 项目名称
GGXH String 150 规格型号 非数电发票规格型号最大长度为40个字符,数电发票长度为150个字符
DW String 300 单位 如果是成品油发票,必填且只能是"吨或升"<\br>如果是矿产品发票,必填且只能是"立方米、吨、千克、克或克拉"
XMSL BigDecimal 15,6 项目数量 如果是成品油发票,必填。
XMDJ BigDecimal 15,6 项目单价 项目单价为空时,根据项目金额反算。不为空时不进行计算
XMJE BigDecimal 15,2 项目金额 项目金额为空,根据项目价税合计反算。不为空时不进行计算
XMJSHJ BigDecimal 15,2 项目价税合计
SL BigDecimal 6,6 税率 6 位小数,例 17%为 0.17
SE BigDecimal 15,2 税额 税额为空,根据价税合计反算。不为空时不进行计算。
HH string 60 行号 有折扣时需要必输
ZKHHH string 60 折扣行行号 有折扣时需要必输
SPBM string 19 商品编码 税收分类编码
ZXBM string 20 10 商品自行编码
YHZCBS int 2 0 享受优惠标识 0:不使用,1:使用
LSLBS string 1 零税率标识 空:非零利率,0:出口退税,1:免税,2:不征收,3普通零税率
ZZSTSGL string 50 优惠政策说明 按3%简易征收,按5%简易征收 (此字段非空时,享受优惠标识YHZCBS字段值需为1)
KCE BigDecimal 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通过友企联方案回调时必传

# 请求样例

标准表单参数样例

requestdatas=[ {
	"FPQQLSH" : "12345678901234567890",
	"XSF_NSRSBH" : "销售方纳税人识别号",
	"XSF_MC" : "销售方名称",
	"XSF_DZDH" : "销售方地址、电话",
    "XSF_DZ" : "销售方地址",  (数电专用字段)
    "XSF_DH" : "销售方电话",(数电专用字段)
	"XSF_YHZH" : "销售方银行、账号",
    "XSF_YH" : "销售方银行",(数电专用字段)
    "XSF_ZH" : "销售方账号",(数电专用字段)
	"GMF_NSRSBH" : "购买方纳税人识别号", (发票类型31 数电专用发票(必填))(数电专用字段)
	"GMF_MC" : "购买方名称",
	"GMF_DZDH" : "购买方地址、电话",
    "GMF_DZ" : "购买方地址",(数电专用字段)
    "GMF_DH" : "购买方电话",(数电专用字段)
	"GMF_YHZH" : "购买方银行、账号",
    "GMF_YH" : "购买方银行",(数电专用字段)
    "GMF_ZH" : "购买方账号",(数电专用字段)
    "ALLELCUSERNAME": "电子税务局用户名(发票类型为数电发票时必传并且是国密四密文)",(数电专用字段)
    "ALLELCPASSWORD": "电子税务局密码 (发票类型为数电发票时必传并且是国密四密文)",  (数电专用字段)
	"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"
    }]
&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 拆分结果数据 拆分结果数据

# 拆分结果数据

  • 发票头数据
参数 类型 必填 描述 说明
bmbBbh String 商品编码表版本
fpjz String 发票介质 0.电票 2.纸票
fplx String 发票类型 详见发票类型
fpzt String 发票状态 详见发票状态
kplx String 开票类型 0.蓝票 1.红票
lylx String 来源类型 详见开票来源类型
lyid String 存储ID 存储拆分前发票的发票请求流水号
zdrq String 制单日期
zsfs String 征税方式:0.征税 2.差额
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 BigDecimal 合计金额
hjse BigDecimal 合计税额
jshj BigDecimal 价税合计
kpr String 开票人
fhr String 复核人
skr String 收款人
bz String 备注
zfbz String 作废标志 Y:是;N否
bred String 是否红冲 Y:是;N否
zdybz String 自定义备注
items Object 发票明细 发票明细数据
  • 发票明细数据
参数 类型 必填 描述 说明
xmmc String 项目名称
ggxh String 规格型号
spbm String 商品编码
xmhsdj String 项目含税单价
yhzcbs String 销售优惠标识;0:不使用,1:使用
zxbm String 商品自行编码
dw String 单位
xmsl doube 项目数量
xmdj doube 项目单价
xmje doube 项目金额
xmjshj String 项目价税合计
sl String 税率
se String 税额
hh string 行号

# 返回样例

{
    "code": "0000",
    "msg": "success",
    "datas": [
        {
            "bmbBbh": "13.0",
            "bz": "BZSHY",
            "fhr": "FHR",
            "fpjz": "0",
            "fplx": "1",
            "fpqqlsh": "shy100000000000006",
            "fpzt": "2",
            "gmfDzdh": "address",
            "gmfMc": "shy00004",
            "gmfNsrsbh": "214356654322666",
            "gmfYhzh": "213456543233523",
            "hjje": 30000,
            "hjse": 5100,
            "jshj": 35100,
            "kplx": 0,
            "kpr": "KPR",
            "lyid":"<对于确实已发生拆分行为的发票,这里是:拆分前发票的发票请求流水号FPQQLSH>",
            "lylx": "1",
            "skr": "SKR",
            "xsfDzdh": "XSFDZDH",
            "xsfMc": "测试3",
            "xsfNsrsbh": "111222333456333",
            "xsfYhzh": "6217000010005805406",
            "zdrq": 1505400195352,
            "zsfs": "0",
            "items": [
                {
                    "dw": "DW",
                    "fphxz": 0,
                    "ggxh": "GGXH",
                    "hh": "1",
                    "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/V3/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 开票失败明细 开票状态为失败时,此处为明细信息
bstatus String 版式状态 0-重画版式成功;1-版式失败 (版式失败,data数据中没有pdf)
data String 开票内容 开票状态为开票成功时,返回开票信息,为JSONObject,信息格式与回调服务相同。

# 返回样例

    样例1(开票中)
    {
	    "fpqqlsh": "12345678901234567890",
		"code": "0000",
	    "msg": "查询成功",
	    "statuscode": "1",
		"status": "待开票"
	}

    样例2(开票失败)
    {
	    "fpqqlsh": "12345678901234567890",
		"code": "0000",
	    "msg": "查询成功",
	    "statuscode": "3",
		"status": "开票失败",
		"errmsg":"税控设备错误"
    }
   样例3(开票成功版式失败)
   {
       "fpqqlsh": "12345678901234567890",
       "bsstatus": "1",
       "code": "0000",
       "msg": "查询成功",
       "statuscode": "4",
       "status": "开票成功",
       "data": "{\"data\":\"{\\\"bmbBbh\\\":\\\"32.0\\\",\\\"bz\\\":\\\"差额征税:70.00。\\\",\\\"ewm\\\":\\\"01,10,011002680026,03198042,99.13,20220830,12530225777282455095,BCC0\\\",\\\"fhr\\\":\\\"KPR1234567890123\\\",\\\"fpDm\\\":\\\"011002680026\\\",\\\"fpHm\\\":\\\"03198042\\\",\\\"fpMw\\\":\\\"0395323/<+46230+83<0<053+502>67*48-<829/<6>/181288983/397*->/29134075+>-39+9225/-*>1/5/3>5+4750156>019<759/57102\\\",\\\"fpjz\\\":\\\"0\\\",\\\"fplx\\\":\\\"1\\\",\\\"fpqqlsh\\\":\\\"83010000004\\\",\\\"gmfDzdh\\\":\\\"测试地址电话 34432222\\\",\\\"gmfMc\\\":\\\"\\\",\\\"gmfNsrsbh\\\":\\\"111222333444555\\\",\\\"gmfYhzh\\\":\\\"111112222233333\\\",\\\"hjje\\\":99.13,\\\"hjse\\\":0.87,\\\"items\\\":[{\\\"dw\\\":\\\"\\\",\\\"fphxz\\\":0,\\\"ggxh\\\":\\\"\\\",\\\"hh\\\":\\\"2\\\",\\\"kce\\\":70.00,\\\"se\\\":0.87,\\\"sl\\\":0.030000,\\\"spbm\\\":\\\"1010101010000000000\\\",\\\"xmdj\\\":9.0114739629,\\\"xmje\\\":99.13,\\\"xmjshj\\\":100.00,\\\"xmmc\\\":\\\"*谷物*测试商品1\\\",\\\"xmsl\\\":11.0000000000,\\\"yhzcbs\\\":0,\\\"zkhhh\\\":\\\"\\\",\\\"zxbm\\\":\\\"10\\\"}],\\\"jqbh\\\":\\\"499000144000\\\",\\\"jshj\\\":100.00,\\\"jym\\\":\\\"12530225777282455095\\\",\\\"kplx\\\":0,\\\"kpr\\\":\\\"KPR1234567890123\\\",\\\"kprq\\\":\\\"20220830165147\\\",\\\"lyid\\\":\\\"\\\",\\\"lylx\\\":\\\"2\\\",\\\"skr\\\":\\\"KPR1234567890123\\\",\\\"xsfDzdh\\\":\\\"北京市海淀区北清路68号 62437728\\\",\\\"xsfMc\\\":\\\"11134444\\\",\\\"xsfNsrsbh\\\":\\\"111222333456333\\\",\\\"xsfYhzh\\\":\\\"建设银行 3333322222111111\\\",\\\"zdrq\\\":1661788800000,\\\"zsfs\\\":\\\"2\\\"}\",\"code\":\"0000\",\"msg\":\"开票成功\",\"fpqqlsh\":\"83010000004\"}",
       
}

# 版式重画

# URL

POST

Content-Type:application/x-www-form-urlencoded; charset=UTF-8

http://{HOST}:{PORT}/output-tax/api/invoiceApply/bsRestartCreate?appid=XXXXX

# 请求体参数

参数 类型 长度 是否必填 默认值 描述 说明
fpDm String 12 发票代码
fpHm String 8 发票号码

# 请求样例

表单参数如下

fpDm=200009975509
fpHm=26112814

# 返回结果说明

参数 类型 必填 描述 说明
code String 状态码 0000-查询成功;1002-数据不存在;详见状态码说明
msg String 信息说明
data String 默认是null

# 返回样例

    样例1(版式生成成功)
    {
		"code": "0000",
        "datas"  null,
	    "msg": "操作成功"
	}

    样例2(版式生成失败)
    {
		"code": "1002",
        "datas"  null,
	    "msg": "发票代码:200009975509,发票号码:26112813该张发票不存在!"
    }
    样例3(版式生成失败)
   {
        "code": "9999",
        "datas"  null,
        "msg": "发票代码:200009975509,发票号码:26112813重新生成版式失败!原因是:[购买方名称为空。]"
}
   

# 发票红冲请求

# 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 发票红冲参数
email 邮件推送配置(可选)
sms 短信推送配置(可选)
url 回调服务配置(可选)
autoAudit 是否自动审核(可选) true:自动审核,false:不自动审核。
如果不传,代表false
nc6x-ubl ncc友企联回调参数

# 发票红冲参数

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号 注意不是蓝字的发票请求流水号,是本次发票红冲的请求流水号
fplx String 4 发票类型 只有数电发票红冲税控发票才必填,必须指定发票类型为数电发票类型;详见发票类型
fpDm String 12 蓝字发票代码 被红冲的发票代码
fpHm String 20 蓝字发票号码 被红冲的发票号码
GMF_MC String 100 购买方名称
GMF_DZDH String 100 购买方地址电话
GMF_YHZH String 100 购买方银行账号
GMF_NSRSBH String 100 购买方纳税人识别号
ORGCODE String 50 开票点编码 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点
KPR String 8 开票人 如果为空,获取发票平台配置的开票人
SKR String 8 收款人 如果为空,获取发票平台配置的收款人
FHR String 8 复核人 如果为空,获取发票平台配置的复核人
SLSM String 1 税率说明 1:当小规模纳税人开具3%税率时需要填写税率说明 2:前期已开具发票,发生销售折让、中止或者退回等情形需要开具红字发票,或者开票有误需要重新开具;3:因为实际经营业务需要,放弃享受减按1%征收率征收增值税政策。
HCYY String 1 红冲原因说明 :1 销货退回 2 开票有误 3 开票中止 4 销售折让 数电发票红冲原因必填
hzxxbbh String 20 红字信息表编号 专票红冲时必传
ALLELCUSERNAME String 300 数电发票用户名 如果发票类型是数电发票用户名必填 ,并且是国密四密文 (数电专用字段)
ALLELCPASSWORD String 300 数电发票密码 如果发票类型是数电发票密码必填 , 并且是国密四密文 (数电专用字段)
LYDJH String 200 业务来源单据号

# 邮件推送配置

参数 类型 长度 是否必填 默认值 描述 说明
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",(数电专票红冲暂不需要红字信息表)
        "HCYY" :"1",  (数电发票红冲原因必填)
      "ALLELCUSERNAME": "电子税务局用户名(发票类型为数电发票时必传并且是国密四密文)", (数电专用字段)
      "ALLELCPASSWORD": "电子税务局密码 (发票类型为数电发票时必传并且是国密四密文)"   (数电专用字段)
    }
] 
&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

TIP

此接口只能用来开具蓝票

签名载荷(Payload)中必须包含属性requestdatas,它的值为表单参数requestdatas的MD5值,具体使用方式参考样例代码示例

# 请求体参数

# 表单说明

参数 描述 说明
requestdatas 发票开具动作参数

# 发票开具动作参数

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号
XSF_NSRSBH String 20 销售方纳税人识别号
JSHJ BigDecimal 价税合计

# 请求样例

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

TIP

每次请求的数据不能超过10张单据

签名载荷(Payload)中必须包含属性requestdatas,它的值为表单参数requestdatas的MD5值,具体使用方式参考样例代码示例

# 请求体参数

# 表单说明

参数 描述 说明
requestdatas 开票申请发票信息,包含发票头发票明细
email 邮件推送配置(可选)
sms 短信推送配置(可选)
url 回调服务配置(可选)
autoAudit 是否自动审核(可选) true:自动审核,即不需要人工在发票平台确认开票,直接进行开票 false:不自动审核,即需要人工确认
如果不传,代表true
nc6x-ubl ncc友企联回调参数

# 发票头

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号 注意不是蓝字的发票请求流水号,是本次发票红冲的请求流水号
fplx String 4 发票类型 只有数电发票红冲税控发票才必填,必须指定发票类型为数电发票类型;详见发票类型
fpDm String 12 蓝字发票代码 被红冲的发票代码
fpHm String 8 蓝字发票号码 被红冲的发票号码
GMF_MC String 100 购买方名称
GMF_DZDH String 100 购买方地址电话
GMF_YHZH String 100 购买方银行账号
GMF_NSRSBH String 100 购买方纳税人识别号
KPR String 8 开票人 如果为空,获取发票平台配置的开票人
SKR String 8 收款人 如果为空,获取发票平台配置的收款人
FHR String 8 复核人 如果为空,获取发票平台配置的复核人
SLSM String 1 税率说明 1:当小规模纳税人开具3%税率时需要填写税率说明 2:前期已开具发票,发生销售折让、中止或者退回等情形需要开具红字发票,或者开票有误需要重新开具; 3:因为实际经营业务需要,放弃享受减按1%征收率征收增值税政策。
HCYY String 1 红冲原因说明 :1 销货退回 2 开票有误 3 服务中止 4 销售折让 数电发票红冲原因必填
JSHJ BigDecimal 15,2 价税合计 两位小数。明细价税合计之和必须与总的价税合计一致;价税合计小于0,并且绝对值不能大于蓝票
HJJE BigDecimal 15,2 合计金额 两位小数。为空时,根据明细项目进行合计。合计金额小于0,并且绝对值不能大于蓝票
HJSE BigDecimal 15,2 合计税额 两位小数。为空时,根据明细项目进行合计。
LYID String 100 请求来源唯一标识
BMB_BBH String 20 13.0 编码版本号 增加商品编码功能后,税局下载的商品编码表版本
ORGCODE String 50 开票点编码 如果一个税号对应多个开票点,此字段必输,用于确定唯一开票点
hzxxbbh String 20 红字信息表编号 专票红冲时必传
ALLELCUSERNAME String 300 数电发票用户名 如果发票类型是数电发票用户名必填 ,并且是国密四密文 (数电专用字段)
ALLELCPASSWORD String 300 数电发票密码 如果发票类型是数电发票密码必填 , 并且是国密四密文 (数电专用字段)
LYDJH String 200 业务来源单据号

# 发票明细

参数 类型 长度 是否必填 默认值 描述 说明
XMMC String 90 项目名称
XMBM String 90 项目编码 如果项目名称为空,会根据项目编码匹配云平台的商品档案
GGXH String 40 规格型号 非数电发票规格型号最大长度为40个字符,数电发票长度为150个字符
DW String 20 单位
XMSL BigDecimal 15,6 项目数量 项目数量必须小于0
XMDJ BigDecimal 15,6 项目单价 项目单价为空时,根据项目金额反算。不为空时不进行计算;
XMJE BigDecimal 15,2 项目金额 项目金额为空,根据项目价税合计反算。不为空时不进行计算;金额必须小于0
XMJSHJ BigDecimal 15,2 项目价税合计 价税合计必须小于0
SL BigDecimal 6,6 税率 6 位小数,例 17%为 0.17
SE BigDecimal 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
		        }
		    ]
}]

# 删除开票失败申请

对于开票失败数据进行删除调用接口删除,以下场景无法删除:

  1. 状态不是开票失败状态;
  2. 税盘已经开票成功但是生成pdf文件失败(可以联系平台人员进行修复)

# URL

POST application/x-www-form-urlencoded; charset=UTF-8

http://{HOST}:{PORT}/output-tax/api/invoiceApply/deleteInvoiceFailData

# 请求头参数

参数 类型 是否必填 描述 备注
sign String 请求签名信息 使用JWT生成token

签名参考开票签名功能

# 请求体

fpqqlsh=22902929292

# 请求体说明

参数 说明 类型 长度 必输
fpqqlsh 发票请求流水号 String 20

# 返回值

{
    "code" : "0000",
    "msg" : "操作成功"
}

# 返回值说明

参数 类型 描述 说明
code String 状态编码
msg String 提示信息

# 重发邮件

# URL

POST application/x-www-form-urlencoded; charset=UTF-8

http://{HOST}:{PORT}/output-tax/api/invoiceApply/callBackByEmail

# 请求头参数

参数 类型 是否必填 描述 备注
sign String 请求签名信息 使用JWT生成token

签名参考开票签名功能

# 请求体

fpqqlsh=22902929292&email=17090028043@163.com

# 请求体说明

参数 说明 类型 长度 必输
fpqqlsh 发票请求流水号 String 20
email 邮件地址 String 100 否,为空发送到开票申请地址

# 返回值

{
    "code" : "0000",
    "msg" : "邮件发送成功"
}

# 返回值说明

参数 类型 描述 说明
code String 状态编码
msg String 提示信息

# 返回状态码说明

状态码 状态说明
0000 yfpdm:%syfphm:%s的蓝票已申请上传红字申请表。如状态长时间显示未上传,请检查开票助手及税盘是否连接正常
1001 yfpdm:%syfphm:%s的蓝票不存在于税务云系统中,请检查
1002 yfpdm:%syfphm:%s的蓝票不可整体红冲,请检查该蓝票是否已作废,或已开具过红票
1003 yfpdm:%syfphm:%s的蓝票已通过该接口进行红冲,且红冲成功,请检查
1008 yfpdm:%syfphm:%s的蓝票已通过其他途径红冲或部分红冲,不能调用专票整体红冲接口,请检查
1011 yfpdm:%syfphm:%s的红冲专票正在开票中,请稍后查看。如长时间未响应,请检查开票助手及税盘是否连接正常
1013 yfpdm:%syfphm:%s的红字申请表已上传成功,且开票成功
2001 未找到相应的税控组织
2003 销售方纳税人识别号与税控组织对应的纳税人识别号不匹配
2007 缺少发票明细数据,请检查
2010 发票金额超过最大限额
3011 电子签名被篡改
3013 购买方纳税人识别号为空
3019 发票已经作废
5001 税盘不在线
8001 签名失败
8002 非法的api调用
8004 appid为空
8005 无法获取appid对应的key
8008 验签失败,签名数据缺少requestdatas
8009 验签失败,签名数据被窜改
9999 未知错误,按照msg提示处理

# 附录

# 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/ofd文件流base64",
    "shareurl": "http://192.168.52.101/invoiceent-web/s/ab799b5413221cc19088a465d62f8c4f?tenantId=ab2c",
    "sharecode": "E3N2",
	"fileType": "ofd"
}

# 回调服务参数

参数 类型 描述 说明
code String 开票信息码 0000代表开票成功
msg String 开票失败信息
fpqqlsh String 发票请求流水号
fileType String 文件类型 增值税电子专用发票、电子发票(增值税专用发票)为ofd格式;其他为pdf格式;
pdf String pdf文件 使用Base64解码为二进制流,可以保存为pdf或ofd文件
shareurl String 发票提取url
sharecode String 发票提取码 与提取url配合使用
data String 发票详细信息 回调服务发票详细信息

# 回调服务发票详细信息

  • 发票头数据
参数 类型 描述 说明
bmbBbh String 商品编码表版本
fpjz String 发票介质 0.电票 2.纸票
fplx String 发票类型
fpzt String 发票状态
kplx String 开票类型 0.蓝票 1.红票
lylx String 来源类型
lyid String 存储ID 存储拆分前发票的发票请求流水号
zdrq String 制单日期
zsfs String 征税方式:0.征税 2.差额
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 BigDecimal 合计金额
hjse BigDecimal 合计税额
jshj BigDecimal 价税合计
kpr String 开票人
fhr String 复核人
skr String 收款人
bz String 备注
zfbz String 作废标志 Y:是;N否
bred String 是否红冲 Y:是;N否
zdybz String 自定义备注
items Object 发票明细 发票明细数据
  • 发票明细数据
参数 类型 描述 说明
xmmc String 项目名称
ggxh String 规格型号
spbm String 商品编码
xmhsdj String 项目含税单价
yhzcbs String 销售优惠标识;0:不使用,1:使用
zxbm 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);
?>

Last Updated: 3/14/2024, 9:01:01 PM