电子发票/数电发票 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();