npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tax-invoice

v1.0.16

Published

发票SDK-提供完整的发票API开票接口

Readme

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

npm version License

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

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


📖 核心功能

基础认证

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

发票开具

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

发票红冲

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

🚀 快速安装

npm install tax-invoice

📦 查看npm最新版本


📚 查看完整中文文档


🔍 为什么选择此SDK?

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

📊 支持的开票类型

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


🤝 贡献与支持

开票

import readline from "readline";
const { TaxInvoice } = require('tax-invoice');
const qrcode = require('qrcode-terminal');

const { createClient } = require('redis');
const QRCode = require('qrcode');

// 配置信息
const appKey = '';
const appSecret = '';

const nsrsbh = ''; // 统一社会信用代码
const title = ''; // 名称(营业执照)
const username = ''; // 手机号码(电子税务局)
const password = ''; // 个人用户密码(电子税务局)
const type = '7'; //6基础 7 标准版
const xhdwdzdh = '重庆市渝北区龙溪街道丽园路2号XXXX 1325580XXXX' //地址和电话 空格隔开
const xhdwyhzh = '工商银行XXXX 15451211XXXX'  // 开户行和银行账号 空格隔开

let token = "";
let debug = false;

// 初始化SDK
const taxInvoice = new TaxInvoice({
  appKey: appKey,
  appSecret: appSecret,
  debug: debug
});
// 初始化redis客户端
const client = createClient({
  url: 'redis://:test123456@localhost:6379'
});


// 主函数
async function main() {
  try {

    //一 获取授权 可从缓存redis中获取Token
    await getToken(false);
    /*
     * 前端模拟数电发票/电子发票开具 (蓝字发票)
     * @link https://fa-piao.com/fapiao.html?source=github
     */

    //二 开具蓝票
    const invoiceResponse = await blueTicket()
    switch (invoiceResponse.code) {
      case 200:
        // 三 下载发票
        await downloadPdfOfdXml(invoiceResponse.data.Fphm);
        break;
      case 420:
        console.log("登录(短信认证)");
        /*
         * 前端模拟短信认证弹窗
         * @link https://fa-piao.com/fapiao.html?action=sms&source=github
         */
        // 1. 发短信验证码
        /*
        * @link https://fa-piao.com/doc.html#api2?source=github
        */
        const loginResponse = await taxInvoice.api.loginDppt({ nsrsbh, username, password, sms: "" });
        if (loginResponse.code === 200) {
          const smsCode = await new Promise<string>((resolve, reject) => {
            const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
            console.log('请在300秒内(' + getFutureTime(300) + '前)输入验证码:');
            const timer = setTimeout(() => {
              rl.close();
              const timeoutError: any = new Error('短信验证码输入超时');
              timeoutError.errorCode = 'TIMEOUT';
              reject(timeoutError);
            }, 300000);
            rl.once('line', (input) => {
              clearTimeout(timer);
              rl.close();
              resolve(input.trim());
            });
          });
          const smsLoginResponse = await taxInvoice.api.loginDppt({ nsrsbh, username, password, sms: smsCode });
          if (smsLoginResponse.code === 200) {
            console.log("短信验证成功");
            console.log("再次调用blueTicket");
            const retryInvoiceResponse = await blueTicket();
            if (retryInvoiceResponse.code === 200) {
              await downloadPdfOfdXml(retryInvoiceResponse.data.Fphm);
            } else {
              const invoiceError: any = new Error('开票失败');
              invoiceError.errorCode = retryInvoiceResponse.code;
              throw invoiceError;
            }
          } else {
            const authError: any = new Error('短信验证失败');
            authError.errorCode = smsLoginResponse.code;
            throw authError;
          }
        }
        break;
      case 430:
        console.log("人脸认证");
        /* 前端模拟人脸认证弹窗
         * @link https://fa-piao.com/fapiao.html?action=face&source=github
         */
        // 1. 获取人脸二维码
        /*
         * @link https://fa-piao.com/doc.html#api3?source=github
         */
        const qrCodeResponse = await taxInvoice.api.getFaceImg({ nsrsbh, username, type: "1" });
        if (qrCodeResponse.data.ewmly === 'swj') {
          console.log("请使用电子税务局app扫码");
        } else if (qrCodeResponse.data.ewmly === 'grsds') {
          console.log("个人所得税app扫码");
        }
        if (qrCodeResponse.data.ewm.length < 500) {
          // //字符串转二维码图片base64   安装 npm install qrcode
          // const base64 = await QRCode.toDataURL(qrCodeResponse.data.ewm);
          // console.log("二维码base64:", base64);
          // qrCodeResponse.data.ewm = base64;
          // // let base64Uri = 'data:image/png;base64,' + base64;
          // // 前端使用示例: <img src="base64Uri" />
        }
        console.log("成功做完人脸认证,请输入数字 1")
        console.log('请在300秒内(' + getFutureTime(300) + '前)输入:');
        qrcode.generate(qrCodeResponse.data.ewm, {
          small: true, // 关键设置:true 会让二维码缩小(使用半高字符)
          margin: 1    // 边距:设为 1 减少空白
        }, (qrCode: string, err: Error | null = null) => {
          console.log(qrCode);
        });
        await new Promise<void>((resolve, reject) => {
          const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
          const timer = setTimeout(() => {
            rl.close();
            const timeoutError: any = new Error('人脸认证等待输入超时');
            timeoutError.errorCode = 'TIMEOUT';
            reject(timeoutError);
          }, 300000);
          rl.once('line', (input) => {
            clearTimeout(timer);
            rl.close();
            console.log(`✅ 收到: ${input}`);
            resolve();
          });
        });
        // 2. 认证完成后获取人脸二维码认证状态
        /*
         * @link https://fa-piao.com/doc.html#api4?source=github
         */
        const rzid = qrCodeResponse.data.rzid;
        const faceStatusResponse = await taxInvoice.api.getFaceState({ nsrsbh, rzid, username, type: "1" });
        if (faceStatusResponse.data) {
          switch (faceStatusResponse.data.slzt) {
            case '1':
              console.log("人脸未认证");
              const faceStatusError: any = new Error('人脸未认证');
              faceStatusError.errorCode = faceStatusResponse.code;
              throw faceStatusError;
            case '2':
              console.log("人脸认证成功");
              const invoiceResponseAfterFace = await blueTicket();
              if (invoiceResponseAfterFace.code === 200) {
                await downloadPdfOfdXml(invoiceResponseAfterFace.data.Fphm);
              } else {
                const authError: any = new Error('人脸认证失败');
                authError.errorCode = invoiceResponseAfterFace.code;
                throw authError;
              }
              break;
            case '3':
              console.log("人脸认证二维码过期-->重新获取人脸二维码");
              const faceStatusError2: any = new Error('人脸认证二维码过期-->重新获取人脸二维码');
              faceStatusError2.errorCode = faceStatusResponse.code;
              throw faceStatusError2;
          }
        }
        break;
      case 401:
        //token过期 重新获取并缓存token
        console.log(`${invoiceResponse.code} 授权失败: ${invoiceResponse.msg}`);
        console.log("重新授权 获取token 缓存到redis");
         await getToken(true);
         console.log("再请求blueTicket");
         const invoiceResponseAfterToken = await blueTicket();
         if (invoiceResponseAfterToken.code === 200) {
            await downloadPdfOfdXml(invoiceResponseAfterToken.data.Fphm);
         } else {
            const invoiceError: any = new Error('开票失败');
            invoiceError.errorCode = invoiceResponseAfterToken.code;
            throw invoiceError;
         }
        break;
      default:
        console.log('其他错误:', `${invoiceResponse.code} ${invoiceResponse.msg}`);
        break;
    }
  } catch (error: unknown) {
    const err = error as any;
    const errorCode = err?.errorCode ?? err?.code ?? 'UNKNOWN';
    const errorMessage =
      err?.message ??
      (typeof error === 'string' ? error : JSON.stringify(error)) ??
      '未知异常';
    console.error(`错误码: ${errorCode}`);
    console.error(`错误信息: ${errorMessage}`);
    if (Array.isArray(err?.errors) && err.errors.length > 0) {
      console.error("根因列表:");
      err.errors.forEach((item: any, index: number) => {
        const rootCode = item?.code ?? item?.errorCode ?? 'UNKNOWN';
        const rootMessage = item?.message ?? String(item);
        console.error(`  ${index + 1}. [${rootCode}] ${rootMessage}`);
      });
    }
    if (err?.stack) {
      console.error("堆栈:");
      console.error(err.stack);
    }
    console.error("=========================================\n");
  }
}

// 获取token 并缓存到redis
async function getToken(forceUpdate = false) {
  await client.connect();
  // 从缓存redis中获取Token 安装npm install redis
  const key = nsrsbh + "@" + username + "@TOKEN";
  if (forceUpdate) {
    /*
    * 获取授权Token文档
    * @link https://fa-piao.com/doc.html#api1?source=github
    */
    const authResult = await taxInvoice.api.getAuthorization(nsrsbh, type);
    // const authResult = await taxInvoice.api.getAuthorization(nsrsbh,type,username,password);
    if (authResult.code === 200) {
      const newToken = authResult.data.token;
      taxInvoice.setToken(newToken);
      await client.set(key, authResult.data.token, { EX: 3600 * 24 * 30 });// 设置过期时间为30天
    } else {
      const authError: any = new Error(`授权失败: ${authResult.msg}`);
      authError.errorCode = authResult.code;
      throw authError;
    }
  } else {
    token = await client.get(key);
    if (token) {
      taxInvoice.setToken(token);
      console.log("Token From Redis");
    } else {
      const authResult = await taxInvoice.api.getAuthorization(nsrsbh, type);
      // const authResult = await taxInvoice.api.getAuthorization(nsrsbh,type,username,password);
      if (authResult.code === 200) {
        const newToken = authResult.data.token;
        taxInvoice.setToken(newToken);
        await client.set(key, authResult.data.token, { EX: 3600 * 24 * 30 });// 设置过期时间为30天
      } else {
        const authError: any = new Error(`授权失败: ${authResult.msg}`);
        authError.errorCode = authResult.code;
        throw authError;
      }
    }
  }
}
// 开票
async function blueTicket() {
      /*
     * 开具数电发票文档
     * @link https://fa-piao.com/doc.html#api6?source=github
     *
     * 税额计算demo
     * @link https://github.com/fapiaoapi/invoice-sdk-nodejs/blob/master/examples/tax_example.ts
     */
  const invoiceParams = {
    username: username,
    fpqqlsh: appKey + Date.now(),// 建议用你的订单号
    fplxdm: '82',
    kplx: '0',
    xhdwsbh: nsrsbh,
    xhdwmc: title,
    xhdwdzdh: xhdwdzdh,
    xhdwyhzh: xhdwyhzh,
    ghdwmc: '个人',
    zsfs: '0',
    fyxm: [
      {
        "fphxz": 0,
        "hsbz": 1,
        "spmc": "*软件维护服务*接口服务费",
        "spbm": "3040201030000000000",
        "je": 100,
        "sl": 0.01,
        "se": 0.99,
        "spsl": 100,
        "dj": 1,
        "dw": "次"
      },
      {
        "fphxz": 0,
        "hsbz": 1,
        "spmc": "*软件维护服务*接口服务费",
        "spbm": "3040201030000000000",
        "je": 100,
        "sl": 0.01,
        "se": 0.99,
        "spsl": 200,
        "dj": 0.5,
        "dw": "次"
      }
    ],
    hjje: 198.02,
    hjse: 1.98,
    jshj: 200
  };

  /*
   * 数电蓝票开具接口 文档
   * @link https://fa-piao.com/doc.html#api6?source=github
   */
  return await taxInvoice.api.blueTicket(invoiceParams);
}

/**
 * 获取pdf ofd xml 下载链接
 * @param fphm 发票号
 */
async function downloadPdfOfdXml(fphm: string) {
  /*
   * 获取销项数电版式文件 文档 PDF/OFD/XML
   * @link https://fa-piao.com/doc.html#api7?source=github
   */
  const pdfResponse = await taxInvoice.api.getPdfOfdXml({
    "nsrsbh": nsrsbh,
    "username": username,
    "fphm": fphm,
    "downflag": "4"
  });
  if (pdfResponse.code === 200) {
    console.log("发票下载结果:", pdfResponse.data);
  }
}

/**
 * 获取指定秒数后的时间字符串 (格式: YYYY-MM-DD HH:mm:ss)
 * @param addSeconds 需要增加的秒数 (例如: 300)
 */
function getFutureTime(addSeconds: number): string {
  const d = new Date();
  d.setSeconds(d.getSeconds() + addSeconds); // 在当前时间基础上增加秒数

  // 辅助函数:补零
  const pad = (n: number) => n.toString().padStart(2, '0');

  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
}

// 执行主函数
main();

发票税额计算demo | 发票红冲demo