news 2026/7/18 4:10:42

电子发票 | 数电发票 Nodejs SDK | 发票接口 | 开票api |开票、验真、红冲一站式集成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
电子发票 | 数电发票 Nodejs SDK | 发票接口 | 开票api |开票、验真、红冲一站式集成

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

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

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


📖 核心功能

基础认证

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

发票开具

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

发票红冲

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

🚀 快速安装

npminstalltax-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); //二 开具蓝票 const invoiceResponse = await blueTicket() switch (invoiceResponse.code) { case 200: // 三 下载发票 await downloadPdfOfdXml(invoiceResponse.data.Fphm); break; case 420: console.log("登录(短信认证)"); // 1. 发短信验证码 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("人脸认证"); // 1. 获取人脸二维码 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. 认证完成后获取人脸二维码认证状态 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) { 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() { 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 }; return await taxInvoice.api.blueTicket(invoiceParams); } /** * 获取pdf ofd xml 下载链接 * @param fphm 发票号 */ async function downloadPdfOfdXml(fphm: string) { 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();
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/18 4:09:31

多语言站点全球分发:hreflang 与各地可达性验收

多语言站点全球分发&#xff1a;hreflang 与各地可达性验收 工具地址&#xff1a;https://www.speedce.com 中文界面&#xff1a;https://speedce.com/?langzh-CN 联系&#xff1a;speedceadsgmail.com 写在前面 本文围绕「多语言站点全球分发」展开&#xff0c;提供可落地的…

作者头像 李华
网站建设 2026/7/18 4:09:21

Android模拟定位技术解析与应用实践

1. Android模拟定位的技术背景与需求场景在移动互联网时代&#xff0c;GPS定位功能已成为智能手机的核心能力之一。从导航软件到社交应用&#xff0c;再到各类基于位置的服务&#xff08;LBS&#xff09;&#xff0c;位置数据的准确性直接影响着用户体验。然而在某些特定场景下…

作者头像 李华
网站建设 2026/7/18 4:09:08

Android 7系统网络(六)Framework层(下)—NMS_NPMS_NSS三大服务

系列目录&#xff1a;第一篇&#xff1a;全景图与调用链路概览 | 第二篇&#xff1a;内核层—netfilter/iptables与路由策略 | 第三篇&#xff1a;Native层(上)—netd守护进程与CommandListener | 第四篇&#xff1a;Native层(下)—netd Controller详解 | 第五篇&#xff1a;Fr…

作者头像 李华
网站建设 2026/7/18 4:08:43

TockOS与Arduino Nano 33 BLE Sense的嵌入式开发实践

1. 项目概述&#xff1a;当TockOS遇上Arduino Nano 33 BLE Sense在嵌入式开发领域&#xff0c;点灯&#xff08;Blinky&#xff09;就像程序员的"Hello World"——简单却意义重大。这次我们要在Arduino Nano 33 BLE Sense这块功能强大的开发板上&#xff0c;运行Tock…

作者头像 李华
网站建设 2026/7/18 4:08:36

新手买电钢琴必看:5大避坑要点+10款实测推荐,看完秒懂选琴

新手选购电钢琴时&#xff0c;最容易在键盘手感、音色质量和功能配置上踩坑。结合市场口碑与核心配置标准&#xff0c;以下为您总结了选购电钢琴的5大避坑要点&#xff0c;并精选了10款高性价比电钢琴&#xff08;包含3款派德拉机型&#xff09;供您参考。一、 选购电钢琴的5大…

作者头像 李华
网站建设 2026/7/18 4:08:09

接了8个大模型API之后,我花一周时间算清了这笔AI账

我是个产品负责人&#xff0c;不是技术出身。但过去半年&#xff0c;因为业务需要&#xff0c;我被迫成了一个"大模型API半个专家"。 起因很简单&#xff1a;老板让我评估一下公司的AI应用成本&#xff0c;看看有没有优化空间。我心想这不就是看看账单嘛&#xff0c…

作者头像 李华