# 扫码开票

# 请求二维码信息

说明

请求信息包含“购买方名称”时,请求发送后会自动开票并返回二维码信息,消费者扫描二维码后直接跳转到发票提取界面;

请求信息如果不包含“购买方名称”,请求发送后只是返回二维码信息,消费者扫描二维码后直接跳转到待开票界面,消费者手工录入购买方信息后开票。

# 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%税率时需要填写税率说明
EMAIL 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格式
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);
        }
    }
}
Last Updated: 11/23/2023, 10:04:29 AM