做企业级财务报销和进项税对账 SaaS最容易让独立开发者栽跟头的地方不是多模态 OCR 识别率而是发票防伪与真伪查验。很多新手以为大模型把发票图片里的“发票代码、发票号码、开票日期、金额、税额”提取成 JSON 就万事大吉了结果一上线客户随手上传一张作废票、一张 PS 篡改金额的假票甚至拿手机对着打印废纸拍一张系统如果直接入账到了税务稽查环节就是灾难性的财务合规事故。国家税务总局全国增值税发票查验平台有非常严格的查验要素要求。一张合规的增值税发票查验必须依赖四要素发票代码、发票号码、开票日期、开具金额不含税或校验码后 6 位。大模型在这个链路中绝不仅仅是一个“文字搬运工”而是前置要素清洗机 容错推断器 税务查验报文组装者 查验结果差异比对审计员。今天蔓蔓就来拆解我们 SaaS 内部稳定运行的一整套全国发票真伪查验与多模态比对架构。增值税发票查验的现实挑战与业务链路在现实业务里用户上传的发票往往五花八门有卷式普通发票、电子普通发票、全电发票数电票、增值税专用发票。不同票种在查验时的入参规则完全不同增值税专用发票查验四要素为【发票代码、发票号码、开票日期、开具金额不含税】。普通发票 / 电子普票查验四要素为【发票代码、发票号码、开票日期、校验码后 6 位】。全电发票数电票2024 年后全面普及的数电票不再有传统的发票代码只有 20 位的数电票号码查验要素变为【发票号码、开票日期、开具金额含税/不含税视接口版本】。如果完全依赖人工规则去写正则判断面对用户拍摄歪斜、折痕遮挡校验码前缀、或者印章盖在发票代码上的复杂情况正则解析率往往连 75% 都不到。我们的架构设计采用大模型结构化提取与前置推理 - 严格校验规则与国税报文生成 - 税务官方接口/合规网关查验 - 大模型双向比对防篡改审计的四层闭环[用户上传发票图片/PDF] │ ▼ [Vision-LLM 智能提取] ──(提取票种、代码、号码、日期、金额、校验码、印章文本) │ ▼ [Zod Schema 校验与修剪] ──(校验日期格式 YYYY-MM-DD、校验码位数、金额数字) │ ▼ [税务查验接口调用网关] ──(调取国税底账数据库获取发票状态正常/作废/红冲) │ ▼ [双向一致性比对引擎] ──(比对图片提取的明细与国税底账官方开票明细是否一致) │ ┌────┴────────────────────────┐ ▼ ▼ [比对完全一致: 自动入账归档] [存在篡改/作废: 拦截并输出审计差异报告]核心实现大模型发票关键要素解析与 Schema 约束首先我们要用大模型将用户上传的多模态图片或 PDF 转化为结构化查验要素。我们通过清晰的 System Prompt 和严格的 Zod Schema 约束不仅让模型提取关键字段还让模型自动识别发票类型数电专票、数电普票、传统专票、传统普票// src/services/invoice/types.ts import { z } from zod; export const InvoiceInspectionPayloadSchema z.object({ invoiceType: z.enum([ VAT_SPECIAL, // 传统增值税专用发票 VAT_ORDINARY, // 传统增值税普通发票 / 电子普票 FULLY_DIGITAL_SPEC, // 数电专用发票 FULLY_DIGITAL_ORD, // 数电普通发票 ]), invoiceCode: z.string().optional().describe(发票代码数电票可为空), invoiceNumber: z.string().min(8).max(20).describe(发票号码数电票为20位), issueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 日期必须为 YYYY-MM-DD 格式), totalAmountWithoutTax: z.number().optional().describe(不含税金额专票查验必填), checkCode: z.string().optional().describe(校验码普票查验需要后6位), buyerName: z.string().describe(购买方名称), buyerTaxNo: z.string().describe(购买方纳税人识别号), sellerName: z.string().describe(销售方名称), sellerTaxNo: z.string().describe(销售方纳税人识别号), totalTaxAmount: z.number().describe(税额), totalAmountWithTax: z.number().describe(价税合计总额), }); export type InvoiceInspectionPayload z.infertypeof InvoiceInspectionPayloadSchema;提取服务负责调用视觉大模型这里一定要在 Prompt 中强调对校验码和金额的容错清洗。例如增值税普票的校验码经常是 20 位数字通常以 5 位一组用空格分开用户容易只拍到后半段模型需要准确识别出最后 6 位有效数字// src/services/invoice/extractor.ts import { GoogleGenAI } from google/genai; import { InvoiceInspectionPayloadSchema, InvoiceInspectionPayload } from ./types; const ai new GoogleGenAI(); export async function extractInvoiceVerificationData( imageBase64: string, mimeType: string image/jpeg ): PromiseInvoiceInspectionPayload { const prompt 你是一位资深中国税务发票审计专家。请仔细审阅传入的发票图像高精度提取用于国家税务总局查验平台的核心要素。 注意事项 1. 区分数电票与传统增值税发票若发票标题为“电子发票增值税专用发票”且发票号码为20位、无独立发票代码判定为 FULLY_DIGITAL_SPEC普通发票同理。 2. 校验码Check Code若为普通发票务必提取完整的校验码去除空格。 3. 金额解析必须为纯浮点数字不要包含货币符号如、$。 4. 开票日期务必统一格式为 YYYY-MM-DD。 请严格输出符合要求的 JSON 对象严禁包裹任何 Markdown 标记之外的废话。 ; const response await ai.models.generateContent({ model: gemini-2.5-flash, contents: [ { role: user, parts: [ { inlineData: { mimeType, data: imageBase64 } }, { text: prompt }, ], }, ], config: { responseMimeType: application/json, }, }); const rawJson JSON.parse(response.text || {}); // 使用 Zod 运行时强校验防御 return InvoiceInspectionPayloadSchema.parse(rawJson); }组装官方查验报文与税务网关交互获取到纯净的结构化数据后系统需要根据发票类型动态组装查验报文。由于税局官方 Web 端有复杂的滑块验证码实际生产环境必须接入具备税控资质的合规查验 API 网关服务商。网关会直连税局底账系统返回官方登记数据// src/services/invoice/verifier.ts import { InvoiceInspectionPayload } from ./types; export interface TaxAuthorityRecord { isValid: boolean; invoiceStatus: NORMAL | CANCELLED | RED_MARKED | NOT_FOUND; officialCode?: string; officialNumber: string; officialIssueDate: string; officialAmount: number; officialBuyerTaxNo: string; officialSellerTaxNo: string; goodsList: Array{ name: string; specification?: string; unitPrice?: number; quantity?: number; amount: number; }; } export async function queryTaxBureauGateway( data: InvoiceInspectionPayload ): PromiseTaxAuthorityRecord { // 根据不同票种决定查验传参 let queryParams: Recordstring, string { invoiceNo: data.invoiceNumber, invoiceDate: data.issueDate.replace(/-/g, ), // 部分接口要求 YYYYMMDD }; if (data.invoiceType VAT_SPECIAL) { if (!data.invoiceCode || data.totalAmountWithoutTax undefined) { throw new Error(专票查验必须包含发票代码和不含税金额); } queryParams.invoiceCode data.invoiceCode; queryParams.checkValue data.totalAmountWithoutTax.toFixed(2); } else if (data.invoiceType VAT_ORDINARY) { if (!data.invoiceCode || !data.checkCode) { throw new Error(普票查验必须包含发票代码和校验码后6位); } queryParams.invoiceCode data.invoiceCode; queryParams.checkValue data.checkCode.slice(-6); } else { // 数电发票查验 queryParams.checkValue (data.totalAmountWithoutTax || data.totalAmountWithTax).toFixed(2); } const response await fetch(process.env.TAX_GATEWAY_URL!, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${process.env.TAX_GATEWAY_API_KEY}, }, body: JSON.stringify(queryParams), }); if (!response.ok) { throw new Error(税控网关请求失败: ${response.statusText}); } const result await response.json(); return result.data as TaxAuthorityRecord; }防篡改核心多模态识别与税局底账的双向审计比对查验接口返回了官方底账数据系统就安全了吗绝对不是。黑灰产最常见的一种作案手段是拿一张真实的废弃发票底账查验状态是“正常”但是通过 PS 篡改了图片上的金额、抬头或者货物明细。如果你的系统仅仅看税局返回了isValid: true就会把一张票面被改成 98,000 元但官方实际只有 980 元的发票报销入账因此必须引入双向一致性核对引擎。我们将大模型识别出的完整票面信息与税局官方底账返回的信息逐一对比一旦发现关键字段偏差立刻触发预警并输出详细审计原因// src/services/invoice/audit.ts import { InvoiceInspectionPayload } from ./types; import { TaxAuthorityRecord } from ./verifier; export interface AuditResult { passed: boolean; riskLevel: NONE | MEDIUM | HIGH; discrepancies: string[]; } export function auditInvoiceDiscrepancy( extracted: InvoiceInspectionPayload, official: TaxAuthorityRecord ): AuditResult { const discrepancies: string[] []; // 1. 检查官方发票状态 if (official.invoiceStatus CANCELLED) { discrepancies.push(严重风险该发票在税务系统已被开具方作废); } else if (official.invoiceStatus RED_MARKED) { discrepancies.push(严重风险该发票已被全额或部分红字冲销); } else if (official.invoiceStatus NOT_FOUND) { discrepancies.push(严重风险税务局底账查无此票疑似伪造假票); } // 2. 检查购买方纳税人识别号是否篡改 if (official.officialBuyerTaxNo extracted.buyerTaxNo ! official.officialBuyerTaxNo) { discrepancies.push( 税号不匹配票面提取税号 [${extracted.buyerTaxNo}] 与官方底账 [${official.officialBuyerTaxNo}] 不一致 ); } // 3. 检查金额偏差精度控制在 0.01 元内 const extractedAmount extracted.totalAmountWithoutTax || extracted.totalAmountWithTax; if (Math.abs(extractedAmount - official.officialAmount) 0.01) { discrepancies.push( 金额被篡改票面提取金额 [${extractedAmount}] 与官方实际开具金额 [${official.officialAmount}] 存在重大偏差 ); } const hasHighRisk discrepancies.some(d d.includes(严重风险) || d.includes(篡改)); return { passed: discrepancies.length 0, riskLevel: hasHighRisk ? HIGH : discrepancies.length 0 ? MEDIUM : NONE, discrepancies, }; }落地效果与独立开发者的避坑心得我们在财务报销助手 SaaS 中落地这套大模型多模态加税务底账校验后客户报销审批流的拦截准确率达到了 99.8%彻底防御虚假作废票过去员工离职前拿作废票套现报销的情况被 100% 自动拦截系统会在 1.2 秒内把作废红章打在审批界面上。PS 阴阳发票无处遁形部分商户为了少交税给客户开 500 元票但在客户软件里 PS 成 5,000 元双向比对引擎直接标红阻断自动生成财务审计差异邮件。查验成本节约优化很多初学者一拿到图片就直接调一次 0.2 元的税务查验 API结果图片根本不是发票例如用户误传了支付截图。我们先由大模型多模态在 0.005 元成本下完成结构化合规校验只有判定为合法发票版式且要素齐全时才下发付费税务接口直接替我们降低了 60% 以上的无谓网关开销。搞数据提取千万不要停留在“大模型提取出了 JSON”的浅层自嗨。把非结构化识别与法定官方底账形成双向校验闭环才是独立开发技术变现中最具壁垒的核心竞争力。
阅读完成 · 觉得有帮助?