# 开票申请

# 开蓝票

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

# 发票头

参数 类型 长度 是否必填 默认值 描述 说明
FPQQLSH String 20 发票请求流水号
FPLX String 1 1 发票类型 1:增值税电子普通发票;
2:增值税电子专用发票;
3:增值税普通发票;
4:增值税专用发票 ;
8:增值税电子普通发票(成品油);
9:成品油普通发票(卷式)
10:成品油普通发票;
11:成品油专用发票;
12:增值税普通发票(卷式)
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-农产品收购

# 发票明细

参数 类型 长度 是否必填 默认值 描述 说明
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 开票申请发票信息,包含发票头发票明细
email 邮件推送配置(可选)
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 发票红冲参数
email 邮件推送配置(可选)
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

TIP

此接口只能用来开具蓝票

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

# 请求体参数

# 表单说明

参数 描述 说明
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

TIP

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

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

# 请求体参数

# 表单说明

参数 描述 说明
requestdatas 开票申请发票信息,包含发票头发票明细
email 邮件推送配置(可选)
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 发票请求流水号
pdf 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);
?>

Last Updated: 11/25/2021, 5:12:17 PM