# 扫码开票
# 请求二维码信息
说明
请求信息包含“购买方名称”时,请求发送后会自动开票并返回二维码信息,消费者扫描二维码后直接跳转到发票提取界面;
请求信息如果不包含“购买方名称”,请求发送后只是返回二维码信息,消费者扫描二维码后直接跳转到待开票界面,消费者手工录入购买方信息后开票。
# URL
POST
Content-Type: application/json
http://{HOST}:{PORT}/output-tax/api/invoiceApply/insertForQRInvoice
# 请求体
{
"XSF_NSRSBH":"销售方纳税人识别号",
"FPQQLSH":"发票请求流水号(发票提取码)",
"JSHJ":"价税合计",
"LYID":"来源ID,订单编号",
"ORGCODE":"门店标识",
"RQSJ":"订单时间",
"SHMC":"商户名称",
"BZ":"备注",
"FPLX":"发票类型",(不填默认是1:电子普通发票)
"EMAIL":"收票邮箱",
"MOBILE":"手机号",
"URL":"开票成功后的回调url",
"GMF_MC":"购买方名称",
"GMF_NSRSBH":"购买方纳税人识别号",
"GMF_DZDH":"购买方地址电话",
"GMF_YHZH":"购买方银行账号",
"items":[
{
"FPHXZ":"发票行性质",
"XMMC":"项目名称",
"GGXH":"规格型号",
"DW":"单位",
"XMSL":"项目数量",
"XMDJ":"项目单价",
"XMJE":"项目金额",
"XMJSHJ":"项目价税合计",
"SL":"税率",
"SE":"税率",
"HH":"行号",
"ZKHHH":"折扣行行号",
"SPBM":"商品税收分类编码"
}
]
}
# 请求体说明
# 发票头
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
XSF_NSRSBH | String | 20 | 是 | 销售方纳税人识别号 | ||
FPQQLSH | String | 20 | 否 | 发票请求流水号(发票提取码),须保证唯一性,不允许重复,如果此字段为空,平台会按照默认规则生成并返回,最长20位,最短1位,可以包含字母数字 | ||
JSHJ | BigDecimal | 15,2 | 是 | 价税合计 | 两位小数 | |
BZ | String | 200 | 否 | 备注 | ||
FPLX | String | 2 | 否 | 支持蓝字发票扫码开票 发票类型不填默认是1 1:增值税电子普通发票; 2:增值税电子专用发票; 3:增值税普通发票; 4:增值税专用发票 ; 31:数电专用发票; 32:数电普通发票; | ||
LYID | String | 100 | 否 | 请求来源唯一标识 | ||
ORGCODE | String | 100 | 是 | 电子发票平台唯一标识,从电子发票平台获取。 | ||
RQSJ | String | 100 | 是 | 日期时间 | ||
SHMC | String | 100 | 是 | 商户名称 | ||
SLSM | String | 1 | 否 | 税率说明 4:政策出台前发生纳税义务 5:前期已开具相应征收率发票,发生销售折让、中止或者退回等情形需要开具红字发票,或者开票有误需要重新开具 6:因为实际经营业务需要,放弃享受免征增值税政策。 | 当小规模纳税人开具3%或者1%税率时需要填写税率说明 | |
String | 100 | 否 | 收票邮箱 | |||
MOBILE | String | 20 | 否 | 手机号 | ||
URL | String | 200 | 否 | 回调url | 开票申请调用方配置回调url,当电子发票开具成功后,调用此服务,POST电子发票相关信息。信息包含:1、pdf版式文件2、发票数据、3、发票下载链接 | |
GMF_MC | String | 100 | 否 | 购买方名称 | ||
GMF_NSRSBH | String | 20 | 否 | 购买方纳税人识别号 | ||
GMF_DZDH | String | 100 | 否 | 购买方地址电话 | ||
GMF_YHZH | String | 100 | 否 | 购买方银行账号 |
# 发票明细
参数 | 类型 | 长度 | 是否必填 | 默认值 | 描述 | 说明 |
---|---|---|---|---|---|---|
FPHXZ | Integer | 1 | 否 | 0 | 发票行性质 | 如果本行为折扣行或者被折扣行则为必输项,0-正常行,1-折扣行,2-被折扣行 |
XMMC | String | 90 | 是 | 项目名称 | ||
GGXH | String | 40 | 否 | 规格型号 | ||
DW | String | 20 | 否 | 单位 | ||
XMSL | BigDecimal | 15,6 | 否 | 项目数量 | ||
XMDJ | BigDecimal | 15,6 | 否 | 项目单价 | 项目单价为空时,根据项目价税合计反算。不为空时不进行计算 | |
XMJE | BigDecimal | 15,2 | 否 | 项目金额 | 项目金额为空,根据项目价税合计反算。不为空时不进行计算 | |
XMJSHJ | BigDecimal | 15,2 | 是 | 项目价税合计 | ||
SL | BigDecimal | 6,6 | 是 | 税率 | 6 位小数,例 17%为 0.17 | |
SE | BigDecimal | 15,2 | 否 | 税额 | 税额为空,根据价税合计反算。不为空时不进行计算。 | |
HH | string | 60 | 否 | 行号 | 如果存在折扣行此字段必输 | |
ZKHHH | string | 60 | 否 | 折扣行行号 | 如果本行为被折扣行即FPHXZ值为2时此字段为必输,值为本行对应的折扣行行号,折扣行的FPHXZ值为1 | |
SPBM | String | 19 | 是 | 商品税收分类编码 | 依据税控设备,此商品应该属于什么分类,此字段就填什么字段 例如:餐饮费属于餐饮服务分类,分类编码为:3070401000000000000 | |
LSLBS | String | 1 | 否 | 零税率标识 | 空:非零利率,0:出口退税,1:免税,2:不征收,3普通零税率 |
# 返回值
{
"code": "0000",
"msg": "操作成功",
"datas": {
"invoicecode": "798797825254998016",
"qrcode": "http://www.yesfp.com:80/mobileinvoice/index.html?lsh=Yk7IzFyYfngwjAWdarGXTr5oa+HA+Iv9&corp=24d0dcd1-7ec5-46a6-843d-224ccf574a0d"
}
}
# 返回值说明
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 状态码 | 0000-操作成功;详见状态码说明 |
msg | String | 信息说明 | |
invoicecode | String | 发票提取码 | |
qrcode | String | 二维码 |
# 附录
# 回调数据样例
{
"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": "base64编码字符串",
"shareurl": "http://192.168.52.101/invoiceent-web/s/ab799b5413221cc19088a465d62f8c4f",
"sharecode": "E3N2"
}
# 回调服务参数
参数 | 类型 | 描述 | 说明 |
---|---|---|---|
code | String | 开票信息码 | 0000代表开票成功 |
msg | String | 开票失败信息 | |
fpqqlsh | String | 发票请求流水号 | |
fileType | String | 文件类型 | ofd代表ofd格式;pdf代表pdf格式 |
String | pdf文件 | 使用Base64解码为二进制流,可以保存为pdf或ofd文件 | |
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 | BigDecimal | 合计金额 | |
hjse | BigDecimal | 合计税额 | |
jshj | BigDecimal | 价税合计 | |
kpr | String | 开票人 | |
fhr | String | 复核人 | |
skr | String | 收款人 | |
bz | 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
# Maven配置文件依赖
依赖配置如下,其中bcprov-jdk15on和使用的签名私钥格式有关,使用PEM格式的签名私钥时需要使用此引用。
<!-- 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>
<!-- bcprov-jdk15on,PEM格式私钥证书读取工具类 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.52</version>
</dependency>
# API调用代码
package com.yonyou.einvoice.sdk.test;
import com.google.gson.GsonBuilder;
import com.yonyou.einvoice.einvoiceApply.JwtParamBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.compression.CompressionCodecs;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.bouncycastle.util.io.pem.PemReader;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @date 2018/5/25
* <p>
* 扫码开票接口测试代码,适用于JDK1.6及更高版本,jdk1.6版本需要对签名方法稍做修改,修改方法在签名方法内已经写明
* 请求参数的注意事项也在参数构建的过程中写明,请详细阅读样例代码。
*/
public class InsertForQRInvoice {
//测试环境有测试appid和证书,正式环境有正式appid和证书,请务必对应使用
//测试环境appid就用这个,正式环境需要替换成正式的
private static String APPID = "commontesterCA";
//这个是测试环境的域名,正式环境为https://fapiao.yonyoucloud.com
private static String DOMAIN = "https://yesfp.yonyoucloud.com";
private static String URL = DOMAIN + "/output-tax/api/invoiceApply/insertForQRInvoice?appid=" + APPID;
//pro22.pfx为测试环境通讯证书,正式环境需要替换成正式的
private static String KEYPATH = "src/main/resources/certificate/pro22.pfx";
//证书密码
private static String PASSWORD = "password";
public static void main(String[] args) {
try {
new InsertForQRInvoice().callQRInvoiceApply();
} catch (Exception e) {
e.printStackTrace();
}
}
private static CloseableHttpClient createSSLClientDefault() {
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(java.security.cert.X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
return HttpClients.custom().setSSLSocketFactory(sslsf).build();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (KeyStoreException e) {
e.printStackTrace();
}
return HttpClients.createDefault();
}
public void callQRInvoiceApply() throws Exception {
// 提供两种构建HttpClient实例的方法,如果使用被注释掉的方法构建实例报证书不被信任的错误,那么请使用未被注释的构建方法
// HttpClient httpClient = HttpClients.custom().build();
HttpClient httpClient = createSSLClientDefault(); //信任所有https证书
HttpPost httpPost = new HttpPost(URL);
// 构造POST请求体
String body = this.buildRequestDatasQR();
// 签名
String sign = this.sign(body);
httpPost.addHeader("sign", sign);
httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
StringEntity se = new StringEntity(body.toString(), "UTF-8");
se.setContentType("text/json");
se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
httpPost.setEntity(se);
// 发送http post请求,并得到响应结果
HttpResponse response = httpClient.execute(httpPost);
String result = "";
if (response != null) {
HttpEntity resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "UTF-8");
System.out.println(result);
}
}
}
/**
* 签名
*
* @param paramsMap 表单参数
* @return 签名值
* @throws Exception
*/
private String sign(String paramsMap) throws Exception {
// 读取CA证书与PEM格式证书需要根据实际证书使用情况而定,目前这两种都支持
PrivateKey privateKey = loadPrivateKeyOfCA();
// PrivateKey privateKey = loadPrivateKeyOfPem();
Map<String, Object> claims =
JwtParamBuilder.build().setSubject("tester").setIssuer("einvoice").setAudience("einvoice")
.addJwtId().addIssuedAt().setExpirySeconds(300).setNotBeforeSeconds(300).getClaims();
// 需要将表单参数requestdatas的数据进行md5加密,然后放到签名数据的requestdatas中。
// 此签名数据必须存在,否则在验证签名时会不通过。
claims.put("requestdatas", getMD5(paramsMap));
// 使用jdk1.6版本时,删除下面代码的中.compressWith(CompressionCodecs.DEFLATE)
String compactJws = Jwts.builder().signWith(SignatureAlgorithm.RS512, privateKey)
.setClaims(claims).compressWith(CompressionCodecs.DEFLATE).compact();
return compactJws;
}
/**
* 计算MD5
*
* @param str
* @return
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
private String getMD5(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
byte[] buf = null;
buf = str.getBytes("utf-8");
MessageDigest md5 = null;
md5 = MessageDigest.getInstance("MD5");
md5.update(buf);
byte[] tmp = md5.digest();
StringBuilder sb = new StringBuilder();
for (byte b : tmp) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
/**
* 读取证书私钥
*
* @return
* @throws UnrecoverableKeyException
* @throws KeyStoreException
* @throws NoSuchAlgorithmException
* @throws CertificateException
* @throws IOException
*/
protected PrivateKey loadPrivateKeyOfCA() throws UnrecoverableKeyException, KeyStoreException,
NoSuchAlgorithmException, CertificateException, IOException {
FileInputStream in = new FileInputStream(KEYPATH);
KeyStore ks = KeyStore.getInstance("pkcs12");
ks.load(in, PASSWORD.toCharArray());
String alias = ks.aliases().nextElement();
PrivateKey caprk = (PrivateKey) ks.getKey(alias, PASSWORD.toCharArray());
return caprk;
}
/**
* 读取PEM编码格式
*
* @return
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws InvalidKeySpecException
*/
protected PrivateKey loadPrivateKeyOfPem()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
PemReader reader = new PemReader(new FileReader("D:\\CA\\keystore\\红桔.private"));
byte[] privateKeyBytes = reader.readPemObject().getContent();
reader.close();
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PrivateKey privateKey = kf.generatePrivate(spec);
return privateKey;
}
/**
* 构造requestdatas
*
* @return
*/
private String buildRequestDatasQR() {
Map<String, Object> data = new HashMap<>();
//测试环境请一定要使用测试纳税人识别号
data.put("XSF_NSRSBH", "201609140000001");
//组织编码,测试环境请一定使用测试环境的组织编码
data.put("ORGCODE", "20160914001");
data.put("FPQQLSH", buildFpqqlsh());
data.put("SHMC", "asdasd");
data.put("JSHJ", 117);
data.put("ZDYBZ", "aa");
data.put("items", buildItems());
GsonBuilder builder = new GsonBuilder();
return builder.create().toJson(data);
}
/**
* 获取发票请求流水号
* 长度不超过20位,长度在1到20位的字母和数字组合,不可以重复的,不要包含window系统文件名限制的特殊字符
*
* @return 发票请求流水号
*/
private String buildFpqqlsh() {
return "164291i05l0000sKs";
}
/**
* 构造request发票明细
*
* @return
*/
private List<Object> buildItems() {
List<Object> items = new ArrayList<>();
Map<String, Object> data = new HashMap<>();
data.put("XMMC", "(pp瓶)0.9%氯化钠注射液");
data.put("XMJSHJ", 117);
//税率17%需要写成0.17的格式
data.put("SL", 0.17);
//SPBM字段为商品税收分类编码,不同的商品会有不同的编码,不对应的话会影响报税,需要咨询下公司财务
data.put("SPBM", "3010504020000000000");
items.add(data);
return items;
}
}
#####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://yesfp.yonyoucloud.com";
var url = baseUrl + "/output-tax/api/invoiceApply/insertForQRInvoice?appid=commontesterCA";
var req = (HttpWebRequest) WebRequest.Create(url);
//请求体参数_requestdatas(发票头+发票明细)
var requestdatas = BuildRequestDatas();
//设置消息头
var bs = Encoding.UTF8.GetBytes(requestdatas);
req.Method = "POST";
req.ContentType = "application/json;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(requestdatas);
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 + 30000;
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 BuildRequestDatas()
{
//构造发票头
var oneInvoice = new Dictionary<string, object>
{
//发票请求流水号,长度不超过20位,长度在1到20位的字母和数字组合,不可以重复的,不要包含window系统文件名限制的特殊字符
{"FPQQLSH", "12345678902222"},
//测试环境请一定使用该测试税号
{"XSF_NSRSBH", "201609140000001"},
//测试环境请一定使用该测试组织编码
{"ORGCODE", "20160914001"},
{"GMF_MC", "购买方名称"},
{"JSHJ", 117}
};
var items = new List<Dictionary<string, object>>();
//构造一个发票明细
var oneItem = new Dictionary<string, object>
{
{"XMMC", "项目名称"},
{"XMSL", 1},
{"XMJSHJ", 117},
{"SL", 0.17},
//SPBM字段为商品税收分类编码,不同的商品会有不同的编码,不对应的话会影响报税,需要咨询下公司财务
{"SPBM", "3040502029902000000"}
};
items.Add(oneItem);
oneInvoice.Add("items", items);
return JsonConvert.SerializeObject(oneInvoice);
}
}
}