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