电子发票/数电发票 Java SDK | 开票、验真、红冲一站式集成|云开票,开票系统

2026-06-14
电子发票/数电发票 Java SDK | 开票、验真、红冲一站式集成|云开票,开票系统
电子发票/数电发票 Java SDK | 开票、验真、红冲一站式集成 电子发票/数电发票 Java SDK | 开票、验真、红冲一站式集成

电子发票/数电发票 Java SDK | 开票、验真、红冲一站式集成

Maven Central
License
Java 17+

发票 Java SDK 专为电子发票、数电发票(全电发票)场景设计,支持开票、红冲、版式文件下载等核心功能,快速对接税务平台API。

关键词: 电子发票SDK,数电票Java,开票接口,发票api,发票开具,发票红冲,全电发票集成


📖 核心功能

基础认证

  • 获取授权 - 快速接入税务平台身份认证
  • 人脸二维码登录 - 支持数电发票平台扫码登录
  • 认证状态查询 - 实时获取纳税人身份状态

发票开具

  • 🎫 数电蓝票开具 - 支持增值税普通/专用电子发票
  • 📄 版式文件下载 - 自动获取销项发票PDF/OFD/XML文件

发票红冲

  • 🔍 红冲前蓝票查询 - 精确检索待红冲的电子发票
  • 🛑 红字信息表申请 - 生成红冲凭证
  • 🔄 负数发票开具 - 自动化红冲流程

🚀 快速安装

Maven

<dependency>
      <groupId>io.github.fapiaoapi</groupId>
      <artifactId>invoice</artifactId>
      <version>1.0.26</version>
  </dependency>

Gradle

implementation 'io.github.fapiaoapi:invoice:1.0.26'

📦 查看Maven Central最新版本

本sdk仅支持Java 17+,其他版本请用旧版本

java8-java16开发票demo

java8-java16红冲发票demo

java8-java16发票税额demo


📚 查看完整中文文档 | 💡 更多示例代码


🔍 为什么选择此SDK?

  • 精准覆盖中国数电发票标准 - 严格遵循国家最新接口规范
  • 开箱即用 - 无需处理XML/签名等底层细节,专注业务逻辑
  • 企业级验证 - 已在生产环境处理超100万张电子发票

📊 支持的开票类型

发票类型 状态
数电发票(普通发票) ✅ 支持
数电发票(增值税专用发票) ✅ 支持
数电发票(铁路电子客票) ✅ 支持
数电发票(航空运输电子客票行程单) ✅ 支持
数电票(二手车销售统一发票) ✅ 支持
数电纸质发票(增值税专用发票) ✅ 支持
数电纸质发票(普通发票) ✅ 支持
数电纸质发票(机动车发票) ✅ 支持
数电纸质发票(二手车发票) ✅ 支持

🤝 贡献与支持

🎯 快速开始

package tax.invoice.example;
  
  import com.google.zxing.BarcodeFormat;
  import com.google.zxing.EncodeHintType;
  import com.google.zxing.WriterException;
  import com.google.zxing.client.j2se.MatrixToImageWriter;
  import com.google.zxing.common.BitMatrix;
  import com.google.zxing.qrcode.QRCodeWriter;
  import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
  import redis.clients.jedis.Jedis;
  import tax.invoice.InvoiceClient;
  import tax.invoice.model.ApiResponse;
  import tax.invoice.model.AuthorizationResponse;
  
  import java.io.ByteArrayOutputStream;
  import java.io.PrintStream;
  import java.time.LocalDateTime;
  import java.time.format.DateTimeFormatter;
  import java.util.*;
  import java.util.concurrent.*;
  
  import static java.lang.Thread.sleep;
  
  public class BasicExample {
  
      public static String appKey = "";
      public static String appSecret = "";
  
      public static String nsrsbh = "";// 统一社会信用代码
      public static String title = "";// 名称(营业执照)
      public static String username = "";// 手机号码(电子税务局)
      public static String password = "";// 个人用户密码(电子税务局)
      public static String type = "6";// 6 基础 7标准
      public static String xhdwdzdh = "重庆市渝北区龙溪街道丽园路2号XXXX 1325580XXXX"; // 地址和电话 空格隔开
      public static String xhdwyhzh = "工商银行XXXX 15451211XXXX";// 开户行和银行账号 空格隔开
  
      public static String token = "";
      public static boolean debug = true; // 是否打印日志
  
      // 创建客户端
      public static InvoiceClient client = new InvoiceClient(appKey, appSecret,debug);
  
      // redis
      public static Jedis redisClient = new Jedis("127.0.0.1", 6379);
  
      public static void main(String[] args) {
          // 配置信息
          try {
              // 显式设置 System.out 的编码为 UTF-8
              System.setOut(new PrintStream(System.out, true, "UTF-8"));
  
              redisClient.auth("test123456");
  
              System.out.println("java " + System.getProperty("java.version"));
              ExecutorService executor = Executors.newSingleThreadExecutor();
              Future<String> future = executor.submit(() -> new Scanner(System.in).nextLine());
  
              /*
               *
               * 一 获取token
               */
              getToken(false);
  
              // 二 开具蓝票
              /*
               * 前端模拟数电发票/电子发票开具 (蓝字发票)
               * @see https://fa-piao.com/fapiao.html?source=github
               *
               */
  
              ApiResponse<Map<String, Object>> invoiceResponse = blueTicket();
              switch (invoiceResponse.getCode()) {
                  case 200:
                      Map<String, Object> invoiceData = invoiceResponse.getData();
                      // 三 下载发票
                      downloadPdfOfdXml(invoiceData.get("Fphm").toString(),invoiceData.get("Kprq").toString());
                      break;
                  case 420:
                      System.out.println("420 登录(短信认证)");
                      /*
                       * 前端模拟短信认证弹窗
                       *
                       * @see https://fa-piao.com/fapiao.html?action=sms&source=github
                       */
                      // 1. 发短信验证码
                      /*
                       * @see https://fa-piao.com/doc.html#api2?source=github
                       */
                      ApiResponse<Map<String, Object>> loginResponse = client.loginDppt(nsrsbh, username, password,"");
                      if (loginResponse.getCode() == 200) {
                          System.out.println("请输入验证码");
                          try {
                              System.out.print("300秒内("
                                      + LocalDateTime.now().plusSeconds(300)
                                      .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
                                      + "前)输入内容: ");
                              String smsCode = future.get(300, TimeUnit.SECONDS);
  
                              System.out.println("🎉 成功获取输入: " + smsCode + " \n" + "当前时间: " + LocalDateTime.now()
                                      .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                              // 2. 输入验证码
                              /*
                               * @see https://fa-piao.com/doc.html#api2?source=github
                               */
                              ApiResponse<Map<String, Object>> loginResponse2 = client.loginDppt(nsrsbh, username,
                                      password, smsCode);
                              if (loginResponse2.getCode() == 200) {
                                  System.out.println("验证成功");
                                  System.out.println("请再次调用blueTicket");
                                  ApiResponse<Map<String, Object>> invoiceResponse2 = blueTicket();
                                  if(invoiceResponse2.getCode() == 200) {
                                      downloadPdfOfdXml(invoiceResponse2.getData().get("Fphm").toString(),invoiceResponse2.getData().get("Kprq").toString());
                                  }
                              } else {
                                  System.out.println(loginResponse2.getCode() + "验证失败: " + loginResponse2.getMsg());
                              }
                          } catch (TimeoutException | InterruptedException | ExecutionException e) {
                              System.out.println("\n短信认证输入超时!");
                              future.cancel(true);
                          }
                          executor.shutdown();
                      } else {
                          System.out.println(loginResponse.getCode() + "发短信验证码失败: " + loginResponse.getMsg());
                      }
                      break;
                  case 430:
                      System.out.println("430 人脸认证");
                      /*
                       * 前端模拟人脸认证弹窗
                       *
                       * @see https://fa-piao.com/fapiao.html?action=face&source=github
                       */
                      // 1. 获取人脸二维码
                      /*
                       * @see https://fa-piao.com/doc.html#api3?source=github
                       */
                      ApiResponse<Map<String, Object>> qrCodeResponse = client.getFaceImg(nsrsbh, username, "1");
                      Map<String, Object> qrData = qrCodeResponse.getData();
                      if (qrData == null) {
                          System.out.println("人脸二维码返回为空: " + qrCodeResponse.getMsg());
                          break;
                      }
                      Object ewmlyObj = qrData.get("ewmly");
                      String ewmly = ewmlyObj == null ? "" : ewmlyObj.toString();
                      System.out.println("swj".equals(ewmly) ? "请使用电子税务局app扫码" : "个人所得税app扫码");
                      Object ewmObj = qrData.get("ewm");
                      if (ewmObj != null && ewmObj.toString().length() < 500) {
                          // 字符串转二维码图片base64
                              /*
                               <dependency>
                               <groupId>com.google.zxing</groupId>
                               <artifactId>core</artifactId>
                               <version>3.5.3</version>
                               </dependency>
                               <dependency>
                               <groupId>com.google.zxing</groupId>
                               <artifactId>javase</artifactId>
                               <version>3.5.3</version>
                               </dependency>
                               */
                          // String base64 = toBase64((String) ewmObj,300);
                          // qrData.put("ewm", base64);
                          // System.out.println("data:image/png;base64," + base64);
                          // String base64Uri = "data:image/png;base64," + base64;
                          // 前端使用示例: <img src="base64Uri" />
  
                      }
                      //字符串转二维码图片 命令行终端打印
                      stringToQrcode((String) ewmObj);
                      System.out.println("成功做完人脸认证,请输入数字 1");
                      try {
                          System.out.print("300秒内(" + LocalDateTime.now().plusSeconds(300)
                                  .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "前)输入内容: ");
                          String inputNum = future.get(300, TimeUnit.SECONDS);
                          System.out.println("🎉 成功获取输入: " + inputNum + " \n" + "当前时间: "
                                  + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                          // 2. 认证完成后获取人脸二维码认证状态
                          /*
                           * @see https://fa-piao.com/doc.html#api4?source=github
                           */
                          String rzid = qrCodeResponse.getData().get("rzid").toString();
                          ApiResponse<Map<String, Object>> faceStatusResponse = client.getFaceState(nsrsbh, rzid,username, "1");
                          if (faceStatusResponse.getData() != null) {
                              String slzt = faceStatusResponse.getData().get("slzt").toString();
                              if ("2".equals(slzt)) {
                                  System.out.println("认证状态: 成功");
                                  System.out.println("请再次调用blueTicket");
                                  ApiResponse<Map<String, Object>> invoiceResponse3 = blueTicket();
                                  if(invoiceResponse3.getCode() == 200) {
                                      downloadPdfOfdXml(invoiceResponse3.getData().get("Fphm").toString(),invoiceResponse3.getData().get("Kprq").toString());
                                  }
                              } else {
                                  System.out.println("认证状态: " + ("1".equals(slzt) ? "未认证" : "二维码过期"));
                              }
                          }
                      } catch (TimeoutException | InterruptedException | ExecutionException e) {
                          System.out.println("\n人脸认证输入超时!");
                          future.cancel(true);
                      }
                      executor.shutdown();
                      break;
                  case 401:
                      // token过期 重新获取并缓存token
                      System.out.println(invoiceResponse.getCode() + "授权失败:" + invoiceResponse.getMsg());
                      System.out.println("401  token过期 重新获取并缓存token");
                      sleep(1000);
                      getToken(true);
                      System.out.println("再调用blueTicket");
                      ApiResponse<Map<String, Object>> invoiceResponse4 = blueTicket();
                      if(invoiceResponse4.getCode() == 200) {
                          downloadPdfOfdXml(invoiceResponse4.getData().get("Fphm").toString(),invoiceResponse4.getData().get("Kprq").toString());
                      }
                      break;
                  default:
                      System.out.println(invoiceResponse.getCode() + "异常" + invoiceResponse.getMsg());
                      break;
              }
          } catch (Exception e) {
              System.out.println("系统错误: " + e.getMessage());
              e.printStackTrace();
          }
      }
  
      // 获取token
      public static void getToken(Boolean ForceUpdate) throws Exception {
          /*
           * <dependency>
           * <groupId>redis.clients</groupId>
           * <artifactId>jedis</artifactId>
           * <version>3.9.0</version>
           * </dependency>
           */
  
          String redisKey = nsrsbh + "@" + username + "@TOKEN";
          if (ForceUpdate) {
              /*
               * 获取授权Token文档
               *
               * @see https://fa-piao.com/doc.html#api1?source=github
               */
              ApiResponse<AuthorizationResponse> authResponse = client.getAuthorization(nsrsbh, type);
  //             ApiResponse<AuthorizationResponse> authResponse = client.getAuthorization(nsrsbh,type,username,password);
              if (authResponse.isSuccess()) {
                  token = authResponse.getData().getToken();
                  redisClient.setex(redisKey, 3600 * 24 * 30, token); // 设置30天过期
                  client.setAuthorization(token);
              }
          } else {
              // 从Redis获取Token
              String token = redisClient.get(redisKey);
              if (token == null || token.isEmpty()) {
                  ApiResponse<AuthorizationResponse> authResponse = client.getAuthorization(nsrsbh, type);
  //             ApiResponse<AuthorizationResponse> authResponse = client.getAuthorization(nsrsbh,type,username,password);
                  if (authResponse.isSuccess()) {
                      token = authResponse.getData().getToken();
                      redisClient.setex(redisKey, 3600 * 24 * 30, token); // 设置30天过期
                      client.setAuthorization(token);
                  }
              }else{
                  client.setAuthorization(token);
              }
          }
  
      }
  
      // 蓝票
      public static ApiResponse<Map<String, Object>> blueTicket() throws Exception {
          /*
           *
           * 开票税额计算说明demo
           *
           * @see
           * https://github.com/fapiaoapi/invoice-sdk-java/blob/master/examples/TaxExample
           * .java
           */
          Map<String, Object> invoiceParams = new LinkedHashMap<>();
          invoiceParams.put("fplxdm", "82");
          invoiceParams.put("fpqqlsh", appKey + System.currentTimeMillis());
          invoiceParams.put("ghdwmc", "张四");
          // invoiceParams.put("ghdwsbh", "914208XXXXXXX");
          invoiceParams.put("hjje", 9.9);
          invoiceParams.put("hjse", 0.1);
          invoiceParams.put("jshj", 10);
          invoiceParams.put("kplx", 0);
          invoiceParams.put("username", username);
          invoiceParams.put("xhdwdzdh", xhdwdzdh);
          invoiceParams.put("xhdwmc", title);
          invoiceParams.put("xhdwsbh", nsrsbh);
          invoiceParams.put("xhdwyhzh", xhdwyhzh);
          invoiceParams.put("zsfs", 0);
          invoiceParams.put("fyxm[0][fphxz]", 0);
          invoiceParams.put("fyxm[0][spmc]", "*玩具*益智玩具");
          // invoiceParams.put("fyxm[0][ggxh]", "");
          // invoiceParams.put("fyxm[0][dw]", "个");
          // invoiceParams.put("fyxm[0][spsl]", 1);
          // invoiceParams.put("fyxm[0][dj]", 10);
          invoiceParams.put("fyxm[0][je]", 10);
          invoiceParams.put("fyxm[0][sl]", 0.01);
          invoiceParams.put("fyxm[0][se]", 0.1);
          invoiceParams.put("fyxm[0][hsbz]", 1);
          invoiceParams.put("fyxm[0][spbm]", "1060408990000000000");
          /*
           * 开具数电发票文档
           * @see https://fa-piao.com/doc.html#api6?source=github
           *
           */
          return client.blueTicket(invoiceParams);
      }
      // 发票下载
      public static void downloadPdfOfdXml(String fphm,String kprq) throws Exception {
          System.out.println("发票号码: " + fphm);
          System.out.println("开票日期: " + kprq);
          /*
           * 获取销项数电版式文件
           *
           * @see https://fa-piao.com/doc.html#api7?source=github
           *
           */
          Map<String, Object> pdfParams = new HashMap<>();
          pdfParams.put("downflag", "4");
          pdfParams.put("nsrsbh", nsrsbh);
          pdfParams.put("username", username);
          pdfParams.put("fphm", fphm);
          pdfParams.put("Kprq", kprq);
  
          ApiResponse<Map<String, Object>> pdfResponse = client.getPdfOfdXml(pdfParams);
          if (pdfResponse.isSuccess()) {
              System.out.println("发票下载成功");
              System.out.println(pdfResponse.getData());
          }
      }
  
      // 生成二维码图片base64
      public static String toBase64(String text, int size) {
          try {
              BitMatrix matrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, size, size);
              ByteArrayOutputStream out = new ByteArrayOutputStream();
              MatrixToImageWriter.writeToStream(matrix, "PNG", out);
              return Base64.getEncoder().encodeToString(out.toByteArray());
          } catch (Exception e) {
              throw new RuntimeException("生成二维码失败", e);
          }
      }
  
      // 字符串转二维码 在命令行输出
      public static void stringToQrcode(String content) {
          try {
              ErrorCorrectionLevel ecLevel = content.length() > 50 ? ErrorCorrectionLevel.L : ErrorCorrectionLevel.M;
              Map<EncodeHintType, Object> hints = new HashMap<>();
              hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
              hints.put(EncodeHintType.ERROR_CORRECTION, ecLevel);
              hints.put(EncodeHintType.MARGIN, 0);
  
              BitMatrix matrix = new QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, 1, 1, hints);
              int size = matrix.getWidth();
              int maxModules = 52;
              int scale = Math.max(1, (int) Math.ceil((double) size / maxModules));
              int renderedSize = (int) Math.ceil((double) size / scale);
  
              System.out.println("请扫描下方二维码 (内容长度: " + content.length() + ", 原尺寸: " + size + "x" + size + ", 输出尺寸: " + renderedSize + "x" + renderedSize + ")");
              renderQrCompact(matrix, scale);
              System.out.println("扫描结束");
          } catch (WriterException e) {
              e.printStackTrace();
          }
      }
  
      public static void renderQrCompact(BitMatrix matrix, int scale) {
          int size = matrix.getWidth();
          int colIndex;
          for (int y = 0; y < size; y += scale * 2) {
              StringBuilder line = new StringBuilder();
              colIndex = 0;
              for (int x = 0; x < size; x += scale) {
                  boolean topBlack = hasBlackInBlock(matrix, x, y, scale);
                  boolean bottomBlack = hasBlackInBlock(matrix, x, y + scale, scale);
                  boolean useNarrowCell = (colIndex + 1) % 5 == 0;
                  if (topBlack && bottomBlack) {
                      line.append(useNarrowCell ? "█" : "██");
                  } else if (topBlack) {
                      line.append(useNarrowCell ? "▀" : "▀▀");
                  } else if (bottomBlack) {
                      line.append(useNarrowCell ? "▄" : "▄▄");
                  } else {
                      line.append(useNarrowCell ? " " : "  ");
                  }
                  colIndex++;
              }
              System.out.println(line);
          }
      }
  
      public static boolean hasBlackInBlock(BitMatrix matrix, int startX, int startY, int scale){
          int width = matrix.getWidth();
          if (startX >= width || startY >= width) {
              return false;
          }
          int endX = Math.min(startX + scale, width);
          int endY = Math.min(startY + scale, width);
          for (int y = startY; y < endY; y++) {
              for (int x = startX; x < endX; x++) {
                  if (matrix.get(x, y)) {
                      return true;
                  }
              }
          }
          return false;
      }
  
  }

发票红冲demo
发票税额计算demo
扫码登录demo
先验证后开发票命令行demo