
电子发票/数电发票 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://:test123456localhost: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 Promisestring((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 srcbase64Uri / } 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 Promisevoid((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();